• 复习 Array,重学 JavaScript


    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 换成 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.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) // => []
    
  • 相关阅读:
    Knight Moves
    Knight Moves
    Catch him
    Catch him
    Linux查看硬件信息以及驱动设备的命令
    23种设计模式彩图
    Android开发指南-框架主题-安全和许可
    Android启动组件的三种主流及若干非主流方式
    ACE在Linux下编译安装
    void及void指针含义的深刻解析
  • 原文地址:https://www.cnblogs.com/gu13/p/08-14-18.html
Copyright © 2020-2023  润新知