《深入理解ES6》之扩展对象的功能

对象类别

ES6规范清晰定义了每一个类别的对象

  • 普通对象 具有JavaScript对象所有的默认行为
  • 特异对象 具有某些与默认行为不符的内部行为
  • 标准对象 ES6规范中定义的对象,例如ArrayDate等。标准对象既可以是普通对象,也可以是特异对象。
  • 内建对象 脚本开始执行时存在于JavaScript执行环境中的对象,所有标准对象都是内建对象。

对象字面量语法扩展

属性初始值的简写

当一个对象的属性与本地变量名同名时,不必再写冒号和值,简单的写属性名即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
//ES5
// function createPerson(name, age){
// return {
// name: name,
// age: age
// }
// }
function createPerson(name, age){
return {
name,
age
}
}

当对象字面量里只有一个属性的名称时,JavaScript引擎会在可访问作用域中查找同名变量。如果找到,则变量的值被赋给对象字面量里的同名属性。

对象方法的简写

之前的版本,如果为对象添加方法,必须通过指定名称并完整定义函数来实现。再ES6中,语法更加简洁,消除了冒号和关键字function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ES5
// var person = {
// name: 'Nicholas',
// sayName: function() {
// console.log(this.name);
// }
// }
var person = {
name: 'Nicholas',
sayName() {
console.log(this.name);
}
}

可计算属性名

在ES5及早期版本中,引用属性,有点方法和方括号方法。当引用一个通过计算得到的属性名或者改属性名是一个非法的标识符(数字,字母,下划线,美元符,数字不能开头),只能使用方括号方法。

1
2
3
4
5
6
7
8
var person = {},
lastName = 'last name';
person['first name'] = 'Nicholas';
person[lastName] = 'Zakas';
console.log(person['first name']); //'Nicholas'
console.log(person[lastName]); //'Zakas'

在对象字面量中,可以直接使用字符串字面量作为属性名称(如果包含非法的标识符,属性名加引号)

1
2
3
4
var person = {
'first name': 'Nicholas'
}
console.log(person['first name']); //'Nicholas'

如果属性名称first name被包含在一个变量中,或者需要通过计算才能得到该变量的值,那么在ES5是无法为一个对象字面量定义该属性。

在ES6中,可以在对象字面量中使用可计算属性名称,其语法与引用对象实例的可计算属性名称相同使用方括号。

1
2
3
4
5
6
7
8
9
let lastName = 'last name';
let person = {
'first name': 'Nicholas',
[lastName]: 'Zakas'
}
console.log(person['first name']); //'Nicholas'
console.log(person['last name']); //'Zakas'

在对象字面量中使用方括号表示的该属性名称是可计算的,他的内容将被求值最终转化为一个字符串,所以可以使用表达式作为属性的可计算名称

1
2
3
4
5
6
7
8
9
let suffix = 'name';
let person = {
['first ' + suffix]: 'Nicholas',
['last ' + suffix]: 'Zakas'
}
console.log(person['first name']); //'Nicholas'
console.log(person['last name']); //'Zakas'

任何可用于对象实例括号记法的属性名,也可以作为对象字面量中的计算属性名。

新增方法

Object.is()方法

在JavaScript中比较两个值时,可以使用相等运算符(==)或全等运算符(===),前者会触发强制类型转换。但是全等运算符也不完全准确,比如+0和-0,使用全等运算符===进行比较,等到的是两者相等。

ES6中引入Object.is()方法来弥补全等运算符的不准确运算。改方法接受两个参数,如果这两个参数类型并且值相同,则返回true

1
2
3
4
5
6
7
console.log(-0 == +0); //true
console.log(-0 === +0); //true
console.log(Object.is(-0,+0)); //false
console.log(NaN == NaN); //false
console.log(NaN === NaN); //false
console.log(Object.is(NaN, NaN)); //true

Object.assign()方法

Object.assign用来实现一个对象接收其他对象的属性和方法,可以接受任意数量的源对象,并按指定顺序将属性复制到接收对象中。如果有同名属性,排位靠后的源对象会覆盖前面的。

1
2
3
4
5
6
7
8
9
10
11
12
13
var receiver = {};
Object.assign(receiver,
{
type: 'js',
name: 'file.js'
},
{
type: 'css'
}
)
console.log(receiver.type); //'css',后面同名属性覆盖前面
console.log(receiver.name); //'file.js'

Object.assign()方法不能将提供者的访问器属性复制到接收对象中。Object.assign()方法执行的式赋值操作,所以访问器属性最终会转变为接收对象中的一个数据属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
var receiver = {},
supplier = {
get name(){
return 'file.js'
}
};
Object.assign(receiver, supplier);
var descriptor = Object.getOwnPropertyDescriptor(receiver, 'name');
console.log(descriptor.value); //'file.js'
console.log(descriptor.get); //undefined

重复的对象字面量属性

ES5中严格模式会对对象字面量的重复属性进行校验,存在重复同名属性时抛出错误。ES6中,无论严格模式还是非严格模式,不再检查重复属性,对于重复属性,都会选取最后一个。

1
2
3
4
5
6
7
'use strict'
var person = {
name: 'Nicholas',
name: 'Greg' //ES6严格模式下没有错误
}
console.log(person.name); 'Greg'

自有属性枚举顺序

ES6严格规定了对象的自有属性被枚举时的返回顺序,这会影响到Object.getOwnPropertyNames()以及Reflect.ownKeys返回属性的方式,Object.assign()方法处理属性的顺序也将随之改变。规则是

  • 所有数字键按升序拍讯
  • 所有字符串键按照它们被加入对象的顺序排序
  • 所有symbol键按照他们被加入对象的顺序排序
1
2
3
4
5
6
7
8
var obj = {
a: 1,
2: 1,
c: 1,
1: 1
}
obj.b = 1;
console.log(Object.getOwnPropertyNames(obj).join('')); //12acb

for-in循环,没有一个明确的枚举顺序。Object.keys()JSON.stringify()方法和for-in使用相同的枚举顺序。

增加对象原型

改变原型对象

正常情况下,无论是通过构造函数还是Object.create()方法创建对象,其原型是在对象被创建时指定的。在ES5中缺少对象在实例化后改变原型的标准方法。

ES6中,添加了Object.setPrototypeof()方法,通过该方法可以改变指定对象的原型,它接受两个参数,被改变原型的对象以及替代第一个参数原型的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let person = {
getGreeting(){
return 'hello'
}
}
let dog = {
getGreeting(){
return 'woof'
}
}
let friend = Object.create(person);
console.log(friend.getGreeting()); //'hello'
console.log(Object.getPrototypeOf(friend) === person); //true
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting()); //'woof'
console.log(Object.getPrototypeOf(friend) === dog); //true

Super

ES6引入Super关键字,Super引用相当于指向对象原型的指针,他可以便捷访问对象原型。比如需要重写对象实例的方法,有需要调用与它同名的原型方法,ES5中可以实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let person = {
getGreeting(){
return 'hello'
}
}
let dog = {
getGreeting(){
return 'woof'
}
}
let friend = {
getGreeting() {
return Object.getPrototypeOf(this).getGreeting.call(this) + ', hi';
}
}
//将原型设置为person
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting());
//将原型设置为dog
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting());

示例中,friend对象的getGreeting()调用了同名的原型方法。Object.getPrototypeOf()方法确保调用正确的原型,call(this)确保正确设置原型方法中的this

ES6中,可以简化上面的getGreeting()方法

1
2
3
4
5
6
let friend = {
getGreeting() {
// return Object.getPrototypeOf(this).getGreeting.call(this) + ', hi';
return super.getGreeting() + ', hi'
}
}

Super引用必须要在使用简写方法的对象中使用,如果在其他方法声明中使用会导致错误。

1
2
3
4
5
6
let friend = {
getGreeting: function() {
//语法错误
return super.getGreeting() + ', hi'
}
}

正式的方法定义

ES6中正式将方法定义为一个函数,他会有一个内部的[[HomeObject]]属性来容纳这个方法从属的对象。Super的所有引用都通过[[HomeObject]]属性来确定。首先是在[[HomeObject]]属性上调用Object.getPrototypeOf()方法来检索原型的引用,然后搜索原型找到同名函数,最后设置this绑定并且调用相应的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let person = {
getGreeting(){
return 'hello'
}
}
let friend = {
getGreeting: function() {
// return Object.getPrototypeOf(this).getGreeting.call(this) + ', hi';
return super.getGreeting() + ', hi'
}
}
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting());

示例中,friend.getGreeting()方法的[[HomeObject]]属性值是friendfriend的原型是person,所以super.getGreeting()等价于person.getGreeting.call(this)