第十章 函数
1. 箭头函数
1) 不使用function关键字;
2) 参数有零个或多个必须用括号;
3) 函数体只有一行可以不写花括号,但这样暗示语句即返回值,此时不能再写return;
4) this值为函数定义时的上下文对象;
5) 箭头函数语法简洁,但不能使用arguments、new.target、super、没有prototype属性;
6) 不能用作构造函数;
2. 函数名
1) 函数名即指向函数对象的指针,后不带括号表示访问函数指针,不会执行函数;
2) 函数对象具有属性name,函数没有命名则该值为空串,Function构造函数生产的函数实例该值被标识为“anonymous”;
3) 对于bind()生成的函数以及获取、设置函数,name值为前缀+空格+函数名,分别是bound、get、set;
3. 参数
JS中函数对参数的长度和数据类型没有限制;
3.1function关键字定义的函数
1) 函数内部可以访问arguments对象——类数组对象(不是Array实例)取得传入的参数值,arguments对象始终只反映传入参数,可以通过中括号语法取得传入参数:arguments[0]表示传入的第一个参数
2) arguments.length属性表示传入参数个数
3) arguments可以和命名参数组合使用,如命名参数为function f(x,y),调用时f(1,2),则arguments[0]为1,arguments[1]为2,此时可以设置arguments[0], arguments[1]的值,x,y会与之同步;改变x,y的值,arguments也会与之同步,但是两者并未共享内存
4) 接3),如果只传入一个参数f(1),则arguments[1]将是undefined,修改它不会有任何作用,也不会与y同步(arguments对象始终只反映传入参数,传入一个参数那么它的有效长度是1)
即使函数内部给命名参数值,arguments仍然只反映传入参数:
5) 严格模式下不能重写arguments对象,修改它也不会同步给命名参数
3.2箭头函数
1) 无法使用arguments对象
2) 但可以在外面包装函数(function定义的),用闭包的方式使用arguments对象
4. 默认参数
1) 可以通过在定义函数时使用“命名参数=值”来给函数设置默认参数,没有传参时使用默认值
2) 默认参数不会反映到arguments对象上,它仍然只与传入参数相关
3) 当函数有多个命名参数时,可以通过给参数传undefined来占位,从而使用其默认值
4) 默认参数的值也可以是函数返回值
5) 默认参数的值只有在被调用时才会开始计算、定义时不会
6) 箭头函数使用默认参数时,即使只有一个参数也要括起来:(name=’Anna’)=>{}
7) 多个默认参数存在暂时性死区,即不能使用未定义的参数,后面定义的可以使用前面定义的
8) 默认参数也存在自己的作用域
5. 参数扩展与收集
5.1扩展操作符
1) …扩展操作符将可迭代对象拆分,将迭代传回的每个值依次传入
2) Arguments依然检验传入参数的个数,即扩展出来多少个,它就会当作是多少个
3) 扩展参数能够对应上命名参数,传多了用不上,传少了undefined
5.2收集参数
1) 接收参数时也可以使用扩展操作符收集参数,但结果会是一个数组
2) 定义函数时尽量将之放在参数
6. 函数声明和函数表达式
1) 函数声明提升:函数声明会在所有代码执行前先被读取并添加到上下文,不能在条件语句中声明同名函数
2) 函数表达式则不能在定义之前调用函数
3) 函数表达式定义的函数是匿名函数(兰姆达函数),name为空串
7. 函数作为值
1) 函数可以作为参数传给其他函数,此时使用函数名不带括号相当于一个指针
2) 函数也可以作为返回值
8. 函数内部
A. arguments
1) 类数组对象,不是数组,不是Array实例
2) 具有callee属性,arguments.callee指向arguments所在函数,在递归函数中可以做到逻辑与函数名解耦,防止函数改名后内部功能失效(还有一个方法不使用arguments.callee也可以解耦即使用命名函数表达式)
B. this
1) 标准函数中:this表示调用函数的上下文对象
2) 箭头函数中:定义箭头函数时的上下文对象
3) 事件回调或定时回调时建议使用箭头函数,将this值固定为定义时的上下文,使用标准函数this值可能会出错
C. caller
函数对象的一个属性,指向调用当前函数的函数,全局作用域中为null
D. new.target
如果函数使用new关键字调用,new.target将引用被调用的构造函数;否则其值为undefined
9. 函数属性与方法
1) length:命名参数的个数
2) prototype:不可枚举、保存共享的实例方法如toString(),valueOf()
3) apply(this指向,参数数组(还可以是arguments)),call(this指向,参数,参数,参数……) :以指定的this值调用函数
4) bind(this值):不会调用函数,而是返回绑定了新this值的函数实例
10. 尾调用优化
对于外部函数返回值是内部函数返回值的情况:
也即如果执行到外部函数返回值时发现需要返回的是内部函数的返回值,则将外部函数栈帧弹出,压入内部函数的栈帧,以此减少嵌套调用的栈帧;
优化条件:
11. 闭包
1) 闭包指的是使用了其他函数作用域中变量的函数;
2) 执行上下文中有一个包含其变量的对象,全局上下文中它叫变量对象,在代码执行期间始终存在;函数的局部上下文中叫活动对象,在函数执行期间存在;定义函数时会为它创建作用域链,预装载全局变量对象,并保存在[[Scope]]中;调用函数时会为它创建相应的上下文并复制[[Scope]],接着创建函数的活动对象并将其推入作用域链的前端;因此内部函数的作用域链包含有外部函数的活动对象。
3) 一般函数执行完毕,局部活动变量就会被销毁,但闭包引用了外部函数变量,即使外部函数执行完毕,其执行上下文的作用域链被销毁,但它的活动对象仍然保留直到闭包被销毁;
4) 如果闭包不是箭头函数,那么其中的this本来应该指向执行函数的上下文,但由于内部函数无法直接访问外部函数的this和arguments,所以内部函数没法使用外部函数的this,所以它的this不会绑定到某个对象,就会指向window;
5) 解决(4):把外部函数的this保存到一个变量中,这样内部函数就可以访问这个变量,间接获取到this值
6) 闭包可能导致内存泄漏;
12. 立即调用函数表达式
1) 立即调用的函数表达式(IIFE),形如函数声明但由于包含在括号中,会被解释为函数表达式,紧跟其后的第二对括号会立即调用前面的函数:
2) ES5及以前常使用IIFE来模拟块级作用域,ES6之后可以使用花括号
3) for循环中使用var定义循环变量i则i不会被限制在for的块级作用域中,存在循环体外部;使用let则会为每次循环创建独立变量,(let要写在for()里,写在外面仍然是不生效的)。
13. 私有变量
1) 任何定义在函数或块中的变量都可以认为是私有的;
2) 特权方法是能够访问函数私有变量的公有方法;
3) 创建特权方法有两种实现——构造函数和静态私有变量:
a) 在构造函数中定义特权方法,利用闭包访问同在构造函数中的私有变量;缺点是每个实例都会重新创建一遍方法,函数没有复用;
b) 静态私有变量避免重复创建函数,但实例会同时共享方法和属性;声明 MyObject没有使用关键字,会创建在全局作用域中,所以 MyObject 变成了全局变量,可以在这个私有作用域外部被访问。
c) 模块模式使用匿名函数立即调用返回一个对象,包含可以公开访问的属性和方法,这个方法可以访问匿名函数内部的私有变量(特权方法)。适用于单例对象(只有一个实例的对象);
d) 模块增强模式也就是在模块模式中,返回对象之前先对其进行增强,为其它添加额外属性或方法。