对象的扩展
属性简洁表示法
属性名表达式
方法的 name 属性
属性的可枚举性和遍历
super 关键字
对象的扩展运算符
1,属性的简洁表示法
Es6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo:foo};
除了属性简写,方法也可以简写。
const o = {
method() {
return 'Hello!';
}
}
属性的复制器(setter)和取值器(getter),事实上也是采用这种写法。
const cart = {
_wheels: 4,
get wheels() {
return this._wheels;
},
set wheels (value) {
if(value < this._wheels) {
throw new Error('数值太小了!');
}
this._wheels = value;
}
}
如果某个方法的值是一个 Generator 函数,前面需要加上 星号
2,属性名表达式
Javascript 定义对象的属性,有两种方法。
// 方法一
obj.foo = true; // 标识符
// 方法二
obj['a' + 'bc'] = 123 // 表达式
Es6 允许字面量定义对象时,用方法二,作为对象的属性名,即把表达式放在括号内。
let key = 'foo';
let obj = {
[key]: true,
['a' + 'bc']: 123
}
表达式还可以用来定义方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
}
obj.hello() // hi
注意:属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串 [object Object],这一点需要注意。
const keyA = {a:1};
const keyB = {b:2};
const myObject = {
[keyA]: 'vA',
[keyB]: 'vB'
}
myObject // Object {[object Object]: 'vB'}
上面代码中,[keyA] 和 [keyB] 得到的都是 [object Object],所以 [keyB] 会把 [keyA] 覆盖掉,而 myObject 最后只有一个 [object Object] 属性。
3,方法的 name 属性
函数的 name 属性,返回函数名。对象方法也是函数,因此也有 name 属性。
const person = {
sayName() {
console.log('hello!');
}
}
person.sayName.name // sayName
属性的可枚举性和遍历
可枚举性:
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
Object.getOwnPropertyDescriptor() 方法可以获取该属性的描述对象。
let obj = {foo: 123};
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writeable: true,
// enumerable: true,
// configurable: true
// }
描述对象的 enumerable 属性,称为 “可枚举性”,如果该属性为 false,就表示某些操作会忽略当前属性。
目前,有四个操作会忽略 enumerable 为 false 的属性。
for...in 循环: 只遍历对象和自身的和继承的可枚举的属性。
Ojbect.keys():返回对象自身的所有可枚举的属性的键名。
JSON.stringify():只串行化对象自身的可枚举的属性。
Ojbect.assign():忽略 enumerable 为 false 的属性,只拷贝对象自身的可枚举的属性。
这四个操作之中,前三个是 Es 5 就有的,最后一个 Object.assign() 是 Es6 新增的。其中,只有 for...in 会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。实际上,引入“可枚举” (enumerable)这个概念的最初目的,就是让某些属性可以规避掉 for...in 操作,不然所有内部属性和方法都会被遍历到。比如,对象原型的 toString 方法,以及数组的length 属性,就通过 "可枚举性",从而避免 for...in 遍历到。
Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerablehg
// false
Object.getOwnPropertyDescriptor([],length).enumerable
// false
上面代码中,toString 和 length 属性的 enumberable 都是 false ,因此 for...in 不会遍历到这两个继承自原型的属性。
另外,Es6 规定,所有 Class 的原型的方法都是不可枚举的。
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable;
// false
总的来说,操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性,所以,尽量不要用 for...in 循环,而用 Object.keys() 代替。
属性的遍历
Es6 一公有 5 种方法可以遍历对象的属性。
1,for...in
for...in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
2,Object.keys(obj)
Object.keys 返回一个数组,包括对象自身的 (不含继承的)所有的可枚举属性(不含 Symbol 属性)的键名。
3,Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性) 的键名。
4,Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols 返回一个数组,包含对象自身所有 Symbol 属性的键名。
5,Reflect.ownKeys(obj)
Reflect.ownKeys 返回一个数组,包含对象自身所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上 5 种方法遍历对象的键名,都遵循同样的属性遍历的次序规则。
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
5,super 关键字
我们知道,this 关键字总是指向 函数所在的当前对象,Es 6 又新增了另一个类似的关键字 super ,指向当前对象的原型对象。
const proto = {
foo: 'hello'
}
const obj = {
foo: 'world',
find() {
return super.foo;
}
}
Ojbect.setPrototypeOf(obj, proto);
obj.find(); // "hello"
注意:super 关键字表示原型对象时,只能用在对象的方法中,用在其他地方都会报错。必须写在对象方法的简写法里面。
JavaScript 引擎内部,super.foo 等同于 Object.getPrototypeOf(this).foo (属性) 或 Object.getPrototypeOf(this).foo.call(this) 方法。
6,对象的扩展运算符
Es 2018 将 ... 运算符引入对象。对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = {a: 3,b: 4};
let n = {...z};
n // {a:3,b:4}
由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let foo = {...['a','b','c']}
foo // {0:'a', 1:'b', 2:'c'}
如果扩展运算符后面是一个空对象,则没有任何效果。如果是数值也是如此。
但是,如果扩展运算符后面是一个字符串,他会自动转成一个类似数组的对象,因此返回的不是空对象。
{..."hello"}
// {0:'h',1:'e',2:'l',3:'l',4:'o'}
扩展运算符还可以用于合并两个对象。
let ab = {...a,...b};
// 等同于
let ab = Object.assign({}, a, b);
2,解构赋值
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable),但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
let {x,y,...z} = {x:1,y:2,a:3,b:4};
// x 1 | y 2 | z {a:3,b:4}
注意:结构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组,对象,函数)那么结构赋值拷贝的是这个值的引用,而不是这个值的副本。
let obj = {a:1};
let {...x} = obj;
obj.a = 2
x.a // 2
另外,扩展运算符的解构赋值,不能复制继承自原型对象的属性。