10 个在 JavaScript 中处理对象的实用函数
01、Object.freeze
Object.freeze() 冻结一个对象。无法再更改冻结的对象。它返回传入的相同对象。
这是通过在创建时冻结对象来实现 JavaScript 不变性的最简单方法。
const game = Object.freeze({ name: 'Warcraft' }); game.developer = 'Blizzard'; //TypeError: Cannot add property developer, object is not extensible
唯一需要注意的是 Object.freeze() 只冻结对象的直接属性,执行所谓的“浅冻结”。稍后我们将使用递归和其他对象效用函数来实现深度冻结函数。
02、Object.isFrozen
Object.isFrozen() 检测对象是否被冻结。
console.log(Object.isFrozen(game)); //true
03、Object.keys
Object.keys() 给出一个包含所有拥有的属性键的数组。检查下面的代码,将游戏对象的所有键检索到一个新的字符串数组中。
const game = { name: 'Warcraft', developer: 'Blizzard' }; const keys = Object.keys(game); console.log(keys); // ["name", "developer"]
一旦我们有了键数组,我们就可以开始在它上面使用数组方法了,比如 forEach。
Object
.keys(game)
.forEach(key => console.log(key));
04、Object.values()
Object.values() 返回一个包含所有拥有的属性值的数组。
const values = Object.values(game); console.log(values); // ["Warcraft", "Blizzard"]
05、Object.entries
Object.entries() 返回一个对象所有属性的 [key, value] 对数组。
const game = { name: 'Warcraft', developer: 'Blizzard' }; const keyValuePairs = Object.entries(game); console.log(keyValuePairs); // [ // ["name", "Warcraft"], // ["developer", "Blizzard"] // ]
请注意,条目是一个具有两个值 [key, value] 的数组。结果是一组数组。一旦我们在数组中拥有所有 [key, value] 对,我们使用数组方法。
Object .entries(game) .forEach(([key, value]) => console.log(key, value)); //name Warcraft //developer Blizzard
创建一个深度冻结工具
现在让我们尝试使用 Object.entries 和 Object.freeze 实用程序创建一个深度冻结实用程序。请记住 Object.freeze 会进行浅度冻结。考虑下面的游戏对象,developer 属性在其中存储了一个对象,而不是一个字符串。即使我们冻结游戏对象,我们也可以更改开发者对象的名称。
const game = { name: 'Overwatch', developer: { name: 'Blizzard' } }; Object.freeze(game); game.developer.name = 'Activision Blizzard'; console.log(game); //{ // developer: {name: "Activision Blizzard"}, // name: "Overwatch" //}
以下是我们如何使用递归实现 deepFreeze 函数。首先,我们将所有条目作为 [key, value] 对的数组获取。然后我们使用 forEach 数组方法遍历所有这些属性,并对作为对象的值再次调用 deepFreeze。最后,我们冻结并返回当前对象。
function deepFreeze(object) { Object .entries(object) .forEach(([name, value]) => { if (value && typeof value === 'object') { deepFreeze(value); } }); return Object.freeze(object); }
现在,如果我们尝试更改内部对象的属性,则会出现错误。
deepFreeze(game); game.developer.name = 'Activision Blizzard'; //Cannot assign to read only property 'name' of object
将对象转换为地图
我们可以使用 Object.entries 和 Map 构造函数将对象转换为地图。这是一个例子
const game = { name: 'Warcraft', developer: 'Blizzard' }; const map = new Map(Object.entries(game)); console.log(map); //Map(2) {"name" => "Warcraft", "developer" => "Blizzard"}
06、Object.fromEntries
我们执行相反的操作并使用 Object.fromEntries 实用程序将 [key, value] 对数组转换为对象。考虑下面的代码。
const keyValuePairs = [ ["name", "Warcraft"], ["developer", "Blizzard"] ]; const game = Object.fromEntries(keyValuePairs); console.log(game); //{name: "Warcraft", developer: "Blizzard"}
07、Object.create()
Object.create() 使用另一个对象作为新对象的原型创建一个新对象。这是一个具有 toString 方法的原型对象。
const gamePrototype = { toString : function(){ return `${this.name}, developed by ${this.developer}`; } };
然后我们使用之前的原型来创建一个引用它的新对象。
const game = Object.create(gamePrototype); game.name = "Overwatch"; game.developer = "Blizzard"; console.log(game.toString()); //'Overwatch, developed by Blizzard
08、Object.getPrototypeOf
Object.getPrototypeOf() 返回指定对象的原型。
Object.getPrototypeOf(game) === gamePrototype; //true
即使是空的对象字面量也有一个原型,即 Object.prototype。
const obj = {}; Object.getPrototypeOf(obj) === Object.prototype; //true
09、Object.assign()
Object.assign() 将拥有的属性从一个或多个源对象复制到目标对象。然后它返回目标对象。下面是我们将 gameMethods 对象中的属性复制到游戏对象中的示例。
const gameMethods = { toString : function(){ return `${this.name}, developed by ${this.developer}`; } }; const game = { name: 'Diablo', developer: 'Blizzard' }; Object.assign(game, gameMethods); console.log(game.toString()); //'Diablo, developed by Blizzard'
我们可以避免修改现有的游戏对象,而是创建一个包含来自现有游戏和 gameMethods 对象的属性的新对象。
我们可以通过将它们的属性复制到一个空对象来做到这一点
const anotherGame = Object.assign({}, game, gameMethods); console.log(anotherGame.toString()); //'Diablo, developed by Blizzard'
如您所见,我们能够使用 Object.assign 和 Object.create 将 toString 方法添加到游戏对象中。
不同之处在于 Object.assign 将属性添加到每个新创建的对象中。Object.create 仅向新创建的对象添加对现有原型的引用。
它不会复制所有可重用的属性。只有在构建数千个对象时才能注意到这种差异。
Object.assign() 也可用于克隆普通对象。
const game = { name: 'Starcraft' }; const anotherGame = Object.assign({}, game); console.log(anotherGame); //{name: "Starcraft"} console.log(game === anotherGame); //false
10、Object.defineProperty
Object.defineProperty 实用程序修改现有属性或在对象上定义新属性。它改变并返回修改后的对象。
Object.defineProperty 主要用于设置特定的属性描述符,如可枚举性或定义只读属性。
考虑以下对象。
const game = { name: 'Fornite', developer: 'Epic Games', toString: function(){ return `${this.name}, developed by ${this.developer}`; } }; Object.keys(game); //["name", "developer", "toString"]
默认情况下,所有新属性都是可枚举的。如您所见,调用 Object.key 实用程序时还返回了 toString 属性名称。
如果我们不希望 toString 成为可枚举属性,我们可以使用 Object.defineProperty 实用程序将其标记为可枚举属性。
Object.defineProperty(game, 'toString', { enumerable: false }); console.log(Object.keys(game)); //["name", "developer"]
尽管如此,我们可以根据需要更改 toString 属性。
game.toString = undefined;
我们通过将 toString 属性上的可写描述符设置为 false 来避免这种行为。
Object.defineProperty(game, 'toString', { enumerable: false, writable: false }); game.toString = undefined; //Cannot assign to read only property 'toString' of object
Object.defineProperty 实用程序可用于添加动态 getter 和 setter。
下面是向游戏对象添加动态 getter 的示例。仅当用户有权访问时,getter 才返回属性的值,否则会引发错误。
const game = { name: 'Fornite', developer: 'Epic Games', }; const hasRight = false; const propName = "name"; Object.defineProperty(game, propName, { get : function () { if(hasRight){ return game[propName]; } else { throw `${propName} no access`; } } }); console.log(game.name);
、//name no access
重点说明
-
我们拥有用于冻结和检测冻结对象的实用程序。(Object.freeze / Object.isFrozen)
-
其他实用程序帮助我们在新数组中提取对象的所有属性,从而使我们能够访问强大的数组方法。(Object.keys / Object.values / Object.entries)
-
我们可以创建一个继承自现有原型的新对象,然后使用另一个实用程序函数来检查给定对象的原型。(Object.create/Object.getPrototypeOf)
-
使用 Object.entries 和 Object.fromEntries 助手将对象转换为 [key, value] 对数组,然后再转换回对象变得简单。这使得在地图和对象之间转换变得容易。
-
Object.assign() 帮助我们克隆对象或将多个对象的属性复制到一个新对象中。
-
Object.defineProperty 实用程序修改现有属性或定义新属性。它主要用于更改属性描述符。