【JS提升】ES6/ES7数组方法

1. Array.prototype.copyWithin(ES6)

  1. 概念

    复制数组的一部分到同一数组中的另一个位置,返回原数组,不会改变原数组的长度

    原理:复制元素集合 => 全选模板元素 => 粘贴

  2. 写法

    arr.copyWithin(target[, start[, end]])

    target:将复制的粘贴到索引位置

    start:开始复制元素的起始位置

    end:开始复制元素的结束位置

    const arr = [1, 2, 3, 4, 5];
    const newArr = arr.copyWithin(0, 3, 4);
    console.log(newArr); //[ 4, 2, 3, 4, 5 ]
    
  3. 描述

    1. 范围:[start, end)

    2. target:从…开始替换

    3. end > length - 1 取到末尾

      const newArr = arr.copyWithin(0, 3, 10);
      console.log(newArr); //[ 4, 5, 3, 4, 5 ]
      
    4. target > length -1 不发生任何替换

      const newArr = arr.copyWithin(5, 3, 4);
      console.log(newArr); //[ 1, 2, 3, 4, 5 ]
      
    5. 当target > start 正常替换

      const newArr = arr.copyWithin(3, 1, 3);
      console.log(newArr); //[ 1, 2, 3, 2, 3 ]
      
    6. start或end是负数,则:[start+length, end+length)

      const newArr = arr.copyWithin(0, -3, -1); //0 2 4
      console.log(newArr); //[ 3, 4, 3, 4, 5 ]
      
    7. 如果没有start,取整个数组的元素

      copyWithin 是不改变数组长度的,超出部分截掉

      const newArr = arr.copyWithin(3);
      console.log(newArr); //[ 1, 2, 3, 1, 2 ]
      
    8. 如果没有end,则取到末尾

      const newArr = arr.copyWithin(5, 3);
      console.log(newArr); //[ 1, 4, 5, 4, 5 ]
      
    9. 返回的是原数组引用

      console.log(newArr == arr); // true
      
  4. 拷贝对象/数组

    1. 拷贝数组(元素值是引用的情况)

      const arr = [{
      id: 1,
      name: 'Tom1'
      },{
      id: 2,
      name: 'Tom2'
      },{
      id: 3,
      name: 'Tom3'
      },{
      id: 4,
      name: 'Tom4'
      },{
      id: 5,
      name: 'Tom5'
      }];
      
      const target1 = arr[0];
      const target3 = arr[2];
      
      const newArr = arr.copyWithin(0, 2, 3);
      
      const target2 = arr[0];
      
      console.log(target1 === target2); // false
      console.log(target3 === target1); // true
      // 直接把 arr[2] 的引用复制了一份到 arr[0],浅拷贝
      
    2. 拷贝对象

      // this 不一定非要指向一个数组,也可以指向一个对象
      var obj = {
          0: 1,
          1: 2,
          2: 3,
          3: 4,
          4: 5,
          length: 5
      }
      const newObj = [].copyWithin.call(obj, 0, 2, 4);
      console.log(newObj); // { '0': 3, '1': 4, '2': 3, '3': 4, '4': 5, length: 5 }
      
      console.log(obj === newObj) // true 是同一个引用
      
  5. 位移

    1. 有符号左/右位移(<</>>)

      备注:二进制码最高位0是正数,1是负数。在JS里面常用 >> 0 保证字符是数字,>>> 0 用于保证数字是正整数。

      正数高位补0,负数高位补1

      1 >> 2; // 10 => 2
      // 不知道是不是数字的情况,保证是一个数字,若是数字则保持不变,若不是则会变为0
      undefined >> 0;  // 0
      'abc' >> 0;  // 0
      
    2. 无符号右位移(>>>)

      不管正负数,高位一律补0

      xxx.length >>> 0; // 已知是数字的情况,保证数字是正整数
      
  6. 重写

    Array.prototype.myCopyWithin = function(target){
    // 判断没有this指向的情况
    if(this == null){
        throw new TypeError('this is null or not defined');
    }
    // 将 this 用 Object 包装一下,让其变为引用值,因为 this有可能是 原始值
    var obj = Object(this),
        len = obj.length >>> 0, // 保证 是一个正整数
        start = arguments[1],
        end = arguments[2],
        count = 0,
        dir = 0;
    
    // 保证 target 是一个整数
    target = target >> 0;
    // 判断 target 是正数还是负数
    target = target < 0 ?
                Math.max(len + target, 0) : // 取最大
                Math.min(target, len);  // 取最小
    // 先判断 start 是否存在,存在则保证其为整数
    start = start ? start >> 0 : 0;
    start = start < 0 ?
                Math.max(len + start, 0) :
                Math.min(start, len);
    end = end ? end >> 0 : len;
    end = end < 0 ?
                Math.max(len + end, 0) :
                Math.min(end, len);
    count = Math.min(end - start, len - target)
    if(start < target && target < (start + count)) {
        dir = -1;
        start += count - 1;
        target += count - 1;
    }
    while(count > 0) {
        if(start in obj) {
        obj[target] = obj[start];
        } else {
        delete obj[target];
        }
        start += dir;
        target += dir;
        count--;
    }
    return obj;
    }
    
    const arr = [1, 2, 3, 4, 5];
    const newArr = arr.myCopyWithin(0, 3, 4);
    console.log(newArr);
    

2. generator与iterator(ES6)

  1. 铺垫

    1. 七种遍历数组的方法

      1. forEach -> 普通的数组遍历方法 -> 是对 es3 for 循环的优化
      2. map -> 映射 -> 每一次遍历,返回一个数组元素 -> 返回一个新的数组
      3. filter -> 过滤 -> 每一次遍历,返回 boolean,来决定当前元素是否纳入新的数组中
      4. reduce -> 归纳 -> 每一次遍历,将当前元素收归到容器中
      5. reductRight -> reduce的反向操作
      6. every -> 判定是否所有元素都符合一个条件
      7. some -> 判定是否有某一个或多个符合一个条件
         

      底层都是 for 循环实现

    2. 遍历与迭代

      遍历 -> 一次性对数组中每一个元素进行查询和处理

      迭代 -> 遍历的过程是可控的(遍历的过程可停止,也可继续),手动的控制遍历流程

      产品迭代 -> 人为控制的产品升级与扩展 -> manual control

      遍历是迭代一次又一次的实现,迭代是遍历的底层方法

  2. 概念

    生成器是一个函数

    迭代器是由生成器函数执行后返回的一个带有next方法的对象

    生成器对迭代的控制是由 yield 关键字来执行的

  3. 写法

    function * generator(){
    // 每次 yield 就是产出一个迭代器对象,
    // 包含了value: 当前yield的值,done:当且生成器里需要迭代的值完成了没有
    yield 'name: tom'; 
    yield 'age: 18';
    yield 'hobby: running';
    return 'js'
    }
    
    const iterator = generator();
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    
    const arr = ['name: tom', 'age: 18', 'hobby: running']; // 疑问:迭代器是怎么知道迭代完的呢? 具体请看迭代器实现
    function * gen(arr){                                   
    // 对可迭代对象进行遍历循环
    for(var i = 0; i < arr.length; i++){
        // 通过 yield 产出一个值,并且停止下来
        yield arr[i];
    }
    return 'js'
    }
    const iterator = gen();
    // 每次迭代都是手动控制
    // 每次遍历都是迭代的过程
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    
  4. 迭代器实现

    const arr = ['name: tom', 'age: 18', 'hobby: running'];
    
    // 利用闭包的原理实现生成器函数
    function gen(arr) {
    var nextIndex = 0;
    
    return {
        next: function(){
        return nextIndex < arr.length ? 
                {value: arr[nextIndex++], done: false} :
                {value: arr[nextIndex++], done: true}
        }
    }
    }
    
    const iterator = gen();
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    

3. Array.prototype.entries(ES6)

  1. 概念

    返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对

  2. 写法

    arr.entries()

    返回值:一个新的 Array 迭代器对象。Array Iterator是对象,它的原型(proto:Array Iterator)上有一个next方法,可用用于遍历迭代器取得原数组的[key,value]。

    const arr = ['a', 'b', 'c'];
    
    const it = arr.entries(); 
    console.log(it); //Object [Array Iterator] -> 说明可以调用 next
    
    console.log(it.next());
    // { value: [ 0, 'a' ], done: false }
    
    for (let c of it) {
        const [i, v] = c;
        console.log(i, v);
    }
    // 0 a
    // 1 b
    // 2 c
    
  3. 迭代对象实现

    1. 普通遍历

      var o = {
          a: 1,
          b: 2,
          c: 3
      }
      
      // for of 是用来遍历可迭代对象的
      // for of 运行原理,Symbol.iterator,调用iterator迭代器里的next进行一步步的遍历
      // 并直接返回 {value:...,done:...} 的value给你
      for(let v of arr){
          console.log(v); // 报错,因为对象没有迭代器接口
      }
      
    2. 部署迭代器接口

      1. 方法一:对象上部署

        // 类数组(模拟)
        var o = {
            0: 1,
            1: 2,
            2: 3,
            length: 3,
            // 通过[Symbol.iterator]判断是否是一个可迭代对象,并部署接口
            [Symbol.iterator]: Array.prototype[Symbol.iterator]
        }
        
        for(let v of it){
            console.log(v); // 1 2 3
        }
        
      2. 方法二:原型上部署

        // 写在原型上也是可以的
        Object.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
        
        for(let v of o) {
            console.log(v); // 1 2 3
        }
        
      3. 方法三:转成数组

        // 将 o 转成数组
        for(let v of Array.from(o)) {
            console.log(v); // 1 2 3
        }
        
  4. next运行实现

    一次迭代完,迭代完就停止

    const arr = [1, 2, 3, 4, 5];
    const it = arr.entries();
    
    var newArr = []; // 保留 value 的具体信息
    for(var i = 0; i < arr.length + 1; i++){
        var item = it.next();
        
        !item.done && (newArr[i] = item.value)
    }
    console.log(newArr); // [ [ 0, 1 ], [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ]
    
  5. 二维数组排序

    const newArr = [
        [56, 23],
        [56, 34, 100, 1],
        [123, 234, 12]
    ]
    
    function sortArr (arr){
    var _it = arr.entries(), // 拿到键/值构成的数组
        _doNext = true; // 是否要进行下一次迭代
    while(_doNext){
        var _r = _it.next(); // 对数组进行迭代
         
        if(!_r.done){ // 判断迭代是否结束
        _r.value[1].sort(function(a, b){ // 拿到迭代抽取的值进行排序
            return a - b;
        });
        _doNext = true;
        } else {
        _doNext = false;
        }
    }
    return arr;
    }
    console.log(sortArr(newArr)); // [ [ 23, 56 ], [ 1, 34, 56, 100 ], [ 12, 123, 234 ] ]
    

4. Array.prototype.fill(ES6)

  1. 概念

    用于将一个值填充到数组中从起始索引到终止索引内的全部元素。不包括终止索引。

  2. 写法

    arr.fill(value[, start[, end]])

    value:用来填充数组元素的值。可选,默认全部填充undefined

    start:起始索引,默认值为0。可选,默认0

    end:终止索引,默认值为 this.length。可选,arr.length

    返回值:原数组引用

    const arr = [1, 2, 3, 4, 5];
    
    // 填充 a 至 2 到 4 位
    console.log(arr.fill('a', 2, 4));
    // [1, 2, 'a', 'a', 5]
    
  3. 描述

    1. 范围:[start, end)

    2. 返回值是数组引用,修改原数组

      const newArr = arr.fill('a', 2, 4);
      console.log(arr === newArr); // true
      
    3. 没有end则取至末尾

      console.log(arr.fill('a', 1));
      // [1, 'a', 'a', 'a', 'a']
      
    4. 没有start则全部替换

      console.log(arr.fill('a'));
      // ['a', 'a', 'a', 'a', 'a']
      
    5. start/end为负数的情况 [start+length, end+length)

      console.log(arr.fill('e', -4, -2)); // e 1 3
      // [ 1, 'e', 'e', 4, 5 ]
      
    6. 没有参数, 则全部覆盖为undefined

       console.log(arr.fill()); 
      // [ undefined, undefined, undefined, undefined, undefined ]
      
    7. start === end 的情况,则不变

       console.log(arr.fill('f', 1, 1)); // [1,1) 空集
      // [ 1, 2, 3, 4, 5 ]
      
    8. start/end是非数, 默认取0

       console.log(arr.fill('g', 'a', 'b')); 
      // [ 1, 2, 3, 4, 5 ]
      console.log(arr.fill('g', 1, 'b')); // [1,0) 空集
      // [ 1, 2, 3, 4, 5 ]
      console.log(arr.fill('g', 'a', 4)); // [0,4)
      // [ 'g', 'g', 'g', 'g', 5 ]
      
    9. start/end是NaN, 默认取0

      console.log(arr.fill('h', NaN, NaN));
      // [ 1, 2, 3, 4, 5 ]
      console.log(arr.fill('h', 1, NaN)); // [1,0) 空集
      // [ 1, 2, 3, 4, 5 ]
      console.log(arr.fill('h', NaN, 4)); // [0,4)
      // [ 'h', 'h', 'h', 'h', 5 ]
      
    10. start/end是null, 默认取0

      console.log(arr.fill('i', null, null));
      // [ 1, 2, 3, 4, 5 ]
      console.log(arr.fill('i', 1, null)); // [1,0) 空集
      // [ 1, 2, 3, 4, 5 ]
      console.log(arr.fill('i', null, 4)); // [0,4)
      // [ 'i', 'i', 'i', 'i', 5 ]
      
    11. start/end是undefined, 则看作没有填写

      console.log(arr.fill('j', undefined, undefined)); // (0,length] 
      // [ 'j', 'j', 'j', 'j', 'j' ]
      console.log(arr.fill('j', 1, undefined)); // (1,length]
      // [ 1, 'j', 'j', 'j', 'j' ]
      console.log(arr.fill('j', undefined, 4)); // (0, 4)  这里undefined看作0
      // [ 'j', 'j', 'j', 'j', 5 ]
      
    12. 对象调用的情况

      写了{length:3},fill会根据你的length去填充相应的数量的元素,值为你填入的value

      const newObj = Array.prototype.fill.call({ length: 3 }, 4);
      console.log(newObj); // { '0': 4, '1': 4, '2': 4, length: 3 }
      

      发现:此方法可以用来创建一个类数组

  4. 创建类数组方法

    数组转类数组

    function makeArrayLike(arr) {
        var arrLike = {
            length: arr.length,
            push: [].push,
            splice: [].splice
        }
    
        arr.forEach(function(item, index) {
            [].fill.call(arrLike, item, index, index + 1);
        })
        return arrLike;
    }
    
    var newObj = makeArrayLike(['a', 'b', 'c', 'd'])
    console.log(newObj);
    // {
    //     '0': 'a',
    //     '1': 'b',
    //     '2': 'c',
    //     '3': 'd',
    //     length: 4,
    //     push: [Function: push],
    //     splice: [Function: splice]
    //   }
    
    // 也可以是引用值
    var obj2 = makeArrayLike([{
        id: 1,
        name: 'Tom1'
    }, {
        id: 2,
        name: 'Tom2'
    }, {
        id: 3,
        name: 'Tom3'
    }, {
        id: 4,
        name: 'Tom4'
    }, {
        id: 5,
        name: 'Tom5'
    }]);
    console.log(obj2);
    // {
    //     '0': { id: 1, name: 'Tom1' },
    //     '1': { id: 2, name: 'Tom2' },
    //     '2': { id: 3, name: 'Tom3' },
    //     '3': { id: 4, name: 'Tom4' },
    //     '4': { id: 5, name: 'Tom5' },
    //     length: 5,
    //     push: [Function: push],
    //     splice: [Function: splice]
    //   }
    
  5. 重写

    Array.prototype.myFill = function(){
        var value = arguments[0] || undefined,
            start = arguments[1] >> 0, // 不符合条件的都设为0,保证为数字
            end = arguments[2];
        // 判断没有this指向的情况
        if(this == null){
            throw new TypeError('this is null or not defined');
        }
        var obj = Object(this), // 先包装成对象,如果不是对象则没有意义
            len = obj.length >>> 0;
        start = start < 0 ?
                Math.max(len + start, 0) :
                Math.min(start, len);
        end = end === undefined ?
                        len:
                        end >> 0;
        end = end < 0 ?
                Math.max(len + end, 0) :
                Math.min(end, len);
        while(start < end){
            obj[start] = value;
            start++;
        }
        return obj;
    }
    var arr = [1, 2, 3, 4, 5];
    const newArr = arr.myFill('a', 2, 4);
    console.log(newArr); // [ 1, 2, 'a', 'a', 5 ]
    

5. Array.prototype.find(ES6)

  1. 概念

    返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

  2. 写法

    arr.find(callback[, thisArg])

    callback:在数组每一项上执行的函数,接收 element/index/array 3个参数

    element:当前遍历到的元素。

    index:当前遍历到的索引。

    array:数组本身。

    thisArg:执行回调时用作 this 的对象。

    返回值:数组中第一个满足所提供测试函数的元素的值,否则返回 undefined。

    const arr = [1, 2, 3, 4, 5];
    
    // 返回第一个满足条件的数组元素
    const item = arr.find(item => item > 3);
    console.log(item); // 4
    
  3. 描述

    1. 返回第一个满足条件的数组元素

      const item = arr.find(item => item > 3);
      console.log(item); // 4
      
      
    2. 如果没有一个元素满足条件,返回 undefined

      const item2 = arr.find(function(item) {
          return item > 5
      })
      console.log(item2); // undefined
      
    3. 数组元素是引用值的情况

      const arr = [{
          id: 1,
          name: '张三'
      }, {
          id: 2,
          name: '李四'
      }, {
          id: 3,
          name: '王五'
      }];
      
      const item = arr.find(item => item.name === '李四');
      console.log(item); // { id: 2, name: '李四' }
      
      // 返回的元素和数组对应下标的元素是同一个引用 
      console.log(item === arr[1]); // true
      
      
    4. 参数问题

      回调函数:当前遍历的元素、当前遍历的元素对应的下标、当前的数组

      回调返回值:boolean,遍历在某一次调用回调后返回true,停止

      第二个参数:

      更改回调函数内部的 this 指向

      非严格模式环境下,this->window

      严格模式下,不传入第二个参数,this为undefined,与严格模式规定相统一

      const item2 = arr.find(function(item, index, arr){
          console.log(item, index, arr); 
          // { id: 1, name: '张三' } 0 [ { id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' } ]
          // ...
          console.log(this); // {a: 1}
      }, {a: 1})
      
    5. 回调函数的返回值是 布尔类型,第一个返回 true 的对应数组元素作为 find 的返回值

      const item = arr.find(function(item){
          return item.id > 1; // boolean
      })
      console.log(item); 
      
    6. 稀松数组

      稀松数组 -> 数组元素与元素之间是有空隙的

      const arr = Array(5);
      console.log(arr); // empty * 5
      
      arr[0] = 1;
      arr[2] = 3;
      arr[4] = 5;
      // [1, , 3, , 5]  稀疏数组 -> 数组元素与元素之间是有空隙的
      
      
      // find 会遍历稀疏数组的空隙 -> empty,具体遍历出的值,由 undefined 占位
      const item = arr.find(function(item){
          console.log(item); // 1 undefind 3 undefined 5 
          return false;
      })
      
      // ES5 数组扩展方法只会遍历有值的索引,下面的几种遍历方法也是一样
      arr.forEach(function(item){
          console.log(item); // 1 3 5
      })
      arr.map(function(item){
          console.log(item); // 1 3 5
      })
      arr.filter(function(item){
          console.log(item); // 1 3 5
      })
      arr.reduce(function(item){
          console.log('Gone'); // 'Gone' 'Gone' 'Gone'
      },[])
      arr.reduceRight(function(item){
          console.log('Gone'); // 'Gone' 'Gone' 'Gone'
      },[])
      arr.every(function(item){
          console.log(item); // 1 3 5
          return true;
      })
      arr.some(function(item){
          console.log(item); // 1 3 5
          return false;
      });
      
      // 结论:find的遍历效率是低于ES5扩展方法的
      
    7. 遍历时对元素进行操作

      find 是不会更改数组的

      新增了元素,但是 find 会在第一次执行回调函数的时候,拿到这个函数最初的索引范围

      const arr = [1,2,3,4,5];
      const item = arr.find(function(item){
          console.log('Gone');
          item = item+1;
      })
      // find 是不会更改数组的
      console.log(arr); // [1,2,3,4,5]
      
      // 新增了元素,但是 find 会在第一次执行回调函数的时候,拿到这个函数最初的索引范围,然后追加上去
      const item1 = arr.find(function(item){
          arr.push(6);
          console.log(item); // 1 2 3 4 5
      })
      console.log(arr); // [1, 2, 3, 4, 5, 6, 6, 6, 6, 6]
      

      splice删除,删除了对应项,当前位置不保留,在数据最后补上undefined

      delete/pop删除,删除该项的值,保留当前位置,并填入 undefined

      const arr = [1,2,3,,,,7,8,9];
      arr.find(function(item, index){
          if(index === 0){
              arr.splice(1, 1); // splice删除,删除了对应项,该项位置不保留,在数据最后补上undefined
          }
          console.log(item); // 1 3 undefined undefined undefined 7 8 9 undefined
      })
      console.log(arr); // [ 1, 3, <3 empty items>, 7, 8, 9 ]
      
      arr.find(function(item, index){
          if(index === 0){
              delete arr[2]; // 删除该项的值,并填入 undefined
          }
          console.log(item); // 1 3 4 5 undefined undefined undefined undefined 7 8 9
      })
      console.log(arr); // [ 1, 2, <4 empty items>, 7, 8, 9 ]
      
      arr.find(function(item, index){
          if(index === 0){
              arr.pop(); // 删除该项的值,并填入 undefined
          }
          console.log(item); // 1 2 3 undefined undefined undefined 7 8 undefined
      })
      console.log(arr); // [ 1, 2, 3, <3 empty items>, 7, 8 ]
      
  4. 重写

    Array.prototype.myFind = function(cb){
    if(this === null){
        throw new TypeError('this is null');
    }
    if(typeof cb !== 'function'){
        throw new TypeError('Callback must be a function type');
    }
    
    // 把 this 包装成一个对象
    var obj = Object(this),
        len = obj.length >>> 0, // 无符号位移,保证是一个正整数
        arg2 = arguments[1], // 有可能传入了第二个参数
        step = 0; // 下标
    while(step < len){
        var value = obj[step]; // 找到数组下标对应的这一项
        if(cb.apply(arg2, [value, step, obj])){  // 如果返回的是真,返回value
            // 第一个返回 true 的对应数组元素作为 find 的返回值
            return value;
            }
            step++;
        }
        return undefined; // 没有找到,返回 undefined
    }
    
    const arr = [1, 2, 3, 4, 5];
    
    const item = arr.myFind(item => item > 3);
    console.log(item);
    

6. Array.prototype.findIndex(ES6)

  1. 概念

    返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。

  2. 写法

    arr.findIndex(callback[, thisArg])

    callback:针对数组中的每个元素, 都会执行该回调函数, 执行时会自动传入element/index/array三个参数

    element:当前元素。

    index:当前元素的索引。

    array:调用findIndex的数组。

    thisArg:可选。执行callback时作为this对象的值.

    返回值: 数组中通过提供测试函数的第一个元素的索引。否则,返回-1

    const arr = [1, 2, 3, 4, 5];
    const idx = arr.findIndex(item => item > 2);
    console.log(idx); // 2
    
  3. 描述

    1. 返回第一个满足条件的数组对应的元素下标 2 => 3

      const idx = arr.findIndex(item => item > 2);
      const item = arr.find(item => item > 2);
      console.log(idx, item); // 2 3
      
    2. 如果没有找到符合条件的元素,则返回-1

      const idx = arr.findIndex(item => item > 5);
      console.log(idx); // -1
      
    3. 数组长度为空的情况,返回-1

      const arr1 = [];
      const idx = arr1.findIndex(item => item > 2);
      console.log(idx); // -1
      
    4. 稀疏数组是正常遍历空隙,空隙将会被填充为undefined

      const arr1 = [,2,,,,,,];
      const idx = arr1.findIndex(function(item){
          console.log(item); // undefined 2
          return item === 2;
      });
      
    5. findIndex如果回调返回了true,遍历就停止

    6. findIndex会遍历空隙,而ES5拓展方法只会遍历有值的索引项

      const idx = arr1.findIndex(function(item){
          console.log('Gone'); // Gone * 7
      });
      // 有值的索引项
      arr1.some(function(item){
          console.log('Gone'); // Gone * 1
          return false;
      });
      
       arr1.every(function(item){
          console.log('Gone'); // Gone * 1
          return true;
      })
      
       arr1.forEach(function(item){
          console.log('Gone'); // Gone * 1
      })
      
       arr1.map(function(item){
          console.log('Gone'); // Gone * 1
      })
      
       arr1.filter(function(item){
          console.log('Gone'); // Gone * 1
      })
      
       arr1.reduce(function(item){
          console.log('Gone'); // Gone * 1
      },[]);
      
    7. 参数问题

      回调函数:遍历的当前数组元素、元素对应的下标、源数组

      回调返回值:boolean,遍历在某一次调用回调后返回true,停止

      第二个参数:

      更改回调内部的this指向

      默认情况下 this -> window

      设置了第二个参数:this -> arg2

      严格模式下:this -> undefined

    8. 回调函数内部是无法改变数组的元素值

          const idx = arr.findIndex(function(item){
              item += 1;
          });
          console.log(arr); // [1,2,3,4,5]
      
    9. 虽然增加了元素,但是遍历只会进行5次

      findIndex在第一次调用回调函数的时候确认数组的范围 5

      const idx = arr.findIndex(function(item,index){
          console.log('Gone'); // Gone * 5
          console.log(item); // 1 2 3 4 5
      });
      console.log(arr); // [1,2,3,4,5,6,6,6,6,6]
      
    10. 删除时要注意的

      const idx  = arr.findIndex(function(item, index){
          if (index === 0) {
              // 最后走的一次(第五次)补undefined,实际上数组被删除了第1项
              arr.splice(1,1); // arr -> [1,3,4,5,undefined]
              // 删除对应下标的值并补undefined,实际数组中,对应下标变成空隙 empty
              delete arr[1]; // arr -> [1,undefined,3,4,5]
              // 删除元素下标对应的值,补undefined,实际数组被删除了最后1项
              arr.pop();  // arr -> [1,2,3,4,undefined]
          }
      })
      
  4. 重写

    // 'use strict'; 
    // 在严格模式下 函数 this 默认指向 null 或 undefined, call(null/undefined)->null/undefined
    // 非严格模式下,默认指向window,call(null/undefined)->window
    Array.prototype.myFindIndex = function(cb) {
        if (this == null) {
            throw new TypeError('this is null');
        }
        if (typeof cb != 'function') {
            throw new TypeError('Callback must be a function');
        }
        var obj = Object(this),
            len = obj.length >>> 0,
            arg2 = arguments[1], // 设置this指向,后面不需要写window,因为非严格模式不传第二个参数默认就是window,严格模式下是undefined,如果写了那么在严格模式下也window,不符合预期
            step = 0;
        while (step < len) {
            var value = obj[step];
            if (cb.apply(arg2, [value, step, obj])) { // 通过apply将参数传给回调函数,如果回调返回为true,停止循环并返回元素索引
                return step;
            }
            step++;
        }
        return -1;
    }
    
    const arr = [1, 2, 3, 4, 5];
    const idx = arr.myFindIndex(function(item, index, arr) {
        console.log(item, index, arr); // 1 0 [ 1, 2, 3, 4, 5 ] ...
        console.log(this); // { a: 1 }
        return item > 2;
    }, {
        a: 1
    })
    console.log(idx); // 2
    

7. Array.prototype.flat(ES7)

  1. 概念

    flat:扁平的

    返回一个新的数组,多维数组 -> 一维数组

  2. 写法

    var newArray = arr.flat([depth])

    depth:指定要提取嵌套数组的结构深度,默认值为 1。

    返回值:一个包含将数组与子数组中所有元素的新数组。

    const arr = [0, 1, [2, 3], 4, 5];
    
    console.log(arr.flat());
    // [0, 1, 2, 3, 4, 5]
    
    const arr2 = [0, 1, 2, [[[3, 4]]]];
    
    console.log(arr2.flat(2));
    // expected output: [0, 1, 2, [3, 4]]
    
  3. 描述

    1. 返回一个新的数组,说明不修改原数组

      const arr = [0, 1, [2, 3], 4, 5];
      
      const newArr = arr.flat();
      
      console.log(arr === newArr); // false
      
    2. 参数

      1. 默认参数

        flat默认情况下参数是1 -> 向内深入一层

        参数:结构深度 默认为1,向数组内部深入一层,两层

        flat 默认情况下是浅扁平化

        const arr = [0, 1, [2, 3], 4, 5];
        // 参数为1
        console.log(arr.flat()); // [0, 1, 2, 3, 4, 5]
        console.log(arr.flat(1)); // 结果一致
        // 参数为2
        const arr1 = [0, 1, [2, [3, 4], 5], 6];
        console.log(arr.flat()); // [ 0, 1, 2, [ 3, 4 ], 5, 6 ]
        console.log(arr.flat(2)); // [1, 2, 3, 4, 5, 6]
        
        
      2. Infinity

        正无穷 Infinity 结构深度 正无穷大的设置

        const arr = [0,[1,2,[3,4,[5,6,[7,8,[9]]]]]]
        const newArr = arr.flat(infinity);
        console.log(newArr); // [1,2,3,4,5,6,7,8,9]
        
      3. 特殊参数

        0/负数/非数字符串 -> 不做任何扁平化处理

        数字字符串/布尔/ -> Number -> 数字类型

        数字必须从1开始填写 -> Infinity

        const arr = [0,1,[2,[3,4],5,]6];
        console.log(arr.flat(-2)); // [0,1,[2,[3,4],5,]6]
        console.log(arr.flat('a')); // [0,1,[2,[3,4],5,]6]
        console.log(arr.flat(0)); // [0,1,[2,[3,4],5,]6]
        console.log(arr.flat(false)); // [0,1,[2,[3,4],5,]6]
        console.log(arr.flat(true)); // [1,2,3,4,5,6]
        console.log(arr.flat(3)); // [1,2,3,4,5,6]
        
    3. 稀疏数组

      剔除所有的数组空隙 empty

      const arr = [1, , [2, , [3, 4, , 5, , , 6, [7, , 8, 9, , [0]]]]];
      
      // 剔除所有的数组空隙empty
      const newArr = arr.flat(Infinity);
      console.log(newArr); // 
      
    4. concat

      可以放入多个数组元素或者其他数组

      var a = 1,
          b = [2, 3],
          c = [3, 4];
      
      const newArr = b.concat(a, c);
      console.log(newArr); // [ 2, 3, 1, 3, 4 ]
      
  4. 浅扁平化实现

    const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]];
    
    // 方法1:利用 reduce
    function shallowFlat(arr) {
        return arr.reduce(function(prev, current) {
            return prev.concat(current);
        }, [])
    }
    console.log(shallowFlat(arr))
    
    // 方法2
    function shallowFlat(arr) {
        return [].concat(...arr);
    }
    console.log(shallowFlat(arr)); // [1,2,3,4,5,6,7,8,9]
    
  5. 深度扁平化实现

    1. 方法1:reduce + concat + isArray + 递归

      const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]];
      Array.prototype.deepFlat = function() {
          var arr = this,
              deep = arguments[0] !== Infinity ? arguments[0] >>> 0 : Infinity;
          return deep > 0 ?
              arr.reduce(function(prev, current) {
                  return prev.concat(Array.isArray(current) ?
                      current.deepFlat(deep - 1) :
                      current)
              }, []) :
              arr;
      }
      
      console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
      
    2. 方法2:forEach + isArray + push + 递归

      const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]];
      Array.prototype.deepFlat = function() {
          var arr = this,
              deep = arguments[0] !== Infinity ? arguments[0] >>> 0 : Infinity,
              res = [];
          (function _(arr, deep) { // 在独立的作用域里操作
              // 数组扩展方法时会剔除 empty 
              arr.forEach(function(item) {
                  if (Array.isArray(item) && deep > 0) {
                      _(item, deep - 1)
                  } else {
                      res.push(item)
                  }
              })
          })(arr, deep)
          return res;
      }
      console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
      
    3. 方法3:方法3:for of + isArray + push 去掉empty

      const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]];
      Array.prototype.deepFlat = function() {
          var arr = this,
              deep = arguments[0] !== Infinity ? arguments[0] >>> 0 : Infinity,
              res = [];
      
          (function _(arr, deep) { // 在独立的作用域里操作
              // 数组扩展方法时会剔除 empty 
              for (var item of arr) {
                  if (Array.isArray(item) && deep > 0) {
                      _(item, deep - 1)
                  } else {
                      item !== void 0 && res.push(item); // for...of 判断empty 要用void 0 
                  }
              }
          })(arr, deep)
      
          return res;
      }
      console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
      
    4. 方法4:stack栈 pop + push(重要)

      const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]];
      Array.prototype.deepFlat = function() {
          var arr = this,
              stack = [...arr],
              res = [];
          while (stack.length) {
              const popItem = stack.pop();
              if (Array.isArray(popItem)) {
                  stack.push(...popItem);
              } else {
                  res.push(popItem);
              }
          }
          return res.reverse();
      }
      console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
      
    5. 方法5:纯递归实现

      const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]];
      Array.prototype.deepFlat = function() {
          var arr = this,
              res = [];
          (function _(arr) {
              arr.forEach(function(item) {
                  if (Array.isArray(item)) {
                      _(item)
                  } else {
                      res.push(item)
                  }
              })
          })(arr)
          return res;
      }
      console.log(arr.deepFlat(Infinity)); // [1,2,3,4,5,6,7,8,9]
      
    6. 方法6:生成器函数

      const arr = [0, [1, 2, [3, 4, [5, 6, [7, 8, [9]]]]]];
      
      function* deepFlat(arr) {
          for (var item of arr) {
              if (Array.isArray(item)) {
                  yield* deepFlat(item);
              } else {
                  yield item;
              }
          }
      }
      // 使用的时候注意,rest运算符可以直接拿到迭代器迭代的结果
      console.log([...deepFlat(arr)]); // [1,2,3,4,5,6,7,8,9]
      

8. Array.prototype.flatMap(ES7)

  1. 概念

    兼容性不太好

    flat + map -> map遍历后返回进行浅扁平化(注意:flat深度是1)

  2. 写法

    var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
        // return element for new_array
    }[, thisArg]);
    

    callback:可以生成一个新数组中的元素的函数,可以传入currentValue/index/array个参数

    currentValue:当前正在数组中处理的元素

    index:可选的。数组中正在处理的当前元素的索引。

    array:可选的。被调用的 map 数组

    thisArg:可选的。执行 callback 函数时 使用的this 值。

    返回值: 一个新的数组,其中每个元素都是回调函数的结果,并且结构深度 depth 值为1。

    const arr = ['123', '456', '789'];
    const newArr = arr.flatMap(function(item) {
        return item.split('');
    });
    console.log(newArr); // [1,2,3,4,5,6,7,8,9]
    
  3. 描述

    1. flat + map

      const arr = ['123', '456', '789'];
      
      // 通过split把每一个字符串分开变成该数组的元素,最后出来的结果是一个二维数组
      const newArr = arr.map(function(item) {
          return item.split('');
      });
      console.log(newArr); // [ [ '1', '2', '3' ], [ '4', '5', '6' ], [ '7', '8', '9' ] ]
      
      // flat + map = flatMap -> 把二维数组变一维数组
      const newArr = arr.map(function(item) {
          return item.split('');
      }).flat(); // 后面加个flat就可以了
      console.log(newArr); // [1,2,3,4,5,6,7,8,9]
      
      // flatMap:
      // 1. 遍历 + 扁平化
      // 2. map + flat
      // 3. 效率高一些,一体化完成
      // 4. 返回值是一个新的数组
      const newArr = arr.flatMap(function(item) {
          return item.split(''); // return时帮你进行扁平化的操作
      });
      console.log(newArr); // [1,2,3,4,5,6,7,8,9]
      
    2. 返回值

      返回值是一个新的数组

      console.log(newArr === arr); // false
      
    3. 参数

      回调参数:当前遍历的元素、当前遍历的元素在数组中对应的下标、 数组本身

      回调函数中 this 默认指向 window

      严格模式下,this为undefined(这里说指向是错误的)

      flatMap的第二个参数可以更改回调内的this指向

      const arr = ['123', '456', '789'];
      
      const newArr = arr.flatMap(function(item, index, arr) {
          console.log(item, index, arr); // 123 0 ['123', '456', '789'] ...
          console.log(this); // {a:1}
      }, {
          a: 1
      });
      
  4. 使用场景

    1. 处理字符串

      对有些字符串可能会需要做统计的处理,就可以用到flatMap

      const arr = ['Hello world', 'Today is sunday'];
      const newArr1 = arr.map(function(item) {
          return item.split(' ')
      })
      
      const newArr = arr.flatMap(function(item) {
          return item.split(' ')
      })
      console.log(newArr); // [ 'Hello', 'world', 'Today', 'is', 'sunday' ]
      
    2. 做运算,并增加项(遇到负数就和前一个数相加,并放入数组)

      我们可以自主的形成数组,把我们想要添加的项放进这个数组,就可以用到flatMap

      const arr = [1, -2, -3, 5, 8, -9, 6, 7, 0];
      const newArr = arr.flatMap(function(item, index) {
          if (item < 0 && index >= 1) {
              return [item, `${item} + ${arr[index-1]} = ${item + arr[index-1]}`]
          }
          return item;
      })
      console.log(newArr); // [ 1, -2, '-2 + 1 = -1', -3, '-3 + -2 = -5', 5, 8, -9, '-9 + 8 = -1', 6, 7, 0 ]
      
  5. 重写

    TODO:这只是浅层的重写,还有一些情况并没有考虑到,比如深拷贝

    const arr = ['123', '456', '789'];
    Array.prototype.myFlatMap = function(cb) {
        if (typeof cb !== 'function') {
            throw new TypeError('Callback must be a function');
        }
        var arr = this,
            arg2 = arguments[1],
            res = [],
            item;
        for (var i = 0; i < arr.length; i++) {
            item = cb.apply(arg2, [arr[i], i, arr]); // 这里arr[i]要深度克隆一下
            item && res.push(item)
        }
        return res.flat(); // 这里可以用自己写的deepFlat替换
    }
    
    const newArr = arr.flatMap(function(item) {
        return item.split('');
    })
    console.log(newArr === arr);
    

9. Array.from(ES6)

  1. 概念

    对一个类似数组或可迭代对象(Map/Set )创建一个新的,浅拷贝的数组实例。

  2. 写法

    Array.from(arrayLike[, mapFn[, thisArg]])

    arrayLike:想要转换成数组的伪数组对象或可迭代对象。

    mapFn:如果指定了该参数,新数组中的每个元素会执行该回调函数。

    thisArg:可选参数,执行回调函数 mapFn 时 this 对象。

    返回值:一个新的数组实例。

    console.log(Array.from('foo'));
    // ["f", "o", "o"]
    
  3. 描述

    1. 返回值

      返回一个新的数组引用

      const arr = [1, 2, 3];
      // 返回一个新的数组引用
      const newArr = Array.from(arr);
      console.log(newArr); // [1,2,3]
      console.log(arr === newArr); // false
      
    2. 参数

      1. 说明

        Array.from 的第一个参数必须要是可迭代对象或者是标准的类数组

        可迭代对象判断的标准:原型上有Symbol.

        标准的类数组:不一定是真正的类数组,但键名要为索引和length长度

      2. 第一个参数

        1. 参数是一个数组

          如果参数是一个带有引用类型元素的数组,返回的新数组是一个浅拷贝

          const arr = [{
              id: 1,
              name: '张三'
          }, {
              id: 2,
              name: '李四'
          }, {
              id: 3,
              name: '王五'
          }]
          
          // 返回的新数组是一个浅拷贝(拷贝的是引用)
          const newArr = Array.from(arr);
          console.log(arr[1] === newArr[1]); // true
          console.log(newArr); // [{...}, {...}, {...}]
          
        2. 参数是一个字符串

          一个字符串,从底层来说就是用String构造函数构造出来的,所以可以理解为它是一个可迭代对象

          const str = '123';
          const newArr = Array.from(str);
          
          console.log(newArr); // ['1','2','3']
          
        3. 参数是一个Symbol类型

          Array.from不做处理,并且返回一个空数组

          因为Symbol是一个唯一的值,如果放入数组了就不是唯一了

          const sm = Symbol('123'); // Symbol是一个唯一的值
          const newArr = Array.from(sm);
          console.log(newArr) // []
          
        4. 参数是一个数字

          Number不可迭代,Array.from不做处理,并且返回一个空数组

          const num = 123;
          const newArr = Array.from(num);
          console.log(newArr) // []
          
        5. 参数是一个boolean

          Boolean不可迭代,Array.from不做处理,并且返回一个空数组

          const bool = true;
          const newArr = Array.from(bool);
          console.log(newArr) // []
          
        6. 参数是一个正则

          Reg不可迭代,Array.from不做处理,并且返回一个空数组

          const reg = /123/;
          const newArr = Array.from(reg);
          console.log(newArr) // []
          
        7. 参数是一个null/undefined

          报错:null/undefined is not iterable

          const newArr = Array.from(undefined);
          const newArr = Array.from(null);
          console.log(newArr); // 报错
          
        8. 参数为空

          相当于里面有一个 undefined, 报错:undefined is not iterable

          const newArr = Array.from();
          console.log(newArr); // 报错
          
        9. 参数是一个普通对象

          普通对象不可迭代,Array.from不做处理,并且返回一个空数组

          const obj = {
              a: 1,
              b: 2,
              c: 3
          }
          const newArr = Array.from(obj);
          console.log(newArr); // []
          
        10. 参数是一个类数组

          正常返回一个对应的数组的必要条件:

          1. 键名必须从0开始按数字顺序排列

          2. length 属性必须正确

          长度决定了新数组的长度,属性名决定了填充该数组的位置

          const arrLike = {
              0: 1,
              1: 2,
              2: 3,
              length: 3
          }
          const newArr = Array.from(arrLike);
          console.log(newArr); // [1,2,3]
          
          const arrLike = {
              a: 1,
              b: 2,
              c: 3,
              length: 3
          }
          const newArr = Array.from(arrLike);
          console.log(newArr); // [undefined,undefined,undefined]
          
          const arrLike = {
              1: 1,
              2: 2,
              3: 3,
              length: 3
          }
          const newArr = Array.from(arrLike);
          console.log(newArr); // [undefined,1,2]
          
          const arrLike = {
              a: 1,
              b: 2,
              c: 3,
              length: 5
          }
          const newArr = Array.from(arrLike);
          console.log(newArr); // [1,2,3,undefined,undefined]
          
        11. 参数是一个 Map

          Map是一个可迭代对象,返回一个二维数组

          const m = new Map([
              ['a', 1],
              ['b', 2],
              ['c', 3],
          ])
          console.log(m); // Map(3) { 'a' => 1, 'b' => 2, 'c' => 3 }
          const newArr = Array.from(m); 
          console.log(newArr); // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]
          
        12. 参数是一个 Set

          Set是一个可迭代对象,可以正常返回数组

          const s = new Set([1, 2, 3, 4]);
          console.log(s); // Set(4) { 1, 2, 3, 4 }
          const newArr = Array.from(s);
          console.log(newArr) // [1,2,3,4]
          
      3. 第二个参数

        相当于map方法,在Array.from执行过程中进行调用,唯一不同的地方就是回调没有第三个参数。

        由于回调执行的时候,Array.from还没有执行完毕,所以不存在逻辑上的新数组。

        所以无法再回调里获取到新数组本身(有别于数组的其它遍历方法)

        const arrLike = {
            0: 1,
            1: 2,
            2: 3,
            length: 3
        }
        
        const newArr = Array.from(arrLike, function(item, index, array) {
            console.log(item, index, array); // 1 0 undefined ...
            // 每一次遍历必须返回一个值
            return item + 1;
        });
        console.log(newArr); // [ 2, 3, 4 ]
        
        // from的第二个参数的执行原理 
        const newArr = Array.from(arrLike).map(function(item, index, array) {
            console.log(item, index, array); // 1 0 [1,2,3]
            return item + 1;
        });
        console.log(newArr); // [ 2, 3, 4 ]
        
        // 二者区别:
        // 1. Array.from执行的过程中执行回调,故拿不到新数组
        // 2. Array.from执行完后再调用map方法,故可以拿到新数组
        
      4. 第三个参数

        第三个参数会更改回调内的this指向

        const arrLike = {
            0: 1,
            1: 2,
            2: 3,
            length: 3
        }
        const newArr = Array.from(arrLike, function(item, index) {
            console.log(item);
            // 非严格模式下,回调内部的 this -> window
            // 严格模式下,回调内部的 this 为 undefined
            console.log(this); // {a:1}
            return item + 1;
        }, {
            a: 1
        });
        console.log(newArr); // [2, 3, 4]
        
    3. 形参长度

      证明了from方法的第一个参数是必填项

      console.log(Array.from.length) // 1
      
  4. 使用场景

    1. 填充数组 - 序列生成器

      从1开始填充到10,每个数的间隔是2

      const r = range(1, 10, 2); // (start, stop, step)
      // 实现 [1,3,5,7,9]
      console.log(r);
      
      function range(start, stop, step) {
          return Array.from({
              length: (stop - start) / step + 1 // from 会直接把小数处理成整数
          }, function(item, index) {
              return start + (index * step)
          })
      }
      
    2. 数组的合并与去重

      function combine() {
          const arr = Array.prototype.concat.apply([], arguments);
          return Array.from(new Set(arr)) // 用 set 去重
      }
      
      const arr1 = [1, 2, 3, 4, 5];
      const arr2 = [2, 3, 4, 5, 6];
      const arr3 = [3, 4, 5, 6, 7];
      
      console.log(combine(arr1, arr2, arr3)); // [1,2,3,4,5,6,7]
      
  5. 重写

    三个参数:(可迭代对象或者类数组, mapFn, this指向)

    可迭代对象或者类数组:必填

    mapFn:可选

    this指向:可选

    // 用立即执行函数创建一个独立的作用域,存放函数和对参数的处理
    
    Array.myFrom = (function() {
        // 判断是否可调用,保证 mapFn 是一个函数,如果不是,则报错
        const isCallable = function(fn) {
            // 若 false 则不可调用
            return typeof fn === 'function' || Object.prototype.toString.call(fn) === '[object Function]';
        }
    
        // 把 传入的数值 转为整数
        const toInt = function(value) {
            const v = Number(value);
            if (isNaN(v)) {
                return 0; // 非数 直接返回0
            }
            // v 是 0 或者 不是一个无穷大的数
            if (v === 0 || !isFinite(v)) {
                return v;
            }
            // 取绝对值,向下取整,乘上符号,变为正整数或负整数
            return (v > 0 ? 1 : -1) * Math.floor(Math.abs(v));
        }
    
        const maxSafeInt = Math.pow(2, 53) - 1; // js 的最大安全正整数
    
        // 转换成 length
        const toLength = function(value) {
            const len = toInt(value);
            return Math.min( // 长度不能超多最大安全整数
                Math.max(len, 0), // len 比 0 小,则取 0,len 比 0 大,证明它有长度,则取 len
                maxSafeInt
            )
        }
    
        return function(arrLike) {
            const caller = this; // 谁调用了我
    
            if (arrLike === null) {
                throw new TypeError('Method `from` requires an array-like object')
            }
    
            // 如果不是对象,包装成对象才能走下去
            const origin = Object(arrLike);
            let arg2; // 改变 this 指向的值
    
            // 判断是否有第二个参数,mapFn
            const mapFn = arguments.length > 1 ? arguments[1] : void undefined;
    
            if (typeof mapFn !== 'undefined') {
                if (!isCallable(mapFn)) { // 不可调用,抛错
                    throw new TypeError('mapFn must be a function')
                }
                if (arguments.length > 2) {
                    arg2 = arguments[2];
                }
            }
    
            const len = toLength(origin.length);
            // 确保调用myFrom返回的函数的是一个函数,因为可能出现(1).myFrom调用
            const arr = isCallable(caller) ?
                Object(new caller(len)) : // 这里如果caller是Array,则相当于 new Array(3)
                new Array(len);
    
            let i = 0,
                val;
            while (i < len) {
                val = origin[i];
                if (mapFn) {
                    arr[i] = typeof arg2 === 'undefined' ?
                        mapFn(val, i) :
                        mapFn.apply(arg2, [val, i]); // 如果传了第三个参数,则更改this指向
                } else {
                    arr[i] = val;
                }
                i++;
            }
            return arr;
        }
    })();
    
    const arrLike = {
        0: 1,
        1: 2,
        2: 3,
        length: 3
    }
    const newArr = Array.myFrom(arrLike, function(item, index) {
        console.log(item, index);
        return item + 1;
    });
    console.log(newArr);
    

10. 相等性判断与Object.is方法(ES6)

  1. 铺垫

    1. JS的相等性判断

      截至 ES6 版本,有四种相等判断的算法:

      1. 全等(三等) ===
      2. 等于 ==
      3. 零值相等 -0 === +0
      4. 同值相等 -0 !== +0 NaN === NaN
         

      JS 中提供有关相等判断的操作方法:

      1. 严格相等 === Strict Equality
      2. 非严格(抽象/非约束)相等 == Loose(自由的,不受限制的) Equality(标准的名词形容)
      3. Object.is(v1, v2) ES6 新的 API, 判断两个参数是否是同一个值
         
    2. 严格相等(===)

      全等的优点:

      1. 全等对结果的预测是更加清晰明确
      2. 全等在不隐式类型转换的前提下,更快
      // 1. 不进行隐式类型转换 —— 类型相同,值也相同
      1 === '1'; // false
      1 === 2; // false
      
      // 2. 引用值必须是同一地址
      var obj = {};
      obj === obj; // true
      {} === {}; // false 每次{},都是 new Object 出来一个新对象
      
      // 3. 两个 NaN 或者是 NaN 跟其他值都不相等
      NaN === NaN; // false;
      NaN === undefined; // false;
      
      // 4. +0 和 -0 相等
      +
      0 === -0; // true
      
      // 5. +Infinity 与 -Infinity 相等性
      +
      Infinity === -Infinity; // false
      Infinity === Infinity; // true
      

      如何定义变量 a,让 a !== a 为 true

      var a = NaN;
      a !== a; // true
      
    3. 非严格相等(==)( Abstract equality(描述))

      1. 隐式类型转换 - 等式两边都有可能被转换,转换以后还是用严格相等来进行比较
      2. 任何对象和 null undefined 都不相等

       
      与之互斥的是 窄对象 Narrow Object -> IE10 -> document.all -> 拿到 document 的所有节点 -> 其他浏览器不支持,后来只是待定为了 undefined -> typeof document.all === ‘undefined’

      document.all == undefined; // true
      typeof document.all === 'undefined' // true
      

      注:直接写 {} == undefined 会报错,因为程序认为 {} 是个代码块,后面跟上 == 无法识别

      ({}) == undefined; // false 可以加上括号,将其变为表达式
      

      ToPrimitive(A) 通过尝试调用 A 的 A.toString() 和 A.valueOf() 方法,将参数 A 转换为原始值(Primitive)

      看 MDN 上的非严格相等表格 -> https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness

      1. 同值相等 same-value

        主要是针对0的

        +0 不等于 -0 => 同值相等

        NaN === NaN => 同值相等是正确的

        同值相等的底层实现是 Object.is()

        Object.is() 是 ES6 抛出来的方法,ES5并没有暴露 JS 引擎的同值相等的方法

        var obj = {};
        Object.defineProperty(obj, 'myZero', {
            value: -0,
            writable: false,
            configurable: false,
            enumerable: false
        })
        
        // 再次定义,+0 或者 0 都会抛出异常,不能重新定义 myZero 属性,除非值相等
        // value 为 -0 可以
        Object.defineProperty(obj, 'myZero', {
            value: -0,
        })
        
        // 证明了在Object.defineProperty定义value的时候,+0 和 -0 是不相等的值
        

        NaN

        var obj = {};
        Object.defineProperty(obj, 'myNaN', {
            value: NaN,
            writable: false,
            configurable: false,
            enumerable: false
        })
        Object.defineProperty(obj, 'myNaN', {
            value: NaN,
            writable: false,
            configurable: false,
            enumerable: false
        })
        // 证明了在Object.defineProperty定义value的时候,NaN 和 NaN 是相等的
        
      2. 零值相等 same-value-zero

        +0 和 -0 是相等的

  2. 概念

    ES2015 ES6

    同值相等的实现

    Object.is() 的判断标准就是同值相等

  3. 写法

    Object.is(value1, value2);

    value1:被比较的第一个值。

    value2:被比较的第二个值。

    返回值:一个布尔值,表示两个参数是否是同一个值。

    Object.is(undefined, undefined); // true;
    Object.is(null, null); // true;
    Object.is(true, true); // true;
    Object.is('1', '1'); // true;
    
    var obj = {};
    Object.is(obj, obj); // true 同一个引用相等
    
    Object.is({}, {}); // false 不同引用不相等
    
    Object.is(1, 1); // true;
    Object.is(+0, +0); // true;
    Object.is(+0, -0); // false; 
    Object.is(NaN, NaN); // true; 
    
  4. 描述

    1. 不进行隐式类型转换

      var a = 1;
      var b = '1';
      Object.is(a, b)// false
      
    2. +0不等于-0,NaN等于NaN -> 这也是和严格相等的区别

      Object.is(+0, -0); // false 
      Object.is(NaN, NaN); // true 
      
  5. 重写

    注:1/+0 = Infinity,1/-0 = -Infinity

    Object.myIs = function(a, b) {
        if (a === b) {
            return a !== 0 ||
                1 / a === 1 / b; // 判断 +0 和 -0
        }
        return a !== a && b !== b; // a!==a -> a = NaN,  b!==b -> b=NaN
    }
    
    const res = Object.myIs(+0, -0);
    console.log(res)
    const res1 = Object.myIs(NaN, NaN);
    console.log(res1)
    const res2 = Object.myIs(1, 1);
    console.log(res2)
    
  6. falsy的8个值

    false +0 -0 8n ‘’ null undefined NaN

11. Array.prototype.includes(ES7)

  1. 概念

    查询数组内是否包含某个元素

    可以替换indexOf语法,解决indexOf的:1. 语义化不明显;2. NaN!==NaN的问题

  2. 写法

    arr.includes(valueToFind[, fromIndex])

    valueToFind:需要查找的元素值。

    fromIndex:从fromIndex 索引处开始查找

    返回值:返回一个布尔值 Boolean

  3. 描述

    1. 参数

      1. 第一个参数

        要查找的数组元素,返回值是 bool

        const arr = [1, 2, 3, 4, 5];
        console.log(arr.includes(3)); // true
        console.log(arr.includes(6)); // false
        
      2. 第二个参数

        fromIndex: 从下标几开始找,默认值为0

        两个参数都不填,返回 false

        const arr = [1, 2, 3, 4, 5];
        console.log(arr.includes(5, 3)); // true
        console.log(arr.includes(3, 3)); // false
        console.log(arr.includes(3)); // true
        
        // 两个参数都不填,返回 false
        console.log(arr.includes()); // false
        
        // 形参长度是1,如果不填,默认是 undefined
        console.log(arr.includes.length); // 1
        
        // 负数 arr.length + (-3) = 2 包含 fromIndex
        console.log(arr.includes(3, -3)); // true
        
        // fromIndex >= arr.length 直接 return false,不会对数组进行搜索 
        console.log(arr.includes(3, 5)); // false
        
        // arr.length + (-6) = -1 < 0 // 加完了之后还是负数,整个数组都会搜索,从0开始
        console.log(arr.includes(3, -6)); // true
        
    2. 区分数字字符串与字母大小写

      const arr = ['1', 'a', 3, 4, 5];
      console.log(arr.includes(1)); // false
      console.log(arr.includes('A')); // false
      
    3. String 使用 includes

      var str = 'abcde';
      console.log(str.includes('D')); // false
      console.log(str.includes('a')); // true
      
    4. 零值相等(same-value-zero)

      var arr = [0, 1, 2, 3, 4, 5];
      console.log(arr.includes(0)); // true
      console.log(arr.includes(-0)); // true
      console.log(arr.includes(+0)); // true
      
    5. 除了数组和字符串,其他类型的数据使用 includes

      includes是一个通用方法,调用者不一定非要是数组和对象 -> 即this不一定是数组和对象

      console.log(Array.prototype.includes.call(1, 'a')); // false
      console.log([].includes.call(true, 'a')); // false
      console.log([].includes.call({ // this -> 标准类数组
          0: 'a',
          length: 1
      }, 'a')); // true
      [NaN].includes(NaN); // true 
      [NaN].indexOf(NaN); // -1 
      
  4. 重写

    Array.prototype.myIncludes = function(value) {
        if (this == null) {
            throw new TypeError('"this" is null');
        }
        var fromIndex = arguments[1],
            obj = Object(this), // 变为引用值
            len = obj.length >>> 0;
        if (len === 0) {
            return false;
        }
        // 位或运算
        fromIndex = fromIndex | 0; // 如果 fromIndex 是 undefined, 那么就取 0,相当于下面注释的代码
        // if (fromIndex === undefined) {
        //     fromIndex = 0;
        // }
        fromIndex = Math.max(
            fromIndex >= 0 ? fromIndex :
            len + fromIndex,
            0);
        while (fromIndex < len) {
            if (obj[fromIndex] === value) {
                return true;
            }
            fromIndex++;
        }
        return false;
    }
    
    const arr = [1, 2, 3, 4, 5];
    console.log(arr.myIncludes(5, 3)); // true
    console.log(arr.myIncludes(3, 3)); // false
    console.log(arr.myIncludes(3)); // true
    console.log(arr.myIncludes()); // false
    console.log(arr.myIncludes.length); // 1
    console.log(arr.myIncludes(3, -3)); // true
    console.log(arr.myIncludes(3, 5)); // false
    console.log(arr.myIncludes(3, -6)); // false
    

12. Array.prototype.sort(ES3)

  1. 概念

    使用原地算法对数组的元素进行排序,并返回数组。

    原地算法:

    • V8 -> arr.length <= 10 插入排序

      arr.length > 10 快速排序

    • Mozilla 归并排序

    • Webkit 使用C++的QSort方法 快速排序的理念

  2. 写法

    arr.sort([compareFunction])

    compareFunction:比较函数方法(可选)-> 函数中两个参数a,b

    自己写一个排序规则:

    1. 没有写排序规则,不进行任何排序操作

    2. return 负数 -> a就排在b前面

      return 正数 -> b就排在a前面

      return 0 -> a与b不进行排列操作

    var arr = [5, 1, 2, 4, 6, 3, 3];
    console.log(arr.sort(function(a, b) {
        // 1. 没有写排序规则,不进行任何排序操作
    }));
    
    console.log(arr.sort(function(a, b) {
        // a => 1   b => 5
        console.log(a, b);
        if (a < b) {
            return -1;
        }
        if (a > b) {
            return 1;
        }
        if (a === b) {
            return 0;
        }
    }));
    
    // <b>纯数字</b>比较,可以简写为
    console.log(arr.sort(function(a, b) {
        return a - b; // 从小到大排序
        // return b - a; // 从大到小排序;
        // return -1; // a 都排在 b 前面
        // return 1;  // b 都排在 a 前面
        // return 0;  // 都不变
    }));
    
    // 随机排序
    console.log(arr.sort(function(a, b) {
        // compareFunction 必须对相同的输入有相同的返回结果,否则结果是不确定的
        if (Math.random() > 0.5) {
            return 1;
        }
        if (Math.random() < 0.5) {
            return -1;
        }
    }));
    

    返回值:返回排序后的原数组引用

    var arr = [5, 3, 1, 2, 6, 4];
    const newArr = arr.sort();
    
    // 返回原数组的引用,不进行数组引用赋值
    console.log(newArr === arr); // true
    
    

    sort 并不会按照数字大小进行排序

    toString -> 数组元素 -> 转为字符串

    sort 默认按照 ASCII 码 升序排序

    DOMString -> UTF-16字符串实现 -> 映射到String构造函数 -> 构建字符串
    每一个string字符串 -> 都是 UTF-16字符串 -> 是 String/DOMString 的实例
    按照UTF-16的编码顺序来进行排序 -> Unicode

    问:为什么要转成字符串呢?
    答:如果是仅限一种类型的排序的话,sort功能性就太弱
    用字符串和字符编码集合在一起形成排序规则,可排序的范围就大了

    var arr = [5, 3, 1000, 1, 6];
    console.log(arr.sort());
    
    var arr = ['b', 'a', 'e', 'd'];
    console.log(arr.sort());
    
    var arr = [true, false];
    console.log(arr.sort());
    

    字符串排序 -> 字符串的逐个字符进行编码位的排序

    var arr = ['abc', 'aba'];
    console.log(arr.sort());
    
  3. 描述

    1. 非ASCII字符串排序

      var arr = ['你', '好', '啊'];
      console.log(arr.sort(function(a, b) {
          return a.localeCompare(b); // 非 ASCII 字符串排序 都用localeCompare⭐
      }));
      
    2. 含大小写的字符串比较

      var arr = ['JACK', 'Tom', 'annie', 'Crystal'];
      console.log(arr.sort(function(a, b) {
          var _a = a.toLowerCase(), // 每一次都会对数据进行处理,如果比较复杂的话,负载很高,可以先把数组的每一项简单化
              _b = b.toLowerCase();
          if (_a < _b) {
              return -1;
          }
          if (_a > _b) {
              return 1;
          }
          return 0;
      }));
      

      优化

      var arr = ['JACK', 'Tom', 'annie', 'Crystal'];
      // 通过 map 先把数组的每一项简单化,把数组的每一项处理好,再去 sort
      var newArr = arr.map(function(item, index) {
          var it = {
              index,
              value: item.toLowerCase()
          }
          return it;
      });
      newArr.sort(function(a, b) {
          if (a.value < b.value) {
              return -1;
          }
          if (a.value > b.value) {
              return 1;
          }
          return 0;
      })
      
      var res = newArr.map(function(item) {
          return arr[item.index]
      })
      console.log(res);
      
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值