对象的扩展
属性的简洁表示法
es6 允许直接写入变量和函数,作为对象的属性和方法。
const a = 1;
const b = { a };
log(b); // {a: 1}
// 相当于 const b = { a: a};
方法简写:
const obj = {
hello() {
return 'Hello';
},
};
属性名表达式
JS 定义属性的方法:
// 用标识符作为属性名
obj.a = 'a';
// 用表达式作为属性名
obj['a' + 'b'] = 'ab';
使用字面量方式定义对象时,es5 只能使用标识符作为属性名。
const obj = {
a: 'a',
};
es6 可以使用表达式作为属性名。
const a = 'aaa';
const obj = {
[a]: 'aaa',
};
log(obj); // {aaa: "aaa"}
属性名表达式和简洁表示法不能同时使用。
const a = 'aaa';
const obj = { [a] }; // Uncaught SyntaxError: Unexpected token }
如果属性名是一个对象,默认情况下会转为字符串 "[object Object]"。
const objA = {};
const objB = {};
const objC = {
[objA]: 'aaa',
[objB]: 'bbb',
};
log(objC); // {[object Object]: "bbb"}
方法的 name 属性
略...
属性的可枚举性和遍历
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
Object.getOwnPropertyDescriptor
可以获取属性的描述对象。
const obj = {
a: 1,
};
log(Object.getOwnPropertyDescriptor(obj, 'a'));
// {
// value: 1,
// writable: true,
// enumerable: true, <-- 可枚举性
// configurable: true
//}
四个操作会忽略 enumerable: false 的属性。
- for...in -- 只遍历自身和继承的 可枚举的 属性
- Object.keys() -- 返回自身的所有可枚举属性的键名
- JSON.stringify() -- 只序列化对象自身的可枚举属性
- Object.assign() -- 只拷贝对象自身的可枚举属性
引入可枚举的概念,最初是为了让某些属性规避 for...in ,不然所有的内部属性和方法都会被遍历到。
比如对象原型的 toString()
方法,数组的 length
属性。
Object.getOwnPropertyDescriptor(Object.prototype, 'toString');
// {
// value: ƒ toString(),
// writable: true,
// enumerable: false, <-- 可枚举属性为 false
// configurable: true
//}
Object.getOwnPropertyDescriptor((class {foo() {}}).prototype, 'foo');
// {
// value: ƒ foo(),
// writable: true,
// enumerable: false, <-- 可枚举属性为 false
// configurable: true
//}
属性的遍历
方法 | 说明 | 举例 |
---|---|---|
for...in | 遍历对象自身和继承可枚举属性(不含 Symbol 属性)。 | |
Object.keys() | 返回数组,包含对象自身(不含继承的)所有可枚举属性(不含 Symbol)的键名。 | Object.keys([]); // [] |
Object.getOwnPropertyNames() | 返回数组,包含对象自身的所有属性(不含 Symbol,但是包含不可枚举属性)的键名。 | Object.getOwnPropertyNames([]); // ["length"] |
Object.getOwnPropertySymbols() | 返回数组,包含对象自身的所有 Symbol 属性的键名 | const symbolA = Symbol('A'); const obj = {[symbolA]: 'a'}; Object.getOwnPropertySymbols(obj); // [Symbol(A)] |
Reflect.ownKeys() | 返回数组,包含对象自身的所有键名——包含字符串、Symbol、可枚举不可枚举。 |
function People() {
this.country = 'China';
}
People.prototype.name = 'Tom';
Object.defineProperty(People.prototype, 'language', {
value: 'Chinese',
enumerable: false
});
let people1 = new People();
people1.age = 10;
people1[Symbol()] = 'symbol';
Object.defineProperty(people1, 'gender', {
value: 'male',
enumerable: false
});
for (let key in people1) {
console.log(key);
}
// country
// age
// name
console.log(Object.keys(people1)); // ["country", "age"]
console.log(Object.getOwnPropertyNames(people1)); // ["country", "age", "gender"]
console.log(Object.getOwnPropertySymbols(people1)); // [Symbol()]
console.log(Reflect.ownKeys(people1)); // ["country", "age", "gender", Symbol()]
以上五种都遵循属性遍历的次序规则:
- 先遍历所有数值键,按数值升序排列
- 再遍历字符串键,按加入时间升序
- 遍历 Symbol 键,按加入时间升序
super 关键字
super 关键字指向当前对象的原型对象。
const proto = {
name: 'proto',
};
const obj = {
name: 'obj',
find() {
return super.name;
},
};
Object.setPrototypeOf(obj, proto);
console.log(obj.find()); // 'proto'
super 关键字表示原型对象时,只能用在对象的方法中,用在其他地方都会报错。
只有在对象方法的简写法中可以使用 super。
const obj = {
name: 'obj',
find: () => super.name // Uncaught SyntaxError: 'super' keyword unexpected here
};
const obj = {
name: 'obj',
find: function() {
return super.name // Uncaught SyntaxError: 'super' keyword unexpected here
}
};
// 以上两种都认为把 super.name 赋值给 find,所以报错
对象的扩展运算符
解构赋值
将目标对象自身可遍历的、但未被读取的属性,分配到指定的对象上。
const { a, b, ...c } = { a: 1, b: 2, c: 3, d: 4, e: 5 };
console.log(a); // 1
console.log(b); // 2
console.log(c); // {c: 3, d: 4, e: 5}
解构赋值的右边如果不是对象,是 undefined 或 null 会报错。
const { ...a } = undefined; // Uncaught TypeError: Cannot destructure 'undefined' or 'null'.
注意:解构赋值的拷贝是浅拷贝。
扩展运算符的解构赋值,不能复制继承自原型的属性。
let o1 = {
a: 1,
};
let o2 = {
b: 2,
};
o2.__proto__ = o1;
const { ...a } = o2;
console.log(a); // {b: 2}
const o = Object.create({
a: 1,
b: 2,
});
o.c = 3;
const { a, ...d } = o; // a 通过解构赋值,可以读取对象 o 继承的属性 a
console.log(d); // {c: 3} 通过扩展运算符解构赋值,只能读取对象自身的属性
const { b, c } = d;
console.log(a); // 1
console.log(b); // undefined
console.log(c); // 3
用处:扩展某个函数的参数
function a({ a, b }) {}
function b(x, y, ...z) {
console.log(x, y);
a(z);
}
扩展运算符
对象的扩展运算符用于取出参数对象的所有可遍历属性,拷贝到当前对象上。
const o1 = {
a: 1,
b: 2,
};
Object.defineProperty(o1, 'c', {
value: 3,
enumerable: false,
});
console.log(o1); // {a: 1, b: 2, c: 3}
const o2 = { ...o1 }; // 只拷贝可遍历属性
console.log(o2); // {a: 1, b: 2}
数组是特殊对象,对象的扩展运算符也适用于数组。
const arr = ['a', 'b', 'c'];
const obj = { ...arr };
console.log(obj); // {0: "a", 1: "b", 2: "c"}
扩展运算符后面是一个空对象,没效果。
const obj = { ...{} };
console.log(obj); // {}
扩展运算符后面不是一个对象,会将其转为对象。
const obj = { ...1 }; // 会将 1 转为数值的包装对象 Number(1),该对象没有自身的属性,所以返回一个空对象
console.log(obj); // {}
扩展运算符后面是字符串,会转成类似数组的对象。
const obj = { ...'abcd' };
console.log(obj); // {0: "a", 1: "b", 2: "c", 3: "d"}
对象的扩展运算符等同于使用 Object.assign()
const o1 = {};
const o2 = { ...o1 }; // 只能拷贝对象实例的属性
// 等同于
const o3 = Object.assign({}, o1); // 只能拷贝对象实例的属性
同时拷贝对象实例的属性和原型属性。
// 方法 1
const o1 = {
__proto__: Object.getPrototypeOf(obj), // __proto__ 在非浏览器环境下不一定部署,所以不推荐
...obj,
};
// 方法 2
const o2 = Object.assign(Object.getPrototypeOf(obj), obj);
// 方法 3
const o3 = Object.assign(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
扩展运算符可合并两个对象。
const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { ...o1, ...o2 };
const o4 = Object.assign({}, o1, o2);
console.log(o3); // {a: 1, b: 2}
// 等同于
console.log(o4); // {a: 1, b: 2}
自定义的属性放在扩展运算符后面,会把扩展运算符内部的同名属性覆盖掉。
const o = {
a: 1,
b: 2,
};
const o2 = { ...o, a: 3 };
console.log(o2); // {a: 3, b: 2}
等同于
const o = {
a: 1,
b: 2,
};
const o2 = { ...o, ...{ a: 3 } };
console.log(o2); // {a: 3, b: 2}
等同于
const o = {
a: 1,
b: 2,
};
const a = 3;
const o2 = { ...o, a };
console.log(o2); // {a: 3, b: 2}
等同于
const o = {
a: 1,
b: 2,
};
const a = 3;
const o2 = Object.assign({}, o, { a: 3 });
console.log(o2); // {a: 3, b: 2}
可以方便地修改现有对象的部分属性。
const oldVal = {
a: 1,
b: 2,
c: 3,
};
const newVal = {
...oldVal,
a: 'a',
};
console.log(newVal); // {a: "a", b: 2, c: 3}
和数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。
const a = 2;
const o = {
...(a > 2 ? { a: 2 } : { a: 1 }),
b: 2,
};
console.log(o); // {a: 1, b: 2}
扩展运算符的参数对象中,如果有取值函数 get,会执行。
const obj = {
a: 1,
};
const objWithGetter = {
...obj,
get x() {
console.log('不会执行');
},
};
const runTimeObj = {
...obj,
...{
get x() {
console.log('会执行');
}
},
};
// 会执行