Leetcode icon indicating copy to clipboard operation
Leetcode copied to clipboard

【深入理解JS核心技术】8. 你如何比较Object和Map

Open webVueBlog opened this issue 2 years ago • 0 comments

  1. 对象的键是字符串和符号; Map是任何值,包括函数、对象等。
  2. Map 中的键是有序的,而添加到 Object 中的键不是。因此,在对其进行迭代时, Map 对象会按插入顺序返回键。
  3. 使用 size 属性轻松获取 Map 的大小,而 Object 中的属性数量必须手动确定。
  4. Map 是可迭代的,因此可以直接迭代,而对 Object 进行迭代则需要以某种方式获取其键并对其进行迭代。
  5. 在涉及频繁添加和删除密钥对的场景中,Map 可能会表现得更好。

Object(大多数引用值的示例使用的是Object类型)

显示地创建Object的示例有两种方式:第一种使用new操作符和Object构造函数;另外一种使用对象字面量表示法。

对象字面量是对象定义的简写形式,目的是为了简化包含大量属性的对象的创建。

对象、类与面向对象编程

ECMA-262 将对象定义为一组属性的无序集合。创建自定义对象的通常方式是创建 Object 的一个新实例,然后再给它添加属性和方法。(目前流行使用对象字面量方式)

ECMA-262 使用一些内部特性来描述属性的特征。

为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来,比如[[Enumerable]]

属性分两种:数据属性和访问器属性。

  1. 数据属性

数据属性有 4 个特性描述它们的行为。

  • [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特 性,以及是否可以把它改为访问器属性。
  • [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。
  • [[Writable]]:表示属性的值是否可以被修改。
  • [[Value]]:包含属性实际的值。默认值为 undefined。

要修改属性的默认特性,就必须使用 Object.defineProperty()方法。这个方法接收 3 个参数:要给其添加属性的对象、属性的名称和一个描述符对象。

一个属性被定义为不可配置之后,就不能再变回可配置的了。虽然可以对同一个属性多次调用 Object.defineProperty(),但在把 configurable 设置为 false 之后就会受限制了。

  1. 访问器属性

访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不过这两个函数不是必需的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。

访问器属性有 4 个特性描述它们的行为。

  • [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。
  • [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。
  • [[Get]]:获取函数,在读取属性时调用。
  • [[Set]]:设置函数,在写入属性时调用。

访问器属性是不能直接定义的,必须使用 Object.defineProperty()。

定义多个属性

ECMAScript 提供了 Object.defineProperties()方法。这个方法可以通过多个描述符一次性定义多个属性。它接收两个参数:要为之添加或修改属性的对象和另一个描述符对象,其属性与要添加或修改的属性一一对应。

读取属性的特性

使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述符的属性名。返回值是一个对象,对于访问器属性包含configurable、enumerable、get 和 set 属性,对于数据属性包含 configurable、enumerable、writable 和 value 属性。

ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors()静态方法。

这个方法实际上会在每个自有属性上调用 Object.getOwnPropertyDescriptor()并在一个新对象中返回它们。

合并对象

ECMAScript 6为合并对象提供了 Object.assign()方法。这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举(Object.propertyIsEnumerable()返回 true)和自有(Object.hasOwnProperty()返回 true)属性复制到目标对象。

以字符串和符号为键的属性会被复制。对每个符合条件的属性,这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标对象上的[[Set]]设置属性的值。

let dest, src, result; 

/** 
 * 简单复制
 */ 

dest = {}; 
src = { id: 'src' }; 

result = Object.assign(dest, src); 

// Object.assign 修改目标对象
// 也会返回修改后的目标对象
console.log(dest === result); // true 
console.log(dest !== src); // true 
console.log(result); // { id: src } 
console.log(dest); // { id: src }
/** 
 * 多个源对象
 */ 
dest = {}; 
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' }); 
console.log(result); // { a: foo, b: bar }

/** 
 * 获取函数与设置函数
 */ 
dest = { 
 set a(val) { 
 console.log(`Invoked dest setter with param ${val}`); 
 } 
}; 
src = { 
 get a() { 
 console.log('Invoked src getter'); 
 return 'foo'; 
 } 
};

/** 
 * 获取函数与设置函数
 */ 
dest = { 
 set a(val) { 
 console.log(`Invoked dest setter with param ${val}`); 
 } 
}; 
src = { 
 get a() { 
 console.log('Invoked src getter'); 
 return 'foo'; 
 } 
}; 
Object.assign(dest, src); 
// 调用 src 的获取方法
// 调用 dest 的设置方法并传入参数"foo" 
// 因为这里的设置函数不执行赋值操作
// 所以实际上并没有把值转移过来
console.log(dest); // { set a(val) {...} }

Object.assign()实际上对每个源对象执行的是浅复制。

对象标识及相等判定

在 ECMAScript 6 之前

// 这些是===符合预期的情况
console.log(true === 1); // false 
console.log({} === {}); // false 
console.log("2" === 2); // false

// 这些情况在不同 JavaScript 引擎中表现不同,但仍被认为相等
console.log(+0 === -0); // true 
console.log(+0 === 0); // true 
console.log(-0 === 0); // true

// 要确定 NaN 的相等性,必须使用极为讨厌的 isNaN() 
console.log(NaN === NaN); // false 
console.log(isNaN(NaN)); // true

ECMAScript 6 规范新增了 Object.is()

console.log(Object.is(true, 1)); // false 
console.log(Object.is({}, {})); // false 
console.log(Object.is("2", 2)); // false

// 正确的 0、-0、+0 相等/不等判定
console.log(Object.is(+0, -0)); // false 
console.log(Object.is(+0, 0)); // true 
console.log(Object.is(-0, 0)); // false

// 正确的 NaN 相等判定
console.log(Object.is(NaN, NaN)); // true

增强的对象语法

  1. 属性值简写
  2. 可计算属性
  3. 简写方法名

对象解构

ECMAScript 6 新增了对象解构语法

  1. 嵌套解构
  2. 部分解构
  3. 参数上下文匹配

Map

作为 ECMAScript 6 的新增特性,Map 是一种新的集合类型.

const m = new Map();

// 使用嵌套数组初始化映射
const m1 = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
alert(m1.size); // 3

// 使用自定义迭代器初始化映射
const m2 = new Map({ 
 [Symbol.iterator]: function*() { 
 yield ["key1", "val1"]; 
 yield ["key2", "val2"]; 
 yield ["key3", "val3"]; 
 } 
}); 
alert(m2.size); // 3

// 映射期待的键/值对,无论是否提供
const m3 = new Map([[]]); 
alert(m3.has(undefined)); // true 
alert(m3.get(undefined)); // undefined

初始化之后,可以使用 set()方法再添加键/值对。另外,可以使用 get()和 has()进行查询,可以通过 size 属性获取映射中的键/值对的数量,还可以使用 delete()和 clear()删除值。

set()方法返回映射实例,因此可以把多个操作连缀起来。

const m = new Map(); 

const functionKey = function() {}; 
const symbolKey = Symbol(); 
const objectKey = new Object(); 

m.set(functionKey, "functionValue"); 
m.set(symbolKey, "symbolValue"); 
m.set(objectKey, "objectValue"); 

alert(m.get(functionKey)); // functionValue 
alert(m.get(symbolKey)); // symbolValue 
alert(m.get(objectKey)); // objectValue 

// SameValueZero 比较意味着独立实例不冲突
alert(m.get(function() {})); // undefined


const m = new Map(); 

const objKey = {}, 
 objVal = {}, 
 arrKey = [], 
 arrVal = []; 

m.set(objKey, objVal); 
m.set(arrKey, arrVal); 

objKey.foo = "foo"; 
objVal.bar = "bar"; 
arrKey.push("foo"); 
arrVal.push("bar"); 

console.log(m.get(objKey)); // {bar: "bar"} 
console.log(m.get(arrKey)); // ["bar"]

顺序与迭代

映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。可以通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器。

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
alert(m.entries === m[Symbol.iterator]); // true

for (let pair of m.entries()) { 
 alert(pair); 
} 
// [key1,val1] 
// [key2,val2] 
// [key3,val3]

for (let pair of m[Symbol.iterator]()) { 
 alert(pair); 
} 
// [key1,val1] 
// [key2,val2] 
// [key3,val3]
const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
console.log([...m]); // [[key1,val1],[key2,val2],[key3,val3]]

m.forEach((val, key) => alert(`${key} -> ${val}`)); 
// key1 -> val1 
// key2 -> val2 
// key3 -> val3
const m1 = new Map([ 
 ["key1", "val1"] 
]); 

// 作为键的字符串原始值是不能修改的
for (let key of m1.keys()) { 
 key = "newKey"; 
 alert(key); // newKey 
 alert(m1.get("key1")); // val1 
} 

const keyObj = {id: 1}; 

const m = new Map([ 
 [keyObj, "val1"] 
]); 

// 修改了作为键的对象的属性,但对象在映射内部仍然引用相同的值
for (let key of m.keys()) { 
 key.id = "newKey"; 
 alert(key); // {id: "newKey"} 
 alert(m.get(keyObj)); // val1 
} 
alert(keyObj); // {id: "newKey"}

ECMAScript 6 新增的“弱映射”(WeakMap)是一种新的集合类型。不可迭代键。

弱映射中的键只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置键会抛出TypeError。值的类型没有限制。

WeakMap 中“weak”表示弱映射的键是“弱弱地拿着”的。

使用弱映射: 1. 私有变量 ; 2. DOM 节点元数据

未完结!更多内容尽情期待下一节~

【深入理解JS核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

webVueBlog avatar Apr 30 '22 08:04 webVueBlog