本篇整理了《深入理解ES6》书中的函数和对象扩展章节中的重要知识点。
函数
函数形参的默认值
ES6语法
|
|
默认参数值对arguments对象的影响
- ES5非严格模式,函数命名参数的变化会体现在arguments对象上。
- ES5严格模式,取消了该行为,无论参数怎么变化,arguments不再随之改变。
- ES6中,如果使用了默认参数值,则无论是否显示定义严格模式,arguments对象的行为都将与ES5严格模式下保持一致。
默认参数表达式
默认参数是在函数调用时求值。
|
|
在引用默认值的时候,只允许引用前面的参数值,即先定义的参数不能访问后定义的参数。
|
|
处理无命名参数
不定参数
- 在参数前加
...
,表示这是一个不定参数。 - 每个函数最多可以声明一个不定参数,而且必须放在所有参数末尾。
- 不定参数不能用于对象字面量setter之中。
增强的Function构造函数
支持在创建函数时定义默认参数和不定参数。
展开运算符
与不定参最相似的是展开运算符。不定参数将多个参数整合为一个数组,而展开运算符刚好相反,可以将一个数组打散作为各自独立的参数传入函数。
|
|
name属性
ES6中所有函数的name属性都有一个合适的值。
|
|
明确函数的多重用途
ES5及早期版本中的函数具有多重功能,可以结合new
使用,函数内的this 将指向一个新对象,函数最终会返回这个新对象。
ES6中在js函数中定义了两个不同的内部方法:[[Call]]和[[Construct]]。当通过 new 关键字调用函数时,执行的是[[Construct]]函数,它负责创建一个被称作实例的新对象,然后再执行函数体,并将this绑定到该实例上;如果不是通过 new 关键字调用函数,则执行[[Call]]函数,从而直接执行代码中的函数体。具有[[Construct]]方法的函数被统称为构造函数。切记,并不是所有函数都有[[Construct]]方法,例如箭头函数。
ES5中判断函数被调用的方法
想确定一个函数是否通过 new 关键字被调用,最流行的方法是使用 instanceof。
|
|
通常来讲是没什么大问题的,但是并不可靠。可以通过Person.call()
调用成功。
原属性 new.target
为了解决是否通过 new 关键字调用的问题,ES6引入了new.target这个元属性。 元属性是指非对象的属性,其可以提供非对象目标的补充信息,如new。当调用函数的[[Construct]]方法时,new.target被赋值为new操作符的目标,通常是新创建的实例;如果调用[[Call]],new.target为undefined。所以可以通过检测new.target来确认是否通过new 关键字来调用的函数。
|
|
块级函数
- ES5严格模式下在代码块内部声明函数会报错;
- ES6代码块定义的函数视为一个块级声明,从而可以定一个该函数的代码块内访问和调用。块级函数会提升至顶部(严格模式下),在非严格模式下,不是提升至代码块顶部,而是提升至外围函数或全局作用域的顶部。
箭头函数
它与传统函数有些许不同,如下:
- 没有this、super、arguments和new.target绑定。
- 不能通过new 关键字调用。
- 没有原型。
- 不可以改变this的绑定。
- 不支持arguments对象。
- 不支持重复的命名参数
常用的一些写法:
|
|
尾部调用优化
在ES5的引擎中,尾调用的实现与其他函数调用的实现类似:创建一个新的栈帧,将其推入调用栈来表示函数调用。也就是说,在循环调用中,每一个未用完的栈帧都会保存在内存中。
ES6尾调用优化
如果满足以下条件,尾调用不再创建新的栈帧,而是清除并重用当前栈帧。
- 尾调用不访问当前栈帧变量。
- 在函数内部,尾调用时最后一条语句。
- 尾调用的结果作为函数值返回。
扩展对象的功能性
对象字面量语法扩展
属性初始值的简写
当一个对象的属性与本地变量同名时,不必再写冒号和值,只需要只写属性名即可。
对象方法的简写
可以省略冒号和function关键字。
可计算属性名
使用方括号表示的属性名是可计算的,它的内容将被求值并被最终转化为一个字符串。
新增方法
Object.is()
该方法用来弥补全等 === 运算符的不准确,举个例子 +0 和 -0在Javascript引擎中表示为两个完全不同的实体,而如果使用 === 比较得到的是 true;同样, NaN === NaN 的返回值为false,需要使用isNaN()方法才可以正确检测NaN。
|
|
Object.assign()
该方法可以接受任意数量的源对象,第一个参数为接收对象,会将其它对象复制到接收对象上。不能复制提供者的访问器属性。
重复的对象字面量属性
ES5严格模式加入了字面量重复属性的校验,当同时存在多个同名属性是会抛出错误。而在ES6中重复属性检查被移除了,不管是不是严格模式,重复属性出现时,只保留最后一个。
自有属性枚举顺序
ES5中未定义对象属性的枚举顺序,由Javascript引擎厂商自行解决。ES6严格规定了对象的自有属性被枚举时的返回属性,这会影响到Object.getOwnPropertyName()方法及Reflect.ownKeys返回属性的方式,Object.assign()方法处理属性的顺序也将随之改变。
自有属性枚举顺序的基本规则是:
- 所有数字键按升序排序;
- 所有字符串按照它们被加入对象的顺序排序;
- 所有symbol键按照它们被加入对象的顺序排序。
增强对象原型
改变对象的原型
ES6添加了Object.setPrototypeOf()方法来改变对象的原型。
|
|
简化原型访问的Super引用
ES6引入了Super
引用的特性,使用它可以更便捷地访问对象原型。举个例子,如果你想重写对象实例的方法,又需要调用与它同名的原型方法,在ES5中可以这样实现:
|
|
通过Object.getPrototypeOf()
方法和call来实现调用原型上的方法,略显复杂。ES6引入super
来解决该问题,Super引用相当于指向对象原型的指针,实际上也就是Object.getPrototypeOf(this)的值。于是,可以简化上面getGreeting方法:
|
|
正式的方法定义
ES6中正式将方法定义为一个函数,它会有一个内部的[[HomeObject]]属性来容纳这个方法从属的对象。请思考以下代码:
|
|
getGreeting()方法的[[HomeObject]]属性值为person。share没有明确定义[[HomeObject]]属性。
Super的所有引用都通过[[HomeObject]]属性来确定后续的运行过程。第一步是在[[HomeObject]]属性上调用Object.getPrototypeOf()方法来检索原型的引用;然后搜索原型找到同名函数;最后,设置this绑定并且调用相应的方法。
上面例子friend.getGreeting()方法的[[HomeObject]]为friend,而friend的原型为person,所以super.getGreeting()等价于person.getGreeting.call(this);