JavaScript对象(Object)研究_03_freeze、fromEntries、getOwnPropertyDescriptor、getOwnPropertyDescriptors
在JavaScript中,Object
构造函数提供了一系列静态方法,用于操作和管理对象的属性和行为。本篇博客将深入探讨Object
的四个重要静态方法:freeze
、fromEntries
、getOwnPropertyDescriptor
、getOwnPropertyDescriptors
。通过详细的介绍、语法说明、代码示例,以及扩展的知识点,我们将全面了解这些方法的用途、工作原理和注意事项。
一、Object.freeze()
1. 基础介绍
Object.freeze()
方法可以冻结一个对象。冻结对象后,不能向其添加新属性,不能删除已有属性,不能修改已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。换句话说,这个对象变得不可变(immutable)。
冻结是浅层次的,也就是说,如果对象的属性是引用类型(如对象、数组),那么这些引用类型的属性仍然可以修改其内部的值。
2. 语法
Object.freeze(obj)
- obj:要被冻结的对象。
3. 示例代码
示例1:冻结简单对象
const obj = {
prop1: 'value1',
prop2: 'value2'
};
Object.freeze(obj);
obj.prop1 = 'newValue'; // 无效操作,属性值不会改变
console.log(obj.prop1); // 输出: 'value1'
obj.prop3 = 'value3'; // 无效操作,无法添加新属性
console.log(obj.prop3); // 输出: undefined
delete obj.prop2; // 无效操作,无法删除属性
console.log(obj.prop2); // 输出: 'value2'
示例2:冻结嵌套对象
const person = {
name: 'Alice',
hobbies: ['reading', 'traveling']
};
Object.freeze(person);
person.name = 'Bob'; // 无效操作
console.log(person.name); // 输出: 'Alice'
person.hobbies.push('coding'); // 有效操作,修改了引用类型内部的值
console.log(person.hobbies); // 输出: ['reading', 'traveling', 'coding']
4. 检测对象是否被冻结
使用Object.isFrozen()
方法可以检测一个对象是否被冻结。
console.log(Object.isFrozen(person)); // 输出: true
5. 深冻结对象
由于Object.freeze()
是浅冻结,如果需要深度冻结对象,需要递归地冻结每个属性。
function deepFreeze(obj) {
// 取出所有属性名
const propNames = Object.getOwnPropertyNames(obj);
// 递归地冻结属性
for (const name of propNames) {
const value = obj[name];
if (typeof value === 'object' && value !== null) {
deepFreeze(value);
}
}
return Object.freeze(obj);
}
deepFreeze(person);
person.hobbies.push('painting'); // 无效操作
console.log(person.hobbies); // 输出: ['reading', 'traveling', 'coding']
6. 注意事项
-
不可逆:一旦对象被冻结,就无法解冻。
-
严格模式下的错误:在严格模式下,试图修改被冻结对象的属性会抛出
TypeError
错误。在非严格模式下,修改操作会被静默忽略。 -
冻结数组:冻结数组后,无法修改其元素或长度。
const arr = [1, 2, 3]; Object.freeze(arr); arr[0] = 10; // 无效操作 arr.push(4); // 无效操作 console.log(arr); // 输出: [1, 2, 3]
7. 扩展知识点
-
不可变对象的应用:在函数式编程中,不可变对象有助于避免副作用,提高代码的可预测性和可维护性。
-
与
const
的区别:const
声明的变量是常量,指向的对象引用不可变,但对象本身是可变的。Object.freeze()
冻结的是对象本身,使其不可变。const obj = { a: 1 }; obj.a = 2; // 有效操作 Object.freeze(obj); obj.a = 3; // 无效操作
二、Object.fromEntries()
1. 基础介绍
Object.fromEntries()
方法将一个键值对数组(或具有可迭代[key, value]
对的对象)转换为一个对象。它是Object.entries()
的逆操作。
2. 语法
Object.fromEntries(iterable)
- iterable:一个可迭代的键值对(
[key, value]
)的对象,如数组或Map
。
3. 示例代码
示例1:从键值对数组创建对象
const entries = [
['name', 'Alice'],
['age', 25],
['city', 'New York']
];
const obj = Object.fromEntries(entries);
console.log(obj);
// 输出: { name: 'Alice', age: 25, city: 'New York' }
示例2:从Map
创建对象
const map = new Map([
['color', 'blue'],
['size', 'medium']
]);
const obj = Object.fromEntries(map);
console.log(obj);
// 输出: { color: 'blue', size: 'medium' }
示例3:处理查询参数
const queryString = 'name=Alice&age=25';
const params = new URLSearchParams(queryString);
const obj = Object.fromEntries(params);
console.log(obj);
// 输出: { name: 'Alice', age: '25' }
4. 注意事项
-
键的覆盖:如果键重复,后面的值会覆盖前面的值。
const entries = [ ['a', 1], ['b', 2], ['a', 3] ]; const obj = Object.fromEntries(entries); console.log(obj); // 输出: { a: 3, b: 2 }
-
非字符串键:键会被转换为字符串类型。
const entries = [ [1, 'one'], [true, 'bool'] ]; const obj = Object.fromEntries(entries); console.log(obj); // 输出: { '1': 'one', 'true': 'bool' }
5. 扩展知识点
-
与
Object.entries()
结合使用const obj = { a: 1, b: 2, c: 3 }; const newObj = Object.fromEntries( Object.entries(obj).map(([key, value]) => [key, value * 2]) ); console.log(newObj); // 输出: { a: 2, b: 4, c: 6 }
-
过滤对象属性
const obj = { a: 1, b: 2, c: 3 }; const filteredObj = Object.fromEntries( Object.entries(obj).filter(([key, value]) => value > 1) ); console.log(filteredObj); // 输出: { b: 2, c: 3 }
三、Object.getOwnPropertyDescriptor()
1. 基础介绍
Object.getOwnPropertyDescriptor()
方法返回指定对象上一个自有属性对应的属性描述符。如果没有对应的属性,返回undefined
。
2. 语法
Object.getOwnPropertyDescriptor(obj, prop)
- obj:要查找的对象。
- prop:要获取其属性描述符的属性名。
3. 示例代码
示例1:获取数据属性描述符
const obj = { a: 1 };
const descriptor = Object.getOwnPropertyDescriptor(obj, 'a');
console.log(descriptor);
// 输出: { value: 1, writable: true, enumerable: true, configurable: true }
示例2:获取存取属性描述符
const obj = {
get b() {
return 2;
}
};
const descriptor = Object.getOwnPropertyDescriptor(obj, 'b');
console.log(descriptor);
// 输出: { get: [Function: get b], set: undefined, enumerable: true, configurable: true }
示例3:属性不存在
const descriptor = Object.getOwnPropertyDescriptor(obj, 'c');
console.log(descriptor); // 输出: undefined
4. 属性描述符的属性
1. 数据描述符(Data Descriptor)
- value:属性的值。
- writable:属性的值是否可写。
- enumerable:属性是否可枚举。
- configurable:属性的描述符是否可修改,属性是否可删除。
2. 存取描述符(Accessor Descriptor)
- get:获取属性值的函数。
- set:设置属性值的函数。
- enumerable:同上。
- configurable:同上。
5. 注意事项
- 仅获取自有属性:该方法只会返回对象自身的属性描述符,不会查找原型链上的属性。
- 区分属性类型:通过查看描述符,可以区分属性是数据属性还是存取属性。
6. 扩展知识点
-
应用于调试和测试
了解属性的描述符,有助于调试代码,尤其是在涉及属性的可写性、可枚举性和可配置性时。
-
与
Object.defineProperty()
结合使用可以先获取一个属性的描述符,修改后再定义到另一个对象上。
const obj1 = { a: 1 }; const descriptor = Object.getOwnPropertyDescriptor(obj1, 'a'); const obj2 = {}; Object.defineProperty(obj2, 'b', descriptor); console.log(obj2.b); // 输出: 1
四、Object.getOwnPropertyDescriptors()
1. 基础介绍
Object.getOwnPropertyDescriptors()
方法返回一个对象的所有自身属性的属性描述符。该方法的返回值是一个对象,其属性名是原对象的属性名,属性值是对应的属性描述符。
2. 语法
Object.getOwnPropertyDescriptors(obj)
- obj:要获取属性描述符的对象。
3. 示例代码
示例1:获取对象的所有属性描述符
const obj = {
a: 1,
get b() {
return 2;
}
};
const descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
/* 输出:
{
a: { value: 1, writable: true, enumerable: true, configurable: true },
b: {
get: [Function: get b],
set: undefined,
enumerable: true,
configurable: true
}
}
*/
示例2:实现对象的完整克隆
const obj = {
a: 1,
get b() {
return this.a * 2;
}
};
const clone = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
console.log(clone.b); // 输出: 2
clone.a = 3;
console.log(clone.b); // 输出: 6
4. 注意事项
- 自有属性:仅获取对象自身的属性描述符,不包括原型链上的属性。
- 可用于复制对象的属性:在复制对象时,使用
Object.defineProperties()
和Object.getOwnPropertyDescriptors()
可以复制属性的所有特性,包括访问器。
5. 扩展知识点
-
复制对象时保留属性特性
function completeClone(origin) { return Object.create( Object.getPrototypeOf(origin), Object.getOwnPropertyDescriptors(origin) ); } const obj = { a: 1, get b() { return this.a * 2; } }; const clone = completeClone(obj); console.log(clone.b); // 输出: 2
-
实现浅拷贝的另一种方法
与
Object.assign()
不同,使用Object.getOwnPropertyDescriptors()
可以复制属性的特性。
五、总结
在本篇博客中,我们深入探讨了Object
构造函数的四个重要静态方法:freeze
、fromEntries
、getOwnPropertyDescriptor
、getOwnPropertyDescriptors
。
Object.freeze()
:用于冻结对象,使其不可变。适用于需要创建常量对象的场景。Object.fromEntries()
:将键值对数组或可迭代对象转换为普通对象,方便处理数据转换。Object.getOwnPropertyDescriptor()
:获取对象某个自有属性的属性描述符,便于了解属性的特性和行为。Object.getOwnPropertyDescriptors()
:获取对象所有自有属性的属性描述符,适用于对象的深度复制和属性特性的复制。
在实际开发中,充分理解和合理应用这些方法,可以帮助我们更有效地操作对象,提高代码的健壮性和可维护性。
注意事项总结
- 冻结对象时的限制:
Object.freeze()
是浅冻结,嵌套对象需要深冻结。 fromEntries()
键的处理:键会被转换为字符串类型,重复键会被后面的值覆盖。- 属性描述符的重要性:了解属性的描述符有助于精确控制对象的属性行为,避免意外的修改或访问。
- 深度复制对象:使用
Object.getOwnPropertyDescriptors()
和Object.create()
可以实现对象的完整克隆,包括属性特性。