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

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

一起养成写作习惯!5月4/31

  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核心技术】欢迎各位观众老爷,求点赞,求关注,求转发~

低调务实优秀中国好青年 (简介) && 附加答案

中文 | English

一个 ☝️ 正经的前端学习 开源 仓库,启发来自 淘宝大佬 @冴羽 ,初心做一个真正能帮助到大家的仓库。一个人可以走的更快,但一群人才能走的更远。(非常口语化的,手写总结)

欢迎大家前来讨论,如果觉得对你的学习有一定的帮助,欢迎点个Star (此仓库每天都会准时更新)- vx联系: xiaoda0423

👤:我是哪吒: 如果你所学的东西 处于喜欢 才会有强大的动力支撑。

地址 ⬇️

github.com/webVueBlog/…

🐤 交流讨论 && 如何学习 && 转载声明 && 帮忙修正以及补充

第一:你可以直接在本仓库阅读即可,阶段性学习。 (可以转载里面的所有知识点用到任何地方,但请添加仓库的地址)有问题欢迎提交issues

🐂 阶段三十一(648)

展开查看

🐂 阶段三十(623)

展开查看

🤨 阶段二十九(598)

展开查看

🤨 阶段二十九(572)

展开查看

😛 阶段二十八(565)

展开查看

😛 阶段二十七(555)

展开查看

😛 阶段二十六(544)

展开查看

😛 阶段二十五(532)

展开查看

😛 阶段二十四(525)

展开查看

😛 阶段二十三(514)

展开查看

📕 阶段二十二(500)

展开查看

😋 阶段二十一(480)

展开查看

✔ 阶段二十(470)

展开查看

😗 阶段十九(460)

展开查看

🐉 阶段十八(450)

展开查看

🐔 阶段十七(440)

展开查看

🐟 阶段十六(425)

展开查看

🦐 阶段十五(401)

展开查看

🦂 阶段十四(384)

展开查看

😘 阶段十三(370)

展开查看

🥰 阶段十二(340)

展开查看

😉 阶段十一(324)

展开查看

🙃 阶段十(306)

展开查看

😍 阶段九(285)

展开查看

🧑🏻 阶段八(250)

展开查看

🧑🏻‍💻 阶段七(215)

展开查看

😇 阶段六(190)

展开查看

🧑🏻‍💻 阶段五(175)

展开查看

🥲 阶段四(150)

展开查看

🧑🏻‍💻 阶段三(145)

展开查看

🤣 阶段二(100)

展开查看

🧑🏻‍💻 阶段一(50)

展开查看

全栈架构师

展开查看

深入理解JS核心技术

NumberTitle
1在 JavaScript 中创建对象的可能方式有哪些
2什么是原型链
3调用、应用和绑定有什么区别
4什么是 JSON 及其常用操作
5数组切片方法的目的是什么
6数组拼接方法的目的是什么
7切片和拼接有什么区别
8你如何比较Object和Map
9== 和 === 运算符有什么区别
10什么是 lambda 或箭头函数
11什么是一级函数
12什么是一阶函数
13什么是高阶函数
14什么是一元函数
15什么是柯里化函数
16什么是纯函数
17let 关键字的作用是什么
18let 和 var 有什么区别
19选择名称let作为关键字的原因是什么
20如何在 switch 块中重新声明变量而不会出错
21什么是时间死区
22什么是 IIFE(立即调用函数表达式)
23您如何在 JavaScript 中解码或编码 URL
24什么是备忘
25什么是吊装
26ES6 中的类是什么
27什么是闭包
28什么是模块
29为什么需要模块
30javascript中的作用域是什么
31什么是服务人员
32如何使用 service worker 操作 DOM
33如何在 service worker 重启时重用信息
34什么是索引数据库
35什么是网络存储
36什么是发布消息
37什么是饼干
38为什么需要 Cookie
39cookie 中有哪些选项
40如何删除 cookie
41cookie、本地存储和会话存储有什么区别
42localStorage 和 sessionStorage 的主要区别是什么
43您如何访问网络存储
44会话存储上可用的方法有哪些
45什么是存储事件及其事件处理程序
46为什么需要网络存储
47你如何检查网络存储浏览器支持
48你如何检查网络工作者浏览器支持
49举个 web worker 的例子
50webworkers对DOM有什么限制
51什么是承诺
52为什么需要承诺
53承诺的三种状态是什么
54什么是回调函数
55为什么我们需要回调
56什么是回调地狱
57什么是服务器发送事件
58您如何接收服务器发送的事件通知
59如何检查浏览器对服务器发送事件的支持
60服务器发送的事件有哪些可用的事件
61承诺的主要规则是什么
62什么是回调中的回调
63什么是承诺链
64什么是promise.all
65承诺中比赛方法的目的是什么
66什么是javascript中的严格模式
67为什么需要严格模式
68你如何声明严格模式
69双感叹号的目的是什么
70删除运算符的目的是什么
71什么是 typeof 运算符
72什么是未定义属性
73什么是空值
74null 和 undefined 有什么区别
75什么是评估
76窗口和文档有什么区别
77你如何在javascript中访问历史记录
78你如何检测大写锁定键是否打开
79什么是 NaN
80未声明变量和未定义变量有什么区别
81什么是全局变量
82全局变量有什么问题
83什么是 NaN 属性
84isFinite 函数的目的是什么
85什么是事件流
86什么是事件冒泡
87什么是事件捕获
88如何使用 JavaScript 提交表单
89您如何找到操作系统详细信息
90文档加载和 DOMContentLoaded 事件有什么区别
91本机,主机和用户对象之间有什么区别
92用于调试 JavaScript 代码的工具或技术有哪些
93与回调相比,promise 的优缺点是什么
94属性和属性有什么区别
95什么是同源策略
96void 0的目的是什么
97JavaScript 是编译型语言还是解释型语言
98JavaScript 是区分大小写的语言吗
99Java和JavaScript之间有什么关系吗
100什么是事件
101谁创建了 JavaScript
102preventDefault 方法有什么用
103stopPropagation 方法有什么用
104return false 涉及哪些步骤
105什么是物料清单
106setTimeout 有什么用
107setInterval 有什么用
108为什么 JavaScript 被视为单线程
109什么是事件委托
110什么是 ECMAScript

👩🏻‍💻:webVueBlog的leetcode刷题📒

  1. Number题号
  2. Title题目
  3. Difficulty难度
  4. Navigation解答
NumberTitleDifficultyNavigation
1.两数之和两数之和
2.两数相加两数相加
3.无重复字符的最长子串无重复字符的最长子串

以 「早起」、「运动」、「冥想」、「写作」、「阅读」这五件能够快速改变人生的事情为切入点,帮助大家建立良好的生活习惯,技术的成长绝不是一朝一夕,良好的习惯将会帮助我们更快的进步,但在技术之外,我更希望大家能在这些事情的坚持中,收获一份自信,多一份底气,对人生多一份积极。 --- (来源:低调务实优秀中国好青年群)

License

MIT

分类:
前端