Object 语法扩展

属性简写

当我们使用字面量声明一个对象时,经常会有下面的写法

let person = {
name: name, // 第一个 name 是键名,第二个 name 是一个变量
age: age
}

其中键的名字和值的名字都是相同的,name,age 写了两遍,在 ES6 中提供了简写语法,当键的名称与值对应变量名相同时,那么就可以简写,如上可以简写为

let person = {
name,
age
}

方法简写

在 ES5 中如下定义方法

let person = {
name: "Alice",
sayHello: function() {
return "Hello";
}
}

在 ES6 中提供了更加简洁的写法

let person = {
name: "Alice",
sayHello() {
return "Hello";
}
}

简写的方法与一般方法的不同在于,在简写方法中可以使用 super,而非简写方法不行。简写方法的 name 属性就是括号前的名称,例如上面 person.sayHello 的 name 属性是 sayHello

计算属性名

在 ES5 中是无法使用一个变量的值作为键名的,例如

let lastName = "last name"
let person = {
"first name": "Xiong",
lastName: "Tao"
}

console.log(person["first name"]); // Xiong
console.log(person["last name"]); // undefiend
console.log(person["lastName"]); // Tao

我们希望的是 person 对象有两个属性,分别是

  • first name
  • last name

但是实际上的两个属性为

  • first name
  • lastName

在 ES6 中可以通过方括号 [] 将变量括起来,使得变量的值成为对象的键

let lastName = "last name"
let person = {
"first name": "Xiong",
[lastName]: "Tao"
}

console.log(person["first name"]); // Xiong
console.log(person["last name"]); // Tao
console.log(person["lastName"]); // undefiend

新方法

Object.is

在比较两个值时,我们会使用 == 或者更加严格的 === ,更多的人乐意使用 ===,但是即使是 === 也会不准确,例如

  • +0 === -0 :true
  • NaN === Nan :false

Object.is 方法正是用来修补这一小的瑕疵的,它的作用与 ===== 一样也是用来比较两个值是否相同的

const print = console.log;

print(5 == "5"); // true
print(5 === "5"); // false
print(Object.is(5, "5")); // false

print(+0 == -0); // true
print(+0 === -0); // true
print(Object.is(+0, -0)); // false

print(NaN === NaN); // false
print(Object.is(NaN, NaN)); // true

Object.is 与 === 的不同仅在于 +0,-0 以及 NaN 的比较。

Object.assign

在实际的工作中,需要将其他对象的属性复制到另一个对象中,这个操作常称之为mixin

function mixin(receiver, supplier) { 
Object.keys(supplier).forEach(function(key) {
receiver[key] = supplier[key];
});
return receiver;
}

在其他的库有相似的方法实现相同的功能,如 mix,extend 等等。ES6 引入了 Object.assign 方法,实现的功能与上述的相同,Object.assign 不能复制原型链上的属性以及不可枚举的属性。Object.assign 的语法如下

Object.assign(target, ...sources)

接收一个 target 对象,以及任意多个 source,将多个 source 中的属性复制到 target 当中,如果 target 中的属性与 sources 中某对象的属性相同,那么 target 中的属性的值将会被覆盖,如果多个 source 有相同的属性名,那么后面的 source 属性值会覆盖签名的属性值。

let target = {}
let source1 = {
name: 'Alice',
age: 2
}
let source2 = {
name: 'Bob'
}

Object.assign(target, source1, source2)
print(target) // { name: 'Bob', age: 2 }

Object.assign 进行的是浅拷贝,如果需要进行深拷贝,需要另求他法。在使用 Object.assign 进行复制的过程中,会触发 getter 和 setter 方法。

Object.assign 的更多细节,参考 MDN Object.assign()

重复字面属性

在 ES6 之前,如果在定义对象时,有重复的属性名,那么会报错

let person = {
// 在 ES5 中会报错
name: 'Alice',
name: 'Bob'
}

但是在 ES6 中不会,后面定义的属性值会覆盖之前定义的属性值

let person = {
name: 'Alice',
name: 'Bob'
}
print(person) // { name: 'Bob' }

自有属性遍历顺序

在 ES5 中没有规定当遍历一个对象时应该以何种顺序来访问对象的属性,但是在 ES6 中规定了访问的顺序,这个影响了 Object.getOwnPropertyNames()Reflect.ownKeys(),同样也影响了调用 Object.assign 进行复制时赋值的顺序。键的顺序如下:

  1. 按照升序排序的数字
  2. 按照添加顺序排序的字符串
  3. 按照添加顺序排序的 Symbol
let obj = {
a: 1,
0: 1,
2: 1,
c: 3,
1: 2,
b: 4
};
obj.d = 1;

console.log(Object.getOwnPropertyNames(obj).join(" ")); // 0 1 2 a c b d

for-in 循环没有具体的遍历顺序,因为不同的 JavaScript 引擎有不同的实现。Object.keys()JSON.stringfy() 规定使用和 for-in 循环一样的遍历顺序。

Prototypes 的增强

改变对象的原型

在创建对象时,我们可以通过 Object.create 为对象指定一个原型

let person = {
sayHello() {
console.log("Hello");
}
}

let friend = Object.create(person);
friend.sayHello(); // Hello

可以通过 Object.getPrototypeOf() 获得对象的原型

console.log(Object.getPrototypeOf(friend) === person); // true

在 ES6 中新增了 Object.setPrototypeOf() 方法,可以改变一个对象的原型

let dog = {
sayHello() {
console.log("Woff");
}
}

Object.setPrototypeOf(friend, dog);
friend.sayHello(); // Woff
console.log(Object.getPrototypeOf(friend) === dog); // true

super

如果对象重写了原型的方法,如何在对象中调用原型的同名方法,一般通过如下方式调用

let person = {
sayHello() {
return "Hello";
}
}
let dog = {
sayHello() {
return "Woff";
}
}

let friend = {
sayHello() {
return Object.getPrototypeOf(this).sayHello.call(this) + ", Man!";
}
}

Object.setPrototypeOf(friend, person);
console.log(friend.sayHello()); // Hello, Man!

Object.setPrototypeOf(friend, dog);
console.log(friend.sayHello()); // Woff, Man!

我们通过 Object.getPrototypeOf(this).sayHello.call(this) 来调用父类的某个方法,在 ES6 中引入了 super,super 指定原型对象,所以 friend 对象中的代码可以简化如下

let friend = {
sayHello() {
return super.sayHello.call(this) + ", Man!";
}
}

super 只能在简写方法中使用,如下会发生语法错误

let friend = {
sayHello: function() {
// SyntaxError: 'super' keyword unexpected here
return super.sayHello.call(this) + ", Man!";
}
}