复习 Array,重学 JavaScript

时间:2021-01-01 16:28:07

1 数组与对象

在 JavaScript 中,一个对象的键只能有两种类型:stringsymbol。下文只考虑键为字符串的情况。

1.1 创建对象

在创建对象时,若对象的键为数字,或者由 字母+数字 组成,那么键上的引号可以省去:

var obj1 = {1: 'one', 2: 'two'} // 等同于 {'1': 'one', '2': 'two'}

var obj2 = {'one': 1, 'two': 2} // 等同于 {one: 1, two: 2}

如果对象的键由 数字+字母 组成,那么键上的引号不能省去:

var obj = {'3d': true}  // √
var obj = {3d: true} // ×

1.2 访问对象

在 JavaScript 中,访问一个对象的属性有三种方式++:++

  1. 方括号 + 字符串:obj['prop']
  2. 方括号 + 字符串变量:obj[keyName]
  3. 点 + 键名
var obj = {prop: 666, 1: 'one'}

obj.prop // => 666
obj['prop'] // => 666 // 若方括号内为数字,语法解释器会将其转换为字符串类型
obj['1'] === obj[1] // => true (*) let keyName = 'prop'
obj[keyName] // => 666 // 访问不存在的属性时,输出 undefined
obj.keyName // => undefined
obj['keyName'] // => undefined // 若方括号内既非数字也非字符串,语法解释器会将其解释为变量
obj[variable] // 将会报错,因为变量 variable 没有定义

上面代码的 (*) 行,obj[1] 方括号内是一个数组,语法解释器会自动将其转换成字符串类型,因此 obj['1']obj[1] 其实是一个东西,这样,我们就找到了 JavaScript 中的数组与对象的相通之处。

const arr = ['a', 'b', 'c']

arr[0] === arr['0'] // => true 

Object.keys(arr) // => ['0', '1', '2'] 注意输出的不是 [0, 1, 2]

可以看到,虽然我们习惯通过索引获取数组的元素,但在语法内部还是通过对象的键获取对应的值来实现的。根据这一特性构造一个伪数组:

const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3}

arrayLike[0] === arrayLike['0']

1.3 数据类型

JavaScript 的八大数据类型分别是:numberbigintbooleanstringnullundefinedsymbolobject,前 7 种称为基本数据类型,object 为唯一的复杂数据类型。

JavaScript 中有 NumberStringObject......但他们是作为构造函数实际存在的。而 numberstringobject等在 JavaScript 中并不存在,是我们用来描述数据类型的。

基本数据类型的数据都是作为一个整体存在,复制操作是直接创建数据的副本,复制以后两个基本数据变量不会相互影响。

复杂数据类型的变量存放的是一个引用,复制以后两个变量指向的都是同一个内存地址,其中一个变量对数据作出改变会影响至另一个变量。

Array 并不是 JavaScript 中八大数据类型的其中一种,它之所以存在,是因为实际应用经常会有一些数据是有序的,而数组正是一种 有序 的数据集合。

我们可以认为: 数组 = 对象 + 额外的特性,这里 额外的特性 就是指数组专有的属性和方法,如表示数组长度的 length,可以进行数组拼接的 concat() 方法......

一切皆为对象,这是 JavaScript 世界中一句广为流传的话,也就是说,如果一个东西不属于 7 个基本数据类型,都可以认为它是基于对象扩展而来的一个新对象,也都能套用上面的公式:对象 + 额外的特性 = 新对象

上面说过,JavaScript 中是实际存在 Object 的,它是一个构造函数。Object 有很多方法可以帮助我们了解一个新对象的特性:

  • Object.keys(o: object): string[] 返回对象中所有 enumerable = true (可枚举)的键
  • Object.values(o: object): any[] 返回对象中所有可枚举的键的值
  • Object.entries(o: object): any[] 以二维数组的格式返回键值对
  • Object.getOwnPropertyNames(o: object): string[] 返回对象中所有的属性名(也就是键),包括 enumerable = false 的属性
  • Obejct.getOwnPropertyDescriptors(o: object): object 返回对象所有属性的描述符

获取数据类型:

  • typeof o
  • o.__proto__.constructor.name
  • Object.prototype.toString.call(o)

2 数组 Array

Array 表示数组的构造函数,Array === Array.prototype.constructor 将输出 true

据我观察,JavaScript 中首字母大写的都是构造函数.......

Object === Object.prototype.constructor // => true
Date === Date.prototype.constructor // => true
Map === Map.prototype.constructor // => true
......

复习 Array,重学 JavaScript

上图中,将 Array 换成 Date Map RegExp 等 JavaScript 中的其他对象,各关系依然成立。

通过 Object.getOwnPropertyNames() 即可获取该对象下所有的属性(包括不可枚举的):

Object.getOwnPropertyNames(Array) // => ["length", "name", "prototype", "isArray", "from", "of"]

Object.getOwnPropertyNames(Date) // => ["length", "name", "prototype", "now", "parse", "UTC"]

......

2.1 Array 的属性

Array.length: number

构造函数的属性,是静态的,固定值为 1

Array.name: string

构造函数的静态属性,固定值为 "Array"

Array.prototype: any[]

数组的原型对象,可认为它是程序中所有数组实例的“母体”

[].__proto__ === Array.prototype // => true

// 可以认为 Array.prototype ≈ []
Object.prototype.toString.call(Array.prototype) // => '[object Array]'
Array.prototype.length // => 0 Array.prototype.max = function () {
let currentArr = this // “谁调用该方法,this 就是谁”
let max = -Infinity // 无穷小
for (let i = 0; i < currentArr.length; ++i) {
max = currentArr[i] > max ? currentArr[i] : max
}
return max
} [6, 9, 7].max() // => 9

2.2 Array 的方法

Array.isArray(param: any): boolean

判断传入的参数是否为数组

// 下面均返回 true
Array.isArray([1])
Array.isArray([]) // 构造空数组
Array.isArray(Array()) // 构造空数组
Array.isArray(new Array()) // 构造空数组
Array.isArray(Array.prototype) // 数组的原型就是数组!

Array.from(arrayLike any, callback?: Function, thisArg?: any): any[]

根据传入的 arrayLike 创建一个数组,并将其返回。arrayLike 应该是下面两种对象的某一种:

// 伪数组对象
Array.from({0: 'a', 1: 'b', length: 2}) // => Array ["a", "b"]
// 可迭代对象
Array.from(['a', 'b'].keys()) // => Array [0, 1]

若指定了 callbackthisArg,那么 Array.from(arrayLike, callback, thisArg) 等同于 Array.from(arrayLike).map(callback, this),即让生成的数组调用一次 map() 方法,实现对数组中元素的“再加工”。

Array.from([1, 2, 3], el => el * 2) // [2, 4, 6]

Array.of(...items): any[]

返回一个数组,数组元素由传入的参数构成

Array.of(66, null, {}, "ok") // => [ 66, null, {}, "ok" ]
Array.of(8).length // => 1
Array(8).length // => 8

3 数组实例

一个 JavaScript 程序中,可以有很多数组实例,这些数组实例的原型只有一个:Array.prototype

[].__proto__ === Array.prototype // => true

[1, 2, 3].__proto__ === Array.prototype // => true

在浏览器控制台中输入 Array.prototype,即可查看当前环境支持的数组全部方法和属性,数组实例都可使用这些方法和属性

复习 Array,重学 JavaScript

可以看到输入 Array.prototype 首先返回的是 [](绝大部分场景下,Array.prototype 是可以用 [] 代替的);其中有两个属性的属性名为 symbol 类型:

Array.prototype[Symbol.iterator] === Array.prototype.values // => true

Array.prototype[Symbol.unscopables]
/* =>
{
copyWithin: true,
entries: true,
fill: true,
find: true,
findIndex: true,
flat: true,
flatMap: true,
includes: true,
keys: true,
values: true
} */

属性值为原始数据类型的只有一个 length,剩下的就是数组成员方法了。

3.1 创建数组实例

4 种方式,创建数组 [1, 2, 3]

[1, 2, 3]

new Array(1, 2, 3)

Array(1, 2, 3)

Array.prototype.constructor(1, 2, 3)

4 种方式,创建长度为 3 的空数组(数组中 3 个元素均为 undefined

new Array(3)

Array(3)

Array.prototype.constructor(3)

var arr = []
arr.length = 3

3.2 数组实例的属性

再次说明:下方的 Array.prototype 都能用 [] 替换

Array.prototype.length: number

通过 length 属性,可以获取、设定一个数组的长度

设定一个比当前 length 更大的数值,可以实现对数组的扩充(扩充的元素为 undefined

const arr = ['a', 'b', 'c']
arr.length = 4
arr[3] // => undefined
arr // => ["a", "b", "c", empty];即相当于 {0: 'a', 1: 'b', 2: 'c', length: 4}

通过设定一个比当前 length 更小的数值,可以实现对数组的裁剪

const arr = ['a', 'b', 'c']
arr.length = 1
arr[1] // => undefined
arr // => ["a"]

3.3 数组实例的方法

下列方法中,如果方法的参数为索引,一般都可以是负索引,如 -1 是倒数第一个元素的索引,-2 是倒数第二个元素的索引......

3.3.1 修改器方法

若数组调用了修改器方法,那么该数组将会 发生变化。常用的修改器方法有如下几个:

copyWithin() fill() pop() push() shift() unshift() reverse() sort() splice()

Array.prototype.copyWithin(target: number, start?: number, end?:number): any[]

复制数组 start (默认为 0)到 end (默认为数组长度) 位置上的元素,再从 target 位置开始粘贴;返回改变后的数组。

let arr = ['a', 'b', 'c', 'd', 'e']
// 复制 arr[3], arr[4], 从 arr[2] 开始进行粘贴
arr.copyWithin(2, 3, 5) // => ["a", "b", "d", "e", "e"]

Array.prototype.fill(value: any, start?: number, end?: number): any[]

value 填充数组,填充的位置是从 start (默认为0) 到 end (默认为数组长度);返回改变后的数组。

let arr = ['a', 'b', 'c', 'd', 'e']
arr.fill([], 3, 5) // => ["a", "b", "c", [], []]
arr.fill('哈') // => ["哈", "哈", "哈", "哈", "哈"]

Array.prototype.pop(): any

删除数组最后一个元素;数组长度将减 1;返回被删除的元素。

let arr = ['a', 'b', 'c']
arr.pop() // => "c"
arr // => ["a", "b"]

Array.prototype.push(...items): number

将参数中的值追加到数组后面;返回数组长度。

let arr = ['a','b']
arr.push('c','d') // => 4
arr // => ["a", "b", "c", "d"]

Array.prototype.shift(): any

删除数组实例的第一个元素;数组长度将减 1;返回被删除的元素

let arr = ['a', 'b']
arr.shift() // => 'a'
arr // Outpus: ['b']

Array.prototype.unshift(...items): number

将传入的参数添加到数组的头部;返回改变后的数组长度

let arr = ['a', 'b']
arr.unshift('A', 'B') // => 4
arr // => ["A", "B", "a", "b"]

Array.prototype.reverse(): any[]

翻转数组实例中元素排列的顺序;返回翻转后的数组

let arr = ['a', 'b', 'c']
arr.reverse() // => ["c", "b", "a"]
arr // => ["c", "b", "a"]

Array.prototype.sort(compare?: Function): any[]

对数组中的元素进行排序;返回排序后的数组

const arr = ['b', 'c', 'a']
arr.sort() // => ["a", "b", "c"]
arr // => 同上

sort() 会试图将数组元素转换为字符串类型,再以字符串的标准进行排序

[2, 31, 111].sort() // [111, 2, 31]

['2', '31', '111'].sort() // ["111", "2", "31"]

可以自定义比较函数,当比较函数返回负数时,排序会发生变化

// arr.reverse() <==> arr.sort(() => -1)
['哈哈', '嘿嘿', '嘻嘻'].sort(() => -1) // => ["嘻嘻", "嘿嘿", "哈哈"] [1, 2, 3, 4].sort((a, b) => {
// 第一轮比较时,b = 1, a = 2,后面也是按照这种先后顺序
return b - a
}) // => [4, 3, 2, 1]

Array.prototype.splice(start: number, deleteCount?: number, ...items): any[]

删除从 start 开始的 deleteCount 个元素,再将 items 插入此处;返回被删除的所有元素

const arr1 = ['a', 'b', 'c', 'd', 'e']
arr1.splice(2) // => ["c", "d", "e"]
arr1 // => ["a", "b"] const arr2 = ['a', 'b', 'c', 'd', 'e']
arr2.splice(2, 2) // => ["c", "d"]
arr2 // => ["a", "b", "e"] const arr3 = ['a', 'b', 'c', 'd', 'e']
arr3.splice(-2, 1, 'dd') // => ["d"]
arr3 // => ["a", "b", "c", "dd", "e"]

3.3.2 访问方法

数组调用访问方法不会改变自身,它会返回访问方法期望的值。常用的访问方法有:concat() includes() join() slice() toString() indexOf() lastIndexOf()

Array.prototype.concat(...items): any[]

将参数拼接到数组末尾;返回拼接后的数组

const arr = [1, 2]
arr.concat(3, 4) // => [1, 2, 3]
arr // => [1, 2]

如果传入的参数是个数组,那么会将该数组展开再拼接,但至多展开一层

[].concat('a', ['b', 'c'], [1, [2,2]]) // => ["a", "b", "c", 1, [2, 2]]

实现数组的扁平化:

function flat(arr) {
let rst = []
for (let i = 0; i < arr.length; ++i) {
if (arr[i].__proto__.constructor.name == 'Array') {
rst = rst.concat(flat(arr[i]))
} else {
rst = rst.concat(arr[i])
}
}
return rst
} flat(['a', ['b', 'c'], [1, [2,2]]]) // => [ "a", "b", "c", 1, 2, 2 ]

Array.prototype.includes(searchElement: any, fromIndex?: number): boolean

fromIndex (默认为 0)开始,判断一个数组是否含有 searchElement

const arr = ['a', 'b', 'c']
arr.includes('b') // => true
arr.includes('b', -1) // => false

Array.prototype.indexOf(searchElement: any, fromIndex?: number): number

fromIndex (默认为 0)开始,在数组中寻找 searchElemnt, 若找到了则返回该元素所在的索引,没找到则返回 -1

const subArr = [3]
const arr = [1, [2], subArr]
arr.indexOf(1) // => 0
arr.indexOf([2]) // => -1
arr.indexOf(subArr) // => 2

Array.prototype.lastIndexOf(searchElement: any, fromIndex?: number): number

indexOf() 是从前往后寻找,lastIndexOf() 则是从后往前寻找

Array.prototype.slice(begin?: number, end?: number): any[]

提取数组区间从 begin (默认为 0) 到 end (默认为数组长度) 之间的元素;返回提取的元素组成的数组

const arr = ['a', 'b', 'c', 'd', 'e']
arr.slice(2, 4) // => ["c", "d"]
arr.slice(-2) // => ["d", "e"]
arr // => ["a", "b", "c", "d", "e"]

Array.prototype.join(separator?: string): string

将数组中的元素用分隔符 separator (默认为 ,) 拼接;返回拼接后的字符串

const arr = ['a', 'b', 'c']
arr.join(',') // => 'a,b,c'
arr.join() // => 同上
arr // => ['a', 'b', 'c']

Array.prototype.toString(): string

',' 将数组元素拼接成字符串,因此 arr.toString()arr.joinarr.join(',') 这三者效果是相同的。

如果一个数组被当作字符串进行字符串拼接时,将隐式调用 toString() 方法

const arr = [1, 2]
'number: ' + arr // => "number: 1,2"

3.3.3 迭代方法

迭代方法会接收一个回调函数作为参数,数组中的元素会按照从前往后的顺序,依次作为参数传入回调函数中。常用的迭代方法有:forEach() every() some() filter() find() findIndex() map() reduce() reduceRight()

回调函数一般都会有如下三个参数:

  1. element 当前传入回调函数中的数组元素
  2. index 当前传入回调函数中的元素对应的索引
  3. array 调用迭代方法的数组实例

除了回调函数,迭代方法一般还会接收另一个参数 thisArg: any,它可作为回调函数中 this 的指向。

迭代方法不会改变数组,为了代码的可读性,请不要在回调函数中对数组进行修改操作。

Array.prototype.forEach(callback: Function, thisArg?: any): undefined

该方法是经典 for 循环的简便写法

let arr = [1]

arr.forEach(function () {
console.log(this === arr)
}) // => false arr.forEach(function () {
console.log(this === arr)
}, arr) // => true

Array.prototype.every(callback: Function, thisArg?: any): boolean

判断数组中的每一个元素,是否都能通过回调函数的测试;若每次回调函数返回的都是 truthy,那么该方法将返回 true,否则返回 false

[3, 5, 1].every(el => el > 0) // => true

只要有一次回调函数返回的是 falsy,那么 every() 方法将立即结束,返回 false

[1, 2, 3].every(el => {
console.log(el) // 分别打印 1 和 2
return el != 2
}) // => false

Array.prototype.some(callback: Function, thisArg?: any): boolean

判断数组是否存在能通过回调函数的测试的元素;只要有一次回调函数返回的是 truthy,那么方法立即结束,返回 true;若所有回调函数返回的都是 falsy,那么方法返回 false

['a', 'b', 'c'].some(el => el === 'b') // => true
[false, 0, '', null, undefined, NaN].some(el => el) // => false

Array.prototype.filter(callback: Function, thisArg?: any): any[]

根据测试条件过滤元素;如果回调函数返回 truthy,那么暂存当前传入回调函数的数组元素,方法结束后,这些暂存的元素将组成一个新的数组,作为方法的返回值

[1, 2, 3, 4, 5].filter(el => el % 2 === 0) // => [2, 4]

Array.prototype.map(callback: Function, thisArg?: any): any[]

方法返回一个数组,数组元素由回调函数每次返回的结果组成

[1, 2, 3].map(el => el * 2) // => [2, 4, 6]

Array.prototype.find(callback: Function, thisArg?: any): any | undefined

寻找符合测试条件的元素;在回调函数 第一次 返回 truthy 时,方法结束,返回此时轮到的数组元素;若回调函数始终不返回 truthy,方法将返回 undefined

[2, 8, 11, 5].find(el => el > 10) // => 11

Array.prototype.findIndex(callback: Function, thisArg?: any): number

寻找符合测试条件的元素的索引;在回调函数 第一次 返回 truthy 时,返回此时轮到的数组元素的索引;若回调函数始终不返回 truthy,方法将返回 -1

[2, 8, 11, 5].findIndex(el => el > 10) // => 2

Array.prototype.reduce(callback: Function, firstValue?: any): any

通过回调函数中的运算规则,计算 array[i]array[i + 1],计算的结果作为下一轮回调函数的参数,与 array[i + 2] 再进行计算……返回最后一次回调函数计算的结果。

在之前的迭代方法中,回调函数接收 3 个参数:callback(element?: any, index?: number, array?: any[]){}reduce() 中的回调函数将会接收四个参数:callback(accumulator?: any, element?: any, index?: number, array?: any[]) {}。可以看出 reduce() 多出一个 accumulator 参数,其译为 累积器,它的值是上一轮回调函数返回的值。

['x', 'y', 'z'].reduce((accumulator, element) => {
console.log(accumulator, element) // 将依次输出:1. x y 2. xy z
return accumulator + element
}) // => "xyz"

从上面的例子中还能看出:

  • accumulator 初始值为数组第一个元素值
  • 一个长度为 N 的数组,调用 reduce() 方法,回调函数执行 N -1 次

因此,单元素的数组调用 reduce() 方法,将始终返回第一个元素值

['first'].reduce(() => 'end') // => 'first'
['first'].reduce(a => a + ' end') // => 同上

reduce() 返回最后一次回调函数的结果

[[1, 1], [2, 2]].reduce((accumulator, element, index, arr) => {
accumulator = accumulator.concat(element)
return accumulator
}) // => [1, 1, 2, 2] [[1, 1], [2, 2]].reduce((accumulator, element, index, arr) => {
accumulator = accumulator.concat(element)
return index === arr.length - 1 ? 'end' : accumulator
}) // => 'end'

若指定了 reduce() 的第二个参数 firstValue: any,那么 accumulator 的初始值就是 firstValue,执行回调函数的次数也将多 1 次

[1, 2].reduce((accumulator, element) => {return accumulator + element}) // => 3
[1, 2].reduce((accumulator, element) => {return accumulator + element}, 97) // => 100;不是 99!

实现数组去重:

function deDuplication(arr) {
return arr.reduce((accumulator, el) => {
return accumulator.includes(el) ? accumulator : accumulator.concat(el)
}, [])
} deDuplication([1,1,2,3,2,3,3]) // => [1, 2, 3]

Array.prototype.reduceRight(callback: Function, firstValue?: any): any

reduce() 是从前往后运算,reduceRight() 是从后往前运算;其他方面一致

3.3.4 获得迭代器对象的方法

  • keys() 返回一个迭代器对象,其中包含所有元素的键
  • values() 返回一个迭代器对象,其中包含所有元素的值
  • entries() 返回一个迭代对象,包含所有数组元素及索引
let arr = ['a', 'b', 'c']

let keyIterator = arr.keys()
keyIterator.next() // => {value: 0, done: false}
keyIterator.next() // => {value: 1, done: false}
keyIterator.next() // => {value: 2, done: false}
keyIterator.next() // => {value: undefined, done: true} for (let value of arr.values()) {
console.log(value)
}
// =>
// 'a'
// 'b'
// 'c' console.log(...arr.entries()) // => [0, 'a'] [1, 'b'] [2, 'c']

3.3.5 数组实例的方法小结

  • 如果要查找符合某条件的元素,使用 find()
  • 如果要查找符合某件的一组元素,使用 filter()
  • 如果要确定符合某条件的元素的索引,使用 findIndex()
  • 如果要确定某元素的索引,使用 indexOf()lastIndexOf()
  • 如果要判断数组是否包含某个元素,使用 includes()
  • 如果要判断数组元素是否全都符合某条件,使用 every()
  • 如果要判断数组是否有符合某条件的元素,使用 some()
  • 具备拷贝功能的方法:concat()slice() 以及所有的迭代方法
  • 尾操作 push()pop() 比头操作 shift()unshift() 效率更高

4 通用性

数组的很多方法被设计成是通用的,也就是说非数组对象也可以调用数组的方法。下面是几个示例:

fill() 的通用用法:

let length = 3
Array.prototype.fill.call({length}, 'x') // => {0: "x", 1: "x", 2: "x", length: 3}
[].fill.apply({'length': 3}, ['x', 0, 3]) // 输出同上

map() 的通用用法:

// 输出字符串中每个字符的代码
[].map.call('ABC', el => el.charCodeAt()) // => [65, 66, 67]

includes() 的通用用法:

// 判断函数输入的参数中是否有某个值
function hasParam(arguments_, param) {
// 每个函数的 arguments 虽然是数组的外形,但它的类型不是 Array,因此不能直接调用数组的方法 includes
return [].includes.call(arguments_, param)
} function func() {
if (hasParam(arguments, 'GET')) {
console.log('输入中包含 GET')
} else {
console.log('输入中不包含 GET')
}
}

push() 的通用用法:

let obj = {}
Array.prototype.push.apply(obj, ['a', 'b']) // => 2(push() 返回数组长度)
obj // => {0: 'a', 1: 'b', length: 2}; 若对象中没有 length 属性将自动加上

5 数组和函数

声明函数时,可以通过 ...args 将待传入的参数包裹在一个数组中。其他地方使用 ... 可以展开一个数组或对象。

// 延时 ms 毫秒执行函数
Function.prototype.delay = function(ms) {
let that = this
return function(...args) {
setTimeout(() => {
that.apply(this, args)
// 或:that.call(that, ...args)
}, ms)
}
} function f(a, b) {
console.log( a + b );
} f.delay(1000)(1, 2) // 1000ms 后输出 3

6 Array、Set、Map、WeakMap、WeakSet

Array 用来表示实际应用中的一组 有序 的数据

Set 用来表示实际应用中一组 不会重复 的数据,即集合中个元素唯一

Map 可以视为 Object 的扩展,因为 Object 只允许键为 stringsymbol 类型,而 Map 的键可以是 任意类型

6.1 Map 实例的方法和属性

  • new Map(...items) 创建实例
  • Map.prototype.set(key: any, value: any): Map 存入键值对
  • Map.prototype.get(key: any): any 获取 key 对应的值,若不存在返回 undefined
  • Map.prototype.has(key: any): boolean 判断是否存在相应的键
  • Map.prototype.delete(key): boolean 根据键删除键值对,若不存在键 key,将返回 false,否则返回 true
  • Map.prototype.clear() 清空实例的元素
  • Map.prototype.size: number 返回当前元素个数
  • Map.prototype.forEach((key, value, map) => {...}) 遍历实例中的元素
  • map 也有 map.keys() map.values() map.entries()

通过 new Map(Object.entries(o))Object.fromEntires(map) 可以实现 MapObject 的相互转换

6.2 Set 实例的方法和属性

  • new Set(source: Iterable)
  • Set.prototype.add(value: any): Set
  • Set.prototype.delete(value: any): boolean
  • Set.prototype.has(value: any): boolean
  • Set.prototype.clear()
  • Set.prototype.size: number

6.3 WeakMapWeakSet

现有如下代码:

let obj = {1: 'one'}
let map = new Map()
// 将 obj 作为 map 的一个键
map.set(obj, '') // 覆盖引用
obj = null map.keys() // => [{value: {1: 'one'}}]

null 赋值给引用 obj,我们预期的是对象 {1: 'one'} 能够随之被“垃圾回收”清理掉,然而因为它存在于 map 中,因此会被阻止清理——我们仍然可以通过 map.keys() 获取到 {1: 'one'}。也就是说 {1: 'one'} 将始终存在内存中,这不是我们预期的,在大型项目当中,这会导致大量不再用到的对象始终存在内存中。

WeakMapWeakSet 可以解决这一问题。

WeakMap 的实例只有以下方法:

  • WeakMap.prototype.get(key: object): any
  • WeakMap.prototype.set(key: object, value: any): WeakMap
  • WeakMap.prototype.has(key: object): boolean
  • WeakMap.prototype.delete(key: object): boolean

WeakSet 的实例只有以下方法:

  • WeakSet.prototype.add(value: object): WeakSet
  • WeakSet.prototype.delete(value: object): boolean
  • WeakSet.prototype.has(value: object): boolean

WeakMap 只允许对象作为键,WeakSet 只允许对象作为集合元素;当该对象在其他地方不能被访问时,WeakMapWeakSet 中的对象将会随之被清理掉。

// 该代码直接在控制台中执行看不到效果
// 需要放在文件中执行,才能看到对象自动被清理
let obj = {}
let weakMap = new WeakMap()
weakMap.set(obj, '')
console.log(weakMap) // => [[{}: '']] obj = null
console.log(weakMap) // => []