ES6 对象的扩展

注:这是一篇学习笔记,记录自己在ES6学习过程中按照自己的思路觉得应该记录的一些要点,方便以后查看和复习。参考:

阮一峰ES6的书籍中函数的扩展
我的ES6入门学习规划

平常所说的对象属性,似乎都是指对象的那些跟值对应的属性,实际上属性除了有值属性,还有方法属性,这也是为啥在遍历对象的时候,能遍历出来的不单是那些值,还有对象的方法。 在掌握了属性描述符(PropertyDescriptor)之后,对属性的理解就会加深不少。

在对象扩展这个部分要特别注意以下几个点的适用情况:

  1. 是否包含继承属性
  2. 是否包含不可枚举属性
  3. setters, getters如何处理
  4. symbol值如何处理

属性的简洁表示法。

ES6在给对象定义值或方法的时,有更简洁的写法:

1
2
3
4
5
6
7
8
9
let foo = 'bar';
let baz = {foo};
baz.foo;//bar

const o = {
method() {
//...
}
}

setters, getters:

1
2
3
4
5
6
7
8
9
const card = {
_wheels: 4,
get wheels() {
return this._wheels;
}
set wheels(value) {
this._wheels = value;
}
}

generator函数,加星号:

1
2
3
4
5
const obj = {
* method() {
// ...
}
}

属性名表达式

在定义属性的时候,可以通过中括号使用表达式来表示属性名称:

1
2
3
4
5
6
7
8
9
10
11
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123,
[propKey + '_yes']() {

},
* [propKey + '_no']() {
yield 1;
}
}

  • 属性名表达式不能与属性名简写同时使用
  • 属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]

属性的可枚举性

对象的属性描述符上面有一个enumerable值,如果为true,则表示这个属性是可枚举的。有四个操作会忽略不可枚举的属性:

  • for…in 只遍历对象自身和继承的可枚举属性
  • Object.keys() 返回对象自身的所有可枚举属性的键名
  • JSON.stringify() 只串行化对象自身的可枚举属性
  • Object.assign() 只考虑对象自身的可枚举属性

这四个方法按照笔记一开始提到的注意点,重新整理如下:

api 继承属性 不可枚举属性 setter getter symbol
for…in 包含 不包含 包含 包含 不包含
Object.keys() 不包含 不包含 包含 包含 不包含
JSON.stringify() 不包含 不包含 不包含 包含 不包含
Object.assign() 不包含 不包含 包含 包含 包含

测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let parent = {name: 'parent'};
let _birth;
let id = Symbol();
let child = {
set birth(birth) {
_birth = birth;
},
get birthday(){
return _birth;
},
[id]: 'lyzg',
__proto__: parent
};
child.birth = '0101';

console.log('for...in');
for(var i in child) {
console.log(i, child[i])
}

console.log('Object.keys()');
console.log(Object.keys(child));

console.log('JSON.stringify()');
console.log(JSON.stringify(child));

console.log('Object.assign()');
console.log(Object.assign({}, child));

注意:

  • JSON.stringify()在串行化的时候,如果属性是一个getter,那么会把getter调用一次,并把返回值进行串行化
  • Object.assign()在复制的时候,如果属性是一个getter,那么会把getter调用一次,并把返回值复制出来,而不是getter属性本身;Object.assign()在复制的时候,如果属性是一个单独的setter,没有getter与其对应,这个属性会以undefined为值进行复制,setter本身不会被复制

ES6规定所有Class的原型的方法都是不可枚举的。

1
2
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
// false

属性的遍历

目前有5种方法遍历对象的属性

  • for…in: 遍历对象自身和继承的可枚举属性,不含Symbol属性
  • Object.keys(obj): 可用来遍历对象自身的可枚举属性,不含Symbol属性
  • Object.getOwnPropertyNames(obj): 可用来遍历对象自身的可枚举属性和不可枚举属性,不含Symbol属性
  • Object.getOwnPropertySymbols(obj): 可用来遍历对象自身的所有Symbol属性
  • Reflect.ownKeys(obj): 可用来遍历对象自身的所有属性,含Symbol属性, 含不可枚举属性

属性遍历的顺序规则:

  • 首先遍历所有数字键属性,按数值升序;
  • 其次遍历所有字符串键属性,按加入时间升序;
  • 最后遍历所有Symbol键属性,按加入时间升序;

super关键字

指向对象的原型对象。 super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法,用在其它地方会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 报错
const obj = {
foo: super.foo
}

// 报错
const obj = {
foo: () => super.foo
}

// 报错
const obj = {
foo: function () {
return super.foo
}
}

JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(值属性)或Object.getPrototypeOf(this).foo.call(this)(方法属性)。

对象扩展运算符

应用于两个方面。 一个是以前的笔记里记录过的对象解构,另外一个就是用于字面量对象创建的时候,从其它对象拷贝属性。举例:

1
2
3
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

它用于字面量对象定义时,作用是取出参数对象自身的所有可枚举属性,拷贝到待定义的字面量对象当中。如果参数不是对象,先将目标转为对象,再进行处理。null,undefined,boolean,string的作用结果如下:

1
2
3
4
5
6
7
8
9
10
// 等同于 {...Object(1)}
{...1} ;// {}
// 等同于 {...Object(true)}
{...true} ;// {}

// 等同于 {...Object(undefined)}
{...undefined}; // {}

// 等同于 {...Object(null)}
{...null} ;// {}

注意null,undefined值,对象扩展运算符使用的时候不会报错,但是数组的扩展运算符使用的时候会报错。

string,数组,NodeList对象,arguments对象,使用对象扩展运算符的时候,效果相同,都是把索引键与值拷贝出来。

1
2
3
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}

其它:与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。

主要应用

  • 对象的复制
  • 对象的合并

对象的扩展运算符等同于Object.assign()方法。

复制:

1
2
3
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);

合并:

1
2
3
4
5
6
7
8
9
10
11
12
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

//如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
let aWithOverrides = { ...a, x: 1, y: 2 };
// x y 会覆盖a里面的x y
// 用来修改现有对象部分的属性就很方便

//如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值
let aWithDefaults = { x: 1, y: 2, ...a };
// a里面的x y 会覆盖一开始的x y

对象扩展运算符用于字面量对象的行为整理如下:

api 继承属性 不可枚举属性 setter getter symbol
对象的扩展运算符 不包含 不包含 包含 包含 包含

注意点:如果属性是一个getter,那么会把getter调用一次,并把返回值复制出来,而不是getter属性本身;如果属性是一个单独的setter,没有getter与其对应,这个属性会以undefined为值进行复制,setter本身不会被复制。这一点与 Object.assign()是完全一样的。