深入浅出JS红宝书 - 集合引用类型(上)

924 阅读9分钟

集合引用类型

Object

显式创建 Object 的实例

  • 使用 new 操作符和 Object 构造函数

    let person = new Object();
    person.name = "dyc";
    person.age = 20;
    
  • 使用对象字面量( object literal )表示法

    let person = {
      'name': "dyc",
      'age': 20,
      5: true   // 注意,数值属性会自动转换为字符串
    };
    

[注]  在使用对象字面量表示法定义对象时,并不会实际调用 Object 构造函数。

[注]  在最后一个属性后面加上逗号在非常老的浏览器中会导致报错

存取

  • 点语法
  • 中括号

[注]  使用中括号的主要优势就是可以通过变量访问属性

Array

创建数组

  • 使用Array构造函数

    // let colors = new Array();
    let colors = new Array(3);     // 创建一个包含3个元素的数组
    let names = new Array("Greg"); // 创建一个只包含一个元素,即字符串"Greg"的数组// 也可以省略new操作符
    let colors = Array(3);     // 创建一个包含3个元素的数组
    let names = Array("Greg"); // 创建一个只包含一个元素,即字符串"Greg"的数组
    
  • 使用数组字面量(array literal)表示法

    let colors = ["red", "blue", "green"];  // 创建一个包含3个元素的数组
    let names = [];                         // 创建一个空数组
    let values = [1,2,];                    // 创建一个包含2个元素的数组
    

    [注]  与对象一样,在使用数组字面量表示法创建数组不会调用Array构造函数。

  • Array构造函数还有两个ES6新增的用于创建数组的静态方法

    • from()

      • 字符串会被拆分为单字符数组

        console.log(Array.from("dyc")); // ["d", "y", "c"]
        
      • 将集合 new Map() 和映射 new Set() 转换为一个新数组

        const m = new Map().set(1, 2)
                           .set(3, 4);
        const s = new Set().add(1)
                           .add(2)
                           .add(3)
                           .add(4);
        ​
        console.log(Array.from(m)); // [[1, 2], [3, 4]]
        console.log(Array.from(s)); // [1, 2, 3, 4]
        
      • 对现有数组执行浅复制

        const a1 = [1, 2, 3, 4];
        const a2 = Array.from(a1);
        ​
        console.log(a1);    // [1, 2, 3, 4]
        alert(a1 === a2);   // false
        
      • 可以使用任何可迭代对象

        const iter = {
          *[Symbol.iterator]() {
            yield 1;
            yield 2;
            yield 3;
            yield 4;
          }
        };
        console.log(Array.from(iter)); // [1, 2, 3, 4]
        
      • arguments对象可以被轻松地转换为数组

        function getArgsArray() {
          return Array.from(arguments);
        }
        console.log(getArgsArray(1, 2, 3, 4)); // [1, 2, 3, 4]
        
      • from()也能转换带有必要属性的自定义对象

        const arrayLikeObject = {
          0: 1,
          1: 2,
          2: 3,
          3: 4,
          length: 4
        };
        console.log(Array.from(arrayLikeObject)); // [1, 2, 3, 4]
        // OHHHHHHHHHHHHH!
        
      • 还接收第二个可选的映射函数参数

        const a1 = [1, 2, 3, 4];
        const a2 = Array.from(a1, x => x**2);
        const a3 = Array.from(a1, function(x) {return x**this.exponent}, {exponent: 2});
        console.log(a2);  // [1, 4, 9, 16]
        console.log(a3);  // [1, 4, 9, 16]
        
    • of()

      • 可以把一组参数转换为数组

        console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
        console.log(Array.of(undefined));  // [undefined]// 这个方法用于替代在ES6之前常用的 Array.prototype.slice.call(arguments)
        

    [注]  对于 undefined, null 的包容性

    Array.of(undefined)   // [undefined]
    Array.of(null)        // [null]
    Array.from(undefined) // TypeError: Array.from requires an array-like object - not null or undefined
    Array.from(null)      // TypeError: Array.from requires an array-like object - not null or undefined
    

数组空位

  • ES6新增方法普遍将这些空位当成存在的元素,只不过值为undefined

    const options = [,,]; // 创建包含2个元素的数组
    console.log(options.length);   // 2
    console.log(options);          // [empty × 2]/* for of */
    for (const option of options) {
      console.log(option === undefined);
    }
    // true
    // true/* Array.form */
    const a = Array.from(options)
    for (const val of a) {
      console.log(val === undefined);
    }
    // true
    // true// ... 等等
    
  • ES6之前的方法则会忽略这个空位

    • map()会跳过空位置
    const options = [1,,,,5];
    console.log(options.map(() => 6));  // [6, undefined, undefined, undefined, 6]
    
    • join()视空位置为空字符串
    const options = [1,,,,5];
    console.log(options.join('-'));     // "1----5"
    

[注]  由于行为不一致和存在性能隐患,因此实践中要避免使用数组空位。如果确实需要空位,则可以显式地用undefined值代替。

数组索引

  • 数组中元素的数量保存在length属性中

    • 通过修改length属性,可以从数组末尾删除或添加元素

      • 数组一开始有3个值。将length设置为2,就删除了最后一个

        let colors = ["red", "blue", "green"]; // 创建一个包含3个字符串的数组
        colors.length = 2;
        alert(colors[2]);  // undefined
        
      • 将length设置为大于数组元素数的值,则新添加的元素都将以undefined填充

        let colors = ["red", "blue", "green"];  // 创建一个包含3个字符串的数组
        colors.length = 4;
        alert(colors[3]);  // undefined
        
    • 使用length属性可以方便地向数组末尾添加元素

      let colors = ["red", "blue", "green"];  // 创建一个包含3个字符串的数组
      colors[colors.length] = "black";        // 添加一种颜色(位置3)
      colors[colors.length] = "brown";        // 再添加一种颜色(位置4)
      

[注]  请避免在Vue2.x中使用 length 来改变数组,否则无法触发动态响应

检测数组

  • instanceof

    if (value instanceof Array){ /* 操作数组 */ }
    
  • Array.isArray()

    if (Array.isArray(value)){ /* 操作数组 */ }
    

迭代器方法

  • 在ES6中,Array的原型上暴露了3个用于检索数组内容的方法

    • keys() - 返回数组索引的迭代器
    • values() - 返回数组元素的迭代器
    • entries() - 返回索引/值对的迭代器
    const a = ["foo", "bar", "baz", "qux"];
    ​
    // 因为这些方法都返回迭代器,所以可以将它们的内容
    // 通过Array.from()直接转换为数组实例
    Array.from(a.keys());     // [0, 1, 2, 3]
    Array.from(a.values());   // ["foo", "bar", "baz", "qux"]
    Array.from(a.entries());  // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]]
    

复制和填充方法

  • 批量复制方法copyWithin()
  • 填充数组方法fill()

转换方法

  • toLocaleString()

    /**
     * toLocaleString() 与 toString() 唯一的区别是,
     * 为了得到最终的字符串,会调用数组每个值的 toLocaleString() 方法,而不是 toString() 方法。
     */
    let person1 = {
      toLocaleString() { return "dyc"; },
      toString() { return "fxddf"; }
    };
    let person2 = {
      toLocaleString() { return "前端王"; },
      toString() { return "飞翔的豆腐"; }
    };
    ​
    let people = [person1, person2];
    people;                   // fxddf,飞翔的豆腐
    people.toString();        // fxddf,飞翔的豆腐
    people.toLocaleString();  // dyc,前端王
    
  • toString() - 返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串

    [1, undefined, null, 2].toString()  // '1,,,2'
    
  • valueOf() - 返回的还是数组本身

    [1, undefined, null,  2].valueOf()  // [1, undefined, null,  2]
    
  • join()

    let colors = ["red", "green", "blue"];
    colors.join()           // red,green,blue
    colors.join(undefined)  // red,green,blue
    colors.join(",");       // red,green,blue
    colors.join("||");      // red||green||blue
    

栈方法

  • 后进先出(LIFO,Last-In-First-Out)

    • 插入(称为推入,push)

      const arr = [1, 2, 3]
      arr.push(4, 5, 6, 7)
      arr // [1, 2, 3, 4, 5, 6, 7]
      
    • 删除(称为弹出,pop)

      const arr = [1, 2, 3]
      let item = arr.pop() 
      item  // 3
      arr   // [1, 2]
      

队列方法

  • 先进先出(FIFO,First-In-First-Out)

    • 正向队列,push()在数据末尾添加,shift()开头取得数据

      const arr = [1, 2, 3]
      let item = arr.shift() 
      item  // 1
      arr   // [2, 3]
      
    • 反向队列,pop()在数据末尾删除,unshift()开头添加新数据

      const arr = [4, 5, 6, 7]
      arr.unshift(1, 2, 3)
      arr // [1, 2, 3, 4, 5, 6, 7]
      

排序方法

  • reverse()

    const values = [1, 2, 3, 4, 5];
    values.reverse();
    values // [5, 4, 3, 2, 1]
    
  • sort()

    • 即使5小于10,但字符串"10"在字符串"5"的前头,所以10还是会排到5前面。很明显,这在多数情况下都不是最合适的

      const value = [0, 15, 5, 10, 1];
      value.sort();
      value // [0, 1, 10, 15, 5]
      
    • sort()方法可以接收一个比较函数,用于判断哪个值应该排在前面

      const value = [0, 15, 5, 10, 1];
      value.sort((a, b) => a - b);
      value // [0, 1, 5, 10, 15]
      

操作方法

  • concat()

    • 默认会强制打平数组 - (一层)

      const arr = [0].concat(1, 2, [3, 4])
      arr // [0, 1, 2, 3, 4]const _arr = [0].concat(1, 2, [3, 4, [5, 6]])
      _arr // [0, 1, 2, 3, 4, [5, 6]]
      
    • 修改[Symbol.isConcatSpreadable]来控制是否打平数组

      let arr = ["1", "2", "3"];
      let _arr = ["5", "6"];
      _arr[Symbol.isConcatSpreadable] = false;
      ​
      let __arr = ["5", "6"];
      __arr[Symbol.isConcatSpreadable] = true;
      ​
      // 强制不打平
      let arr2 = arr.concat("4", _arr);
      ​
      // 强制打平
      let arr3 = arr.concat("4", __arr);
      ​
      arr;   //  ['1', '2', '3']
      arr2;  //  ['1', '2', '3', '4', ['5', '6']]
      arr3;  //  ['1', '2', '3', '4', '5', '6']
      
  • slice()

    let arr = [1, 2, 3, 4, 5]
    let arr2 = arr.slice(1)     // 1个参数,index起始到最右
    let arr3 = arr.slice(1, 3)  // 2个参数,index含左不含右
    ​
    arr  // [1, 2, 3, 4, 5]
    arr2 // [2, 3, 4, 5]
    arr3 // [2, 3]
    

    [注]  如果slice()的参数有负值,那么就以数值长度加上这个负值的结果确定位置。比如,在包含5个元素的数组上调用slice(-2,-1),就相当于调用slice(3,4)。如果结束位置小于开始位置,则返回空数组。

  • splice()

    • 或许最强大的数组方法就属splice()
    • splice()的主要目的是在数组中间插入元素
    // 删除
    // 比如 splice(0, 2) 会删除前两个元素。
    let arr = [1, 2, 3]
    let remove = arr.splice(0, 2)
    arr    // [3]
    remove // [1, 2]
    
    // 插入
    // 传3+个参数:开始位置、0(要删除的元素数量)和要插入的元素 [和要插入的元素] [和要插入的元素]...
    // 比如 splice(1, 0, 'string', [9, 10]) 会在位置 1(的左边)插入为目标元素
    let arr = [1, 2, 3]
    arr.splice(1, 0, 'string', [9, 10])
    arr // [1, 'string', [9, 10], 2, 3]
    
    // 替换
    // 传3+个参数:开始位置、1(要删除的元素数量)和要替换的元素 [和要替换的元素] [和要替换的元素]...
    // 比如 splice(1, 0, 'string', [9, 10]) 会在位置 1 处替换为目标元素
    let arr = [1, 2, 3]
    arr.splice(1, 1, 'string', [9, 10])
    arr // [1, 'string', [9, 10], 3]
    

搜索和位置方法

  • 严格相等

    • indexOf()
    • lastIndexOf()
    • includes()
    const obj = { a: 1 }
    const arr = [{ a: 1 }]
    const Arr = [obj]
    ​
    // 会使用全等(`===`)比较
    arr.includes(obj) // false
    Arr.includes(obj) // true
    
  • 断言函数

    [注]  接收3个参数:元素、索引和数组本身。其中元素是数组中当前搜索的元素,索引是当前元素的索引,而数组就是正在搜索的数组。断言函数返回真值,表示是否匹配。

    • find()

      // 返回第一个匹配的元素
      [
        { a: 1 },
        { a: 2 },
        { a: 4 }
      ].find((element, index, array) => element.a < 3) // { a: 1 }
      
    • findIndex()

      // 返回第一个匹配的元素
      [
        { a: 1 },
        { a: 2 },
        { a: 3 }
      ].find((element, index, array) => element.a < 3) // 0
      

迭代方法

  • every()

    // 对数组每一项都运行传入的函数,如果对每一项函数都返回true,则这个方法返回true。
    [1, 2, 3].every((item, index, arr) => item < 4) // true
    
  • some()

    // 对数组每一项都运行传入的函数,如果有一项函数返回true,则这个方法返回true。
    [1, 3, 4].some((item, index, arr) => item < 2) // true
    
  • filter()

    // 对数组每一项都运行传入的函数,函数返回true的项会组成数组之后返回。
    let arr = [1, 2, 4]
    let _arr = arr.filter((item, index, array) => item < 3)
    arr  // [1, 2, 4]
    _arr // [1, 2]
    
  • forEach()

    // 对数组每一项都运行传入的函数,没有返回值。
    // 本质上,forEach()方法相当于使用for循环遍历数组。
    let arr = [1, 2, 4]
    let _arr = arr.forEach((item, index, array) => item * 2)
    arr  // [1, 2, 4]
    _arr // undefined
    
  • map()

    // 对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。
    let arr = [1, 2, 4]
    let _arr = arr.map((item, index, array) => item * 2)
    arr  // [1, 2, 4]
    _arr // [2, 4, 8]
    

归并方法

  • reduce()

    // 接收两个参数:对每一项都会运行的归并函数,以及可选的以之为归并起点的初始值。
    // 函数接收4个参数:上一个归并值、当前项、当前项的索引和数组本身。
    // 这个函数返回的任何值都会作为下一次调用同一个函数的第一个参数。
    // 如果没有给这两个方法传入可选的第二个参数(作为归并起点值),则第一次迭代将从数组的第二项开始
    // 因此传给归并函数的第一个参数是数组的第一项,第二个参数是数组的第二项。
    let arr = [1, 2, 3, 4, 5]
    let sum = arr.reduce((prev, cur, index, array) => prev + cur )
    arr // [1, 2, 3, 4, 5]
    sum // 15
    
  • reduceRight()

    [注]  顺序从右至左,其余跟reduce()一致