ES6中对象新增了哪些扩展
问题解析
ES6 对对象进行了大量增强,使得对象字面量的写法更加简洁,同时新增了许多实用的静态方法。这个问题考察对对象新特性的全面掌握,包括简洁表示法、属性名表达式、扩展运算符、新的静态方法等。
核心概念
1. 简洁表示法
- 属性简写:键值同名时可省略值
- 方法简写:
method() {}替代method: function() {}
2. 属性名表达式
- 可用
[expr]作为属性名
3. super 关键字
- 指向当前对象的原型对象
4. 扩展运算符
- 解构赋值中的扩展运算符
- 对象浅拷贝
5. 属性遍历
- 多种遍历方法的区别
6. 新的静态方法
Object.is():严格相等比较Object.assign():对象合并/浅拷贝Object.getOwnPropertyDescriptors():获取属性描述符Object.setPrototypeOf()/getPrototypeOf():原型操作Object.keys()/values()/entries():遍历方法Object.fromEntries():键值对数组转对象
详细解答
1. 属性简写
当属性名和变量名相同时,可以简写:
const name = 'Alice';
const age = 25;
// ES5
const person1 = {
name: name,
age: age
};
// ES6
const person2 = {
name,
age
};
// 嵌套对象简写
const firstName = 'Alice';
const lastName = 'Smith';
const user = {
firstName,
lastName,
fullName: `${firstName} ${lastName}`
};
2. 方法简写
// ES5
const person1 = {
name: 'Alice',
sayHello: function() {
return 'Hello, ' + this.name;
}
};
// ES6
const person2 = {
name: 'Alice',
sayHello() {
return `Hello, ${this.name}`;
},
// getter/setter 简写
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(' ');
}
};
// 注意:简写方法可以作为构造函数使用
const obj = {
Foo() {}
};
new obj.Foo(); // OK
// 但箭头函数不行
const obj2 = {
Foo: () => {}
};
new obj2.Foo(); // TypeError
3. 属性名表达式
// ES5 只能通过方括号表示法动态设置属性名
const obj1 = {};
obj1['a' + 'bc'] = 123;
// ES6 可以在对象字面量中使用表达式作为属性名
const propKey = 'foo';
const obj2 = {
[propKey]: true,
['a' + 'bc']: 123,
['h' + 'i']() {
return 'hi';
}
};
// 表达式还可以是函数调用
const getKey = () => 'dynamicKey';
const obj3 = {
[getKey()]: 'value'
};
// Symbol 作为属性名
const sym = Symbol('description');
const obj4 = {
[sym]: 'symbol value'
};
// 注意:属性名表达式与简洁表示法不能同时使用
const foo = 'bar';
const baz = { [foo] }; // 正确:{ bar: 'bar' }
// const baz = { [foo]: foo }; // 冗余但合法
4. super 关键字
super 指向当前对象的原型对象,只能在简写方法中使用:
const proto = {
foo: 'hello',
bar() {
return 'world';
}
};
const obj = {
foo: 'hi',
find() {
// super.foo 指向 proto.foo
return super.foo;
},
callBar() {
// super.bar() 调用 proto.bar()
return super.bar();
}
};
Object.setPrototypeOf(obj, proto);
obj.find(); // 'hello'
obj.callBar(); // 'world'
// 注意:super 只能在简写方法中使用
const obj2 = {
find: () => super.foo // SyntaxError
};
5. 扩展运算符
解构赋值中的扩展运算符
const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
// 嵌套解构
const { a, ...rest } = { a: 1, b: 2, c: 3 };
// 结合默认值
const { x = 0, y, ...z } = { y: 2, a: 3 };
console.log(x); // 0
对象浅拷贝
const original = { a: 1, b: 2 };
const copy = { ...original };
console.log(copy); // { a: 1, b: 2 }
console.log(copy === original); // false
// 合并多个对象
const merged = { ...obj1, ...obj2, ...obj3 };
// 覆盖属性
const updated = { ...original, a: 10 };
console.log(updated); // { a: 10, b: 2 }
// 后面的属性覆盖前面的
const obj = { ...{ x: 1, y: 2 }, ...{ y: 3, z: 4 } };
console.log(obj); // { x: 1, y: 3, z: 4 }
// 注意:浅拷贝,嵌套对象仍是引用
const nested = { a: { b: 1 } };
const copy2 = { ...nested };
copy2.a.b = 2;
console.log(nested.a.b); // 2
6. 属性遍历
ES6 提供了 5 种方法遍历对象属性:
const obj = {
a: 1,
[Symbol('sym')]: 2
};
Object.defineProperty(obj, 'b', {
value: 3,
enumerable: false
});
Object.setPrototypeOf(obj, { c: 4 });
// 1. for...in:遍历自身和继承的可枚举属性(不含 Symbol)
for (const key in obj) {
console.log(key); // 'a', 'c'(如果 c 可枚举)
}
// 2. Object.keys():返回自身可枚举属性的键名数组(不含 Symbol)
Object.keys(obj); // ['a']
// 3. Object.getOwnPropertyNames():返回自身所有属性的键名数组(不含 Symbol,含不可枚举)
Object.getOwnPropertyNames(obj); // ['a', 'b']
// 4. Object.getOwnPropertySymbols():返回自身所有 Symbol 属性
Object.getOwnPropertySymbols(obj); // [Symbol(sym)]
// 5. Reflect.ownKeys():返回自身所有属性(含 Symbol 和不可枚举)
Reflect.ownKeys(obj); // ['a', 'b', Symbol(sym)]
遍历顺序:
- 数值键,按数值升序
- 字符串键,按添加时间升序
- Symbol 键,按添加时间升序
const obj = {
b: 1,
10: 1,
2: 1,
a: 1,
[Symbol('c')]: 1,
[Symbol('a')]: 1
};
Reflect.ownKeys(obj);
// ['2', '10', 'b', 'a', Symbol(c), Symbol(a)]
7. Object.is()
严格相等比较,修复了 === 的两个问题:
// === 的问题
+0 === -0; // true
NaN === NaN; // false
// Object.is()
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
// 其他情况与 === 相同
Object.is('foo', 'foo'); // true
Object.is({}, {}); // false
Object.is([], []); // false
8. Object.assign()
用于对象合并,将源对象的可枚举属性复制到目标对象:
// 基本用法
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 2, c: 3 }
// 返回目标对象
const merged = Object.assign({}, source1, source2);
// 同名属性后面的覆盖前面的
Object.assign({}, { a: 1, b: 1 }, { b: 2, c: 2 });
// { a: 1, b: 2, c: 2 }
// 处理数组(按索引合并)
Object.assign([1, 2, 3], [4, 5]);
// [4, 5, 3]
// 只拷贝可枚举属性
const obj = Object.defineProperty({}, 'foo', {
value: 1,
enumerable: false
});
Object.assign({}, obj); // {}
// 浅拷贝
const obj1 = { a: { b: 1 } };
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
console.log(obj2.a.b); // 2
常见用途:
// 1. 对象浅拷贝
const copy = Object.assign({}, original);
// 2. 合并默认配置和用户配置
const config = Object.assign({}, defaults, userOptions);
// 3. 为对象添加属性
const user = { name: 'Alice' };
Object.assign(user, { age: 25, email: 'alice@example.com' });
// 4. 为类添加方法
Object.assign(MyClass.prototype, {
method1() {},
method2() {}
});
9. Object.getOwnPropertyDescriptors()
返回对象所有自身属性的描述符:
const obj = {
foo: 123,
get bar() { return 'bar'; }
};
Object.getOwnPropertyDescriptors(obj);
// {
// foo: {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// },
// bar: {
// get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true
// }
// }
// 配合 Object.defineProperties 使用
const copy = Object.defineProperties({},
Object.getOwnPropertyDescriptors(original)
);
// 可以完整复制 getter/setter
10. Object.setPrototypeOf() / getPrototypeOf()
const proto = { x: 10 };
const obj = {};
// 设置原型
Object.setPrototypeOf(obj, proto);
console.log(obj.x); // 10
// 获取原型
Object.getPrototypeOf(obj) === proto; // true
// 对比 __proto__
obj.__proto__ === proto; // true(不推荐直接使用)
// 创建对象时指定原型(推荐)
const obj2 = Object.create(proto);
11. Object.keys() / values() / entries()
const obj = { a: 1, b: 2, c: 3 };
// Object.keys() - ES5
Object.keys(obj); // ['a', 'b', 'c']
// Object.values() - ES2017
Object.values(obj); // [1, 2, 3]
// Object.entries() - ES2017
Object.entries(obj); // [['a', 1], ['b', 2], ['c', 3]]
// 应用:对象转 Map
const map = new Map(Object.entries(obj));
// 应用:遍历对象
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
// 应用:过滤对象
const filtered = Object.fromEntries(
Object.entries(obj).filter(([k, v]) => v > 1)
);
12. Object.fromEntries()
键值对列表转为对象(ES2019):
// 基本用法
const entries = [['a', 1], ['b', 2]];
Object.fromEntries(entries); // { a: 1, b: 2 }
// Map 转对象
const map = new Map([['a', 1], ['b', 2]]);
Object.fromEntries(map); // { a: 1, b: 2 }
// 与 Object.entries() 互为逆操作
const obj = { a: 1, b: 2 };
Object.fromEntries(Object.entries(obj)); // { a: 1, b: 2 }
// 应用:URL 参数解析
const params = new URLSearchParams('foo=bar&baz=qux');
Object.fromEntries(params); // { foo: 'bar', baz: 'qux' }
// 应用:转换对象值
const doubled = Object.fromEntries(
Object.entries({ a: 1, b: 2 }).map(([k, v]) => [k, v * 2])
);
// { a: 2, b: 4 }
深入理解
1. 对象扩展运算符的实现
// 扩展运算符内部使用 Object.assign()
const copy = { ...original };
// 等价于
const copy2 = Object.assign({}, original);
// 但扩展运算符不会触发 setter
const obj = {
set foo(value) {
console.log('setter called');
}
};
const copy3 = { ...obj, foo: 1 }; // 不会触发 setter
2. Object.assign 的局限性
// 1. 不能正确复制 getter/setter
const source = {
get foo() { return 1; }
};
const target = {};
Object.assign(target, source);
// target.foo 是 1,而不是 getter
// 正确做法:使用 Object.getOwnPropertyDescriptors
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
// 2. 不拷贝原型链上的属性
const proto = { inherited: 'value' };
const obj = Object.create(proto);
obj.own = 'property';
Object.assign({}, obj); // { own: 'property' }
// 3. 浅拷贝问题
const source2 = { nested: { value: 1 } };
const copy = Object.assign({}, source2);
copy.nested.value = 2;
source2.nested.value; // 2
3. super 的实现原理
const proto = { foo: 'proto' };
const obj = {
foo: 'self',
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
// super.foo 实际上是通过 Object.getPrototypeOf(this).foo 实现的
// 但 super 是静态绑定的,在定义时确定
4. 属性描述符的完整复制
function deepClone(obj) {
// 获取所有属性描述符
const descriptors = Object.getOwnPropertyDescriptors(obj);
// 创建新对象并复制描述符
const clone = Object.create(
Object.getPrototypeOf(obj),
descriptors
);
// 递归复制嵌套对象
for (const key of Reflect.ownKeys(descriptors)) {
const value = obj[key];
if (typeof value === 'object' && value !== null) {
clone[key] = deepClone(value);
}
}
return clone;
}
5. 遍历方法的性能比较
const obj = { /* 大量属性 */ };
// for...in 最慢(需要遍历原型链)
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
// 处理
}
}
// Object.keys 较快
Object.keys(obj).forEach(key => {
// 处理
});
// Object.entries 方便但稍慢
Object.entries(obj).forEach(([key, value]) => {
// 处理
});
最佳实践
1. 对象创建与合并
// 推荐:使用扩展运算符进行浅拷贝和合并
const copy = { ...original };
const merged = { ...defaults, ...options };
// 需要深拷贝时使用库
import cloneDeep from 'lodash/cloneDeep';
const deepCopy = cloneDeep(original);
// 合并配置时指定默认值
const config = {
timeout: 5000,
retries: 3,
...userConfig
};
2. 属性定义
// 使用简洁表示法
const name = 'Alice';
const user = { name }; // { name: 'Alice' }
// 使用方法简写
const utils = {
log(message) {
console.log(message);
},
get timestamp() {
return Date.now();
}
};
// 使用属性名表达式
const prefix = 'user';
const data = {
[`${prefix}_id`]: 123,
[`${prefix}_name`]: 'Alice'
};
3. 原型操作
// 推荐:创建时指定原型
const obj = Object.create(proto);
// 不推荐:运行时修改原型(性能影响)
Object.setPrototypeOf(obj, newProto);
// 使用 super 访问原型方法
const child = {
method() {
// 调用父类方法
const result = super.method();
return result + ' extended';
}
};
4. 属性遍历选择
// 只需要可枚举的自身属性
Object.keys(obj).forEach(key => {});
// 需要值
Object.values(obj).forEach(value => {});
// 需要键值对
Object.entries(obj).forEach(([key, value]) => {});
// 需要包括 Symbol 和不可枚举属性
Reflect.ownKeys(obj).forEach(key => {});
// 需要继承的属性
for (const key in obj) {
// 注意使用 hasOwnProperty 过滤
}
5. 相等性比较
// 一般情况使用 ===
if (a === b) {}
// 特殊值比较使用 Object.is
if (Object.is(a, NaN)) {} // 检查 NaN
if (Object.is(a, -0)) {} // 检查 -0
// 对象深度相等使用库
import isEqual from 'lodash/isEqual';
if (isEqual(obj1, obj2)) {}
面试要点
-
简洁表示法的限制:
- 属性简写要求变量名与属性名相同
- 方法简写不能使用箭头函数(this 绑定问题)
-
super 的使用限制:
- 只能在简写方法中使用
- 指向当前对象的原型对象
- 静态绑定,定义时确定
-
Object.assign 的注意事项:
- 浅拷贝,嵌套对象共享引用
- 不复制 getter/setter,只复制值
- 不拷贝不可枚举属性和原型链属性
- 后面的源对象属性覆盖前面的
-
遍历方法的区别:
for...in:遍历自身和继承的可枚举属性Object.keys:仅自身可枚举属性(字符串键)Object.getOwnPropertyNames:自身所有属性(字符串键)Object.getOwnPropertySymbols:自身所有 Symbol 属性Reflect.ownKeys:自身所有属性
-
Object.is 与 === 的区别:
Object.is(+0, -0)为falseObject.is(NaN, NaN)为true- 其他情况与
===相同
-
扩展运算符与 Object.assign:
- 扩展运算符更简洁,不会触发 setter
Object.assign会触发 setter- 两者都是浅拷贝
-
Object.fromEntries 的应用:
- 与
Object.entries互为逆操作 - 常用于转换对象值、过滤对象
- 可以将 Map、URLSearchParams 转为对象
- 与