函数是指将代码分割成可复用的片段
<br />一般来说,一个函数是可以通过外部代码调用的一个“子程序”(或在递归的情况下由内部函数调用)。像程序本身一样,一个函数由称为函数体的一系列语句组成。值可以传递给一个函数,函数将返回一个值。<br />在 JavaScript中,函数是头等(first-class)对象,因为它们可以像任何其他对象一样具有属性和方法。它们与其他对象的区别在于函数可以被调用。简而言之,它们是[Function](https://developer.mozilla.org/zh-cn/JavaScript/Reference/Global_Objects/Function)对象。<br />也可以叫函数是action object<br />如果函数挂载在对象上,就称它为对象的方法<br />函数是“可调用对象”,它有一个内部属
性 [[Call]],该属性使其可以被调用。
函数有内部属性[[Construct]],表明该函数可以作为构造函数,箭头函数没有这个属性,说明不能作为构造函数
函数种类
普通函数,由 function关键字定义
箭头函数
generator 函数
异步函数
内部属性
[[call]]
如果该对象有 call 内部属性,说明可以调用是函数对象,typeof 返回 function 是因为根据 call
[[construct]]
如果有 construct 内部属性的函数可以作为构造函数被 new 调用,只有普通函数和 class 定义的类有 constuct
[[scopes]]
var a = 1;
function fun(){
var b = 1;
const p = function p () {
console.log(b)
console.dir(p)
}
p();
}
fun();
https://segmentfault.com/a/1190000012842459
[[FunctionLocation]]
类似于debugger功能,可以很容易的查找到此函数的代码位置,
描述
在JavaScript中,每个普通函数其实都是一个Function对象。
(function(){}).constructor === Function // true
(() => {}).constructor === Function // true
(class {}).constructor === Function // true
(async function (){}).constructor === Function // false
(function *(){}).constructor === Function // false
(async function *(){}).constructor === Function // false
如果一个函数中没有使用return语句,则它默认返回undefined。
函数定义
函数声明语句
函数名是变量名,指向函数对象
- 一对圆括号里面指定参数
- 一堆花括号里面是函数体,调用函数的时候执行
- this指向函数的调用上下文,
- 函数是动态作用域,在运行时绑定
- 函数声明语句是函数提升,声明和函数体都会提前,重名还会被覆盖
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
// 预编译 定义 foo,重名第二个覆盖第一个
gc = {
vo: {
foo: reference -> function foo() { console.log('foo2');}
}
}
// 执行的时候略过预编译声明的函数
函数表达式
- 函数名可选,指代函数对象本身,没有函数名的叫匿名函数,当函数只使用一次用 IIFE
- 函数定义表达式是变量提升,跟其他用 var 定义的变量一样,只是把变量的声明提前了,变量的初始化代码还在原来的位置
//匿名函数表达式
var foo = function (){}
//命名函数表达式
var foo = function bar(){}
foo()// 成功
bar() // 不成功,函数名只能在内部调用
// 如果有名称,函数的名称将会成为函数内部的一个局部变量, 可以用来递归
var foo = function bar(n){
if (n === 0) {
return 0
}
bar(--n)
}
foo(3) // 3 2 1 0
// 赋给变量
var s = function () {}
s()
// 赋值给对象的属性
var o = {
prop: function () {}
}
// 函数做为参数
function func(callback) {
callback()
}
// 作为数组元素
var a = [function (x){x*x}]
a[0](2) // 4
箭头函数表达式 (=>)
箭头函数看起来和常规的匿名函数很相似,但它们本质上完全不同
箭头函数不能显式地命名,尽管现代运行环境会将箭头函数所赋予 的变量名作为函数名
箭头函数不能用作构造函数,也没有 prototype 属性,这意味着不能对它们使用 new 关键字;
箭头函数会绑定到 所在词法作用域中(最近的函数),因此它们不会改变 this 的指向。this 、arguments 以及 super 均属于所在的父级作用域。 call() 、apply() 、bind() 等方法调用函数时也无法改变 this 的指向。
当需要定义任何情况下词法作用域都不改变的匿名函数时,箭头函数很 适合,
定义
([param] [, param]) => { statements } param => expression
- param
参数名称. 零参数需要用()或_表示. 只有一个参数时不需要括号. (例如 foo => 1)
- statements or expression
多个声明statements需要用大括号括起来,而单个表达式时则不需要。表达式expression也是该函数的隐式返回值。
参数简写
let hello = () => {
console.log('hello')
}
// 或者
_ => {}
// 只有一个参数,可以省略括号
let hello = item => {
console.log(item)
}
函数体简写
// 如果是表达式的话,可以省略括号
// 表达式计算后返回
let hello = (x,y,z) => x + y + z
hello(1,2,3)
// 隐式返回对象字面量
let hello = (x,y,z) => (x+y+z)
let hello = (x,y,z) => {
return x+y+z
}
// 和变量解构结合
let func = ({value, num}) => ({total: value * num})
// 使用
var result = func({
value: 10,
num: 10
})
console.log(result); // {total: 100}
Function构造函数
不推荐使用 Function 构造函数创建函数,因为它需要的函数体作为字符串可能会阻止一些JS引擎优化,也会引起其他问题。
函数的参数
形参和实参
函数的形参: 定义时候的参数 parameter
函数的实参: 调用是的的参数 arguments
实参是形参一个拷贝,作为函数变量
所以修改引用值的参数的属性,会修改形参
如果对这个引用值的参数赋值了,就不会给改变
var value = 1;
function foo(v) {
v = 2;
console.log(v); //2
}
foo(value);
console.log(value) // 1
// 按引用传递
var obj = {
value: 1
};
function foo(o) {
// o 和 obj 都指向{value: 1}这个对象
// 但是 o和 obj 不是同一个变量
// 相当与 var o = obj
o.value = 2;
console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2
//其实是共享传递,参数如果是基本类型是按值传递,如果是引用类型按共享传递。
var obj = {
value: 1
};
function foo(o) {
o = 2;
console.log(o); //2
}
foo(obj);
console.log(obj.value) // 1
参数类型
- 传值 (基本数据类型值 primitive type,比如Undefined,Null,Boolean,Number,String。)
- 传对象 (引用类型值 也就是对象类型 Object type,比如Object,Array,Function,Date等。)
函数默认参数
es5
function f (x,y) {
y = (typeof y !== 'undefined') ? y : 10
// 或者是y = y || 10; y 传了‘’就不行
return x + y
}
f(1) // 11
es6
- 如果默认参数在最后面
function foo (a, b, c = 1) {
console.log(a, b, c)
}
foo(1, 2)
- 如果默认参数在中间 ```javascript function foo1 (a, b = 1, c) { console.log(a, b, c) }
foo1(1, 2) // => 1,2 undefined foo1(1, undefined, 2) // => 1, 1, 2 foo1(1, null, 2) // => 1, null, 2 foo1(1, NaN, 2) // => 1, null, 2 // 上面的结果显示了函数参数的优先级, // 函数参数的优先级:undefined < 默认参 < 除undefined以外的实参 (NaN / null)
默认参数就是可选的参数,放在最后<br />一般把可选参数做成对象,放在最后
- 默认参数可以是表达式
```javascript
function foo (x, y = 7, z = x + y) {
return x * 10 + z
}
foo(1, undefined, 2) // 12
foo(1) // 18
foo(1,9) // 20
// 但是参数不定
函数不定参数
arguments
arguments 是 es5的做法,不推荐使用
- arguments 是 arraylike,object。不是 array
在js中,允许函数定义参数与传入的参数的数量不一致
函数运行时执行的时候有一个隐藏参数arguments,它包括了函数所要调用的参数
它不是数组,typeof arguments 返回的是’object’
虽然可以用length和index,所有的 Array.prototype的方法都不能用,比如但push,pop,arguments.forEach
Array.isArray(arguments) // ==> false
typeof arguments // ==> 'object'
function sum2(x,y) {
arguments[0] = 1;
let sum = x + y
return sum
}
console.log(sum2(2,2));
意外修改了 参数 x,使用前最好拷贝下
function sum3(x,y) {
let arr = [].slice.call(arguments)
console.log(arr)
arr[0] = 1;
let sum = x + y
return sum
}
- 如何转化成 array
转换成标准的数组
var args = Array.prototype.slice.call(arguments);
手工拷贝
var args = []
var len = arguments.length;
while ( len-- ) {
args[ len ] = arguments[ len ];
}
直接使用
Array.prototype.forEach.call(arguments, function (item) {})
Array.from(arguments).forEach(function (item) {})
rest parameter
es6使用…扩展符替代 arguments
扩展运算符(…):可以理解成一个默认的遍历器
如果函数的最后一个命名参数以…为前缀,则在函数被调用时,该形参会成为一个数组,数组中的元素都是传递给该函数的多出来的实参的值。这代表多出来的实参的值
// 所有的参数都放在这个 nums 中
// ...nums 就是把 nums 的参数数据解构成参数
// ..nums 相当于 num0,num1...
function sum (base,...nums) {
let num = base;
nums.forEach(function (item) {
num += item
})
return num
}
sum(1,2,3) // 9
rest parameter 和 arguments 对象的区别
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
- arguments 对象不是一个真实的数组,而剩余参数是真实的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach,pop。
- arguments 对象对象还有一些附加的属性 (比如callee属性)。
- 修改了arguments,可能会意外修改了参数
函数自定义属性
函数也是一种特殊的对象,也有属性
var func = function () {
return func.count += 1
}
func.count = 0;
func.count // 0
var f1 = new func()
func.count // 1
count是 func 函数的属性,调用依次 func 函数,count 增加1,注意 count 在 func 函数上,不在实例对象上
作为命名空间的函数
因为以前的 js 没有块作用域,所以简单定义一个函数作为临时的命名空间
function mymodule() {
}
mymodule()
这样 mymodule还是全局变量
(function () {
})()
块级函数
非严格模式下的块级函数
严格模式下
this 指针
let test = {
name: 'test',
say: function () {
console.log(this.name)
}
}
test.say(); // test
let test = {
name: 'test',
say: () => {
console.log(this.name, this)
}
}
// this 是宿主,全局对象
test.say(); // undefined window
let _this = this
let test = {
name: 'test',
say: () => {
console.log(_this.name, _this)
}
}
// this 是宿主,全局对象
test.say(); // undefined window
//匿名函数
var obj = {
birth : 1996,
getAge : function(){
var b = this.birth;
console.log(b) // 1996
var fn = function(){
return (new Date().getFullYear() - this.birth);
}
return fn();
}
}
obj.getAge();//NaN
// return (new Date().getFullYear() - this.birth);
// fn()的上下文环境是全局对象
//箭头函数
var obj = {
birth : 1996,
getAge : function(){
var f = () => new Date().getFullYear() - this.birth;
return f();
}
}
obj.getAge();//24
var obj = {
birth : 1996,
getAge : function(){
var f = function () {
return new Date().getFullYear() - this.birth;
}
return f.call({birth: 2000});
}
}
obj.getAge();//20
var obj = {
birth : 1996,
getAge : function(){
var _this = this
var f = function () {
return new Date().getFullYear() - _this.birth;
}
return f();
}
}
obj.getAge();//24
//由于箭头函数中的this已绑定了词法作用域,
//因此call()或者apply()调用箭头函数时,传入的第一个参数无效。
var obj = {
birth : 1996,
getAge : function(){
var f = () => new Date().getFullYear() - this.birth;//this.birth仍为1996,而非2000
return f.call({birth: 2000});
}
}
obj.getAge();//24
普通函数和构造函数的区别
定义:如果一个函数(使用new运算符)用来初始化一个对象,称为构造函数(constructor)
类比:java中使用与类名同名的函数作为构造函数
箭头函数和普通函数的区别
- 箭头函数没有 this
所以需要通过查找作用域链来确定 this 的值。
这就意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this。
因为箭头函数没有 this,所以也不能用 call()、apply()、bind() 这些方法改变 this 的指向,可以看一个例子:
var value = 1;
var result = (() => this.value).bind({value: 2})();
console.log(result); // 1
- 没有 arguments,但是可以访问外围函数的 arguments 对象
function constant() {
return () => arguments[0]
}
var result = constant(1);
console.log(result()); // 1
那如果我们就是要访问箭头函数的参数呢?
你可以通过命名参数或者 rest 参数的形式访问参数:
let nums = (...nums) => nums;
- 不能通过 new 关键字调用
JavaScript 函数有两个内部方法:[[Call]] 和 [[Construct]]。
当通过 new 调用函数时,执行 [[Construct]] 方法,创建一个实例对象,然后再执行函数体,将 this 绑定到实例上。
当直接调用的时候,执行 [[Call]] 方法,直接执行函数体。
箭头函数并没有 [[Construct]] 方法,不能被用作构造函数,如果通过 new 的方式调用,会报错。
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
- 没有原型,没有 super
由于不能使用 new 调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在 prototype 这个属性。
var Foo = () => {};
console.log(Foo.prototype); // undefined
没有重载
function aa (a) {return a};
function aa (a,b) {return a+b};
aa(1) // NaN
aa(1+2) // 3
相当与
var aa = function (a) {return a};
var aa = function (a,b) {return a+b};
aa(1) // NaN
aa(1+2) // 3
总结
箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数(non-method functions),并且它们不能用作构造函数。
对象属性中的函数就被称之为 method,那么 non-mehtod 就是指不被用作对象属性中的函数了,可是为什么说箭头函数更适合 non-method 呢?
var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log( this.i, this)
}
}
obj.b();
// undefined Window
obj.c();
// 10, Object {...}
自执行函数
(function(){
console.log(1)
})()
(() => {
console.log(1)
})()
函数表达式和函数声明的区别
在预编译阶段,遇到函数声明会被添加到 AO 中
a()
function a() {
console.log(1)
}
function a() {
console.log(2)
}
a();
var a = function () {
console.log(3)
}
a()
// 2 2 3
执行没有报错
在预编辑阶段,遇到 var 定义的变量,指向 undefined 添加到 AO 中
foo() // foo is not a function
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
// 创建全局上下文执行
// 扫描所有的 var
gc = {
vo: {
foo:undefined
}
}
// 所有的声明都没了