函数和对象特性
获取属性名称
访问器属性的名称获取
当我在对象中定义 getter 和 setter 时,可以通过 Object.getOwnPropertyDescriptor
获取它们的名称。
const obj = {
get foo() {},
set foo(x) {},
};
console.log(obj.foo);
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
console.log(descriptor.get.name); // 'get foo'
console.log(descriptor.set.name); // 'set foo'
阻止对象扩展
Object.preventExtensions
方法使对象变得不可扩展,无法添加新的属性。
const obj = { a: 2 };
// 封闭对象,防止扩展
const sealedObj = Object.preventExtensions(obj);
obj.b = 3;
// 判断对象是否可扩展
console.log(Object.isExtensible(obj)); // false
console.log(sealedObj); // { a: 2 }
使用 Object.defineProperty
添加属性会失败,因为对象已经被封闭。
const obj = { a: 2 };
Object.preventExtensions(obj);
try {
Object.defineProperty(obj, 'b', {
value: 6,
});
} catch (error) {
console.error(error); // TypeError: Cannot define property b, object is not extensible
}
两种增加属性方式的对比
通过赋值和 defineProperty
方法添加属性有不同的默认属性描述符。
const obj = { a: 2 };
// 通过赋值方式添加属性,属性描述符默认为可写、可枚举、可配置
obj.b = 3;
// 通过 defineProperty 方法添加属性,属性描述符默认为不可写、不可枚举、不可配置
Object.defineProperty(obj, 'c', {
value: 6,
});
console.log(Object.getOwnPropertyDescriptor(obj, 'b'));
// { value: 3, writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptor(obj, 'c'));
// { value: 6, writable: false, enumerable: false, configurable: false }
Object.seal 和 Object.isSealed
封闭对象阻止添加新属性,并将现有属性标记为不可配置,但允许修改可写属性的值。
const obj = { a: 1 };
// 密封对象
Object.seal(obj);
// 判断对象是否被密封
console.log(Object.isSealed(obj)); // true
// 尝试添加新属性失败
obj.b = 2;
console.log(obj.b); // undefined
// 修改可写属性成功
obj.a = 3;
console.log(obj.a); // 3
// 尝试删除属性失败
delete obj.a;
console.log(obj.a); // 3
Object.freeze()
Object.freeze
冻结对象,使其无法被修改,无法添加或删除属性,属性的可枚举性、可配置性和可写性也被锁定。冻结对象后,其原型也无法修改。
const obj = { a: 1 };
Object.freeze(obj);
console.log(Object.isFrozen(obj)); // true
// 尝试修改属性失败
obj.a = 2;
console.log(obj.a); // 1
// 尝试添加新属性失败
obj.b = 3;
console.log(obj.b); // undefined
// 尝试删除属性失败
delete obj.a;
console.log(obj.a); // 1
深度冻结
对于嵌套的对象,需递归冻结所有子对象。
function deepFreeze(obj) {
Object.freeze(obj);
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
deepFreeze(obj[key]);
}
}
}
const obj = {
a: 1,
b: {
c: 2,
},
};
deepFreeze(obj);
// 尝试修改嵌套属性失败
obj.b.c = 3;
console.log(obj.b.c); // 2
Object.is
Object.is
方法判断两个值是否为同一值。与严格相等运算符类似,但对 NaN
和 -0
的处理不同。
console.log(NaN === NaN); // false
console.log(+0 === -0); // true
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0)); // false
console.log({} === {}); // false
console.log(Object.is({}, {})); // false
Object.create
Object.create
方法创建一个新对象,使用现有对象作为新对象的原型。
返回值
新对象具有指定的原型和属性。
const obj = {
a: 1,
b: 2,
c: 3,
d() {
console.log(this);
},
};
const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
console.log(clone);
// 输出: { a: 1, b: 2, c: 3, d: [Function: d] }
深度拷贝
通过递归使用 Object.create
和 Object.getOwnPropertyDescriptors
实现深度拷贝。
const obj = {
a: 1,
b: 2,
c: 3,
d: {
e: {
f: {},
},
},
};
function deepClone(source) {
return Object.create(Object.getPrototypeOf(source), Object.getOwnPropertyDescriptors(source));
}
const clone = deepClone(obj);
console.log(clone);
// 输出: { a: 1, b: 2, c: 3, d: { e: { f: {} } } }
Object.assign
Object.assign
方法执行浅拷贝,将源对象的可枚举属性复制到目标对象。
const source = { a: 1 };
const target = { b: 2 };
// 目标对象,源对象
const copy = Object.assign(target, source);
// 方法返回拷贝对象
console.log(copy); // { b: 2, a: 1 }
// 拷贝对象和目标对象是同一个引用
console.log(copy === target); // true
// source 不变,target 改变
console.log(source, target); // { a: 1 } { b: 2, a: 1 }
合并多个对象
const target1 = { a: 1 };
const target2 = { b: 2 };
const target3 = { c: 3 };
Object.assign(target1, target2, target3);
console.log(target1); // { a: 1, b: 2, c: 3 }
属性覆盖
后面的属性会覆盖前面的属性。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 2, c: 3 }
原始值调用包装类
当目标对象是原始值时,会被包装为对象。
const result = Object.assign(1, { a: 1 });
console.log(result); // Number {1, a: 1}
第二个参数不是对象
如果源对象不是对象,则会被忽略。
const result = Object.assign({ a: 1 }, undefined);
console.log(result); // { a: 1 }
sources
参数会被包装,如果包装后是可迭代对象才会被复制。
const source1 = 'abc';
const source2 = true;
const source3 = 10;
const obj = Object.assign({}, source1, source2, source3);
console.log(obj); // { '0': 'a', '1': 'b', '2': 'c' }
console.log(new Object('abc')); // String {"abc"}
console.log(new Boolean(true)); // Boolean {true}
console.log(new Number(10)); // Number {10}
拷贝原型和不可枚举属性
Object.assign
不会拷贝原型上的属性和不可枚举属性。
const proto = { foo: 1 };
const obj = Object.create(proto, {
bar: {
value: 2,
enumerable: false,
},
baz: {
value: 3,
enumerable: true,
},
});
console.log(obj); // { baz: 3 },不可枚举的 bar 不会显示
const copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }
同名属性替换
可以替换同名属性,但不会进行深拷贝。
const target = {
a: { b: 'c', d: 'e' },
};
const source = {
a: { b: 'hello' },
};
const copy = Object.assign(target, source);
console.log(copy); // { a: { b: 'hello' } }
数组替换的问题
数组的索引作为属性名,赋值会覆盖相应位置的值。
const array = Object.assign([1, 2, 3], [4, 5]);
console.log(array); // [4, 5, 3]
拷贝取值属性
取值属性无法被拷贝,Object.assign
会调用 getter 并拷贝返回值。
const source = {
get foo() {
return 1;
},
};
const target = {};
const copy = Object.assign(target, source);
console.log(copy); // { foo: 1 }
给原型扩充属性和方法
可以通过 Object.assign
扩充原型的属性和方法。
function Person() {}
let age = 1;
Object.assign(Person.prototype, {
eat() {
console.log('Eating...');
},
age,
});
const person = new Person();
person.eat(); // 'Eating...'
console.log(person.age); // 1
配置默认值
可以使用 Object.assign
为函数参数设置默认值。
const defaults = {
url: {
host: 'www.baidu.com',
port: 8080,
},
};
function configure(options) {
// 设置默认值
options = Object.assign({}, defaults, options);
console.log(options);
}
configure({
url: {
port: 3000,
},
});
// 输出: { url: { port: 3000 } }
Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors
获取对象所有属性的描述符,可用于定义多个属性。
const obj = {};
Object.defineProperties(obj, {
a: {
value: true,
writable: true,
},
b: {
value: 'hello',
writable: false,
},
});
console.log(obj);
// 输出: { a: true, b: 'hello' }
console.log(Object.getOwnPropertyDescriptors(obj));
/*
{
a: { value: true, writable: true, enumerable: true, configurable: true },
b: { value: 'hello', writable: false, enumerable: true, configurable: true }
}
*/
完全拷贝一个对象
使用 defineProperties
和 getOwnPropertyDescriptors
复制对象,包括 getter 和 setter。
const source = {
set foo(value) {
console.log(`Setting foo to ${value}`);
},
};
const target = {};
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
console.log(Object.getOwnPropertyDescriptors(target));
/*
{
foo: {
set: [Function: set foo],
get: undefined,
enumerable: true,
configurable: true
}
}
*/
部署对象
展示多种创建对象的方法。
// 方式1: 直接定义对象
const obj1 = { a: 1 };
console.log(obj1); // { a: 1 }
// 方式2: 使用 Object.create 继承原型
const proto = { a: '原型绑定' };
const obj2 = Object.create(proto);
obj2.foo = 123;
console.log(obj2); // { foo: 123 }
// 方式3: 指定原型并部署属性
const obj3 = Object.assign(Object.create(proto), {
foo1: '123',
});
console.log(obj3); // { foo1: '123' }
// 方式4: 使用 Object.create 指定原型和属性描述符
const obj4 = Object.create(
proto,
Object.getOwnPropertyDescriptors({
foo: '345',
})
);
console.log(obj4); // { foo: '345' }
Symbol
Symbol
是一种不重复的原始类型,每次调用 Symbol()
都会生成一个唯一的值。
const symA = Symbol('a');
const symB = Symbol('a');
console.log(symA === symB); // false
符号属性可以被迭代和复制,但需要注意它们不会被 Object.keys
或 for...in
遍历到。
const obj = Object.assign(
{
a: 'b',
},
{
[Symbol('c')]: 'd',
}
);
console.log(obj); // { a: 'b', [Symbol(c)]: 'd' }
// 通过 Reflect.ownKeys 可以获取所有属性,包括符号属性
console.log(Reflect.ownKeys(obj)); // [ 'a', Symbol(c) ]