跳到主要内容

函数和对象特性

获取属性名称

访问器属性的名称获取

当我在对象中定义 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.createObject.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 }
}
*/

完全拷贝一个对象

使用 definePropertiesgetOwnPropertyDescriptors 复制对象,包括 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.keysfor...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) ]