JavaScript不清不楚之遍历

Array.prototype.every

以下代码均来自:MDN

  1. every函数测试数组的每一个元素是否通过测试,此方法并不一定会遍历全部的元素,它的执行机制是遇到未通过测试的项则立即返回false,如果全部通过测试则返回true
  2. every函数并不改变原数组
  3. 空数组对任何检测回调函数都返回true(空数组中所有元素都符合给定的条件,注:因为空数组没有元素)
    这个解释还真绕
  4. IE9+支持
  5. 如果某个索引的值未定义,则这个未定的索引不会被遍历,而且不会影响结果的返回
if (!Array.prototype.every)
{
  Array.prototype.every = function(fun /*, thisArg */)
  {
    'use strict';

    //用void 0是为了防止undefined被重写而出现判断不准确的情况
    //过滤undefined和null
    if (this === void 0 || this === null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    //检测传入的参数类型
    if (typeof fun !== 'function')
        throw new TypeError();

    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++)
    {
      //用(值,索引,数组)参数来调用回调函数,如果回调函数返回false则立即return false退出
      if (i in t && !fun.call(thisArg, t[i], i, t))
        return false;
    }
    //全部通过测试则返回true
    return true;
  };
}

Array.prototype.some

  1. some函数也不一定会遍历所有的项,当遇到符合条件的第一个项时就会返回true退出遍历
  2. some不会改变原数组
  3. some 遍历的元素的范围在第一次调用 callback. 时就已经确定了。在调用 some 后被添加到数组中的值不会被 callback 访问到。如果数组中存在且还未被访问到的元素被 callback 改变了,则其传递给 callback 的值是 some 访问到它那一刻的值。未被遍历的元素被动态删除后是不会被访问的
  4. 如果某个索引的值未定义,则这个未定的索引不会被遍历,而且不会影响结果的返回
// Production steps of ECMA-262, Edition 5, 15.4.4.17
// Reference: http://es5.github.io/#x15.4.4.17
if (!Array.prototype.some) {
  Array.prototype.some = function(fun/*, thisArg*/) {
    'use strict';
    //过滤undefined和null
    if (this == null) {
      throw new TypeError('Array.prototype.some called on null or undefined');
    }
    //检测传入的参数类型
    if (typeof fun !== 'function') {
      throw new TypeError();
    }

    var t = Object(this);
    var len = t.length >>> 0;

    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++) {
      //用(值,索引,数组)参数来调用回调函数,如果回调函数返回true则立即return true退出
      if (i in t && fun.call(thisArg, t[i], i, t)) {
        return true;
      }
    }
    //如果没有元素通过测试,则返回false
    return false;
  };
}
//改变数组中的项
[1,2,3,4,5].some(function(v, i, arr){
    if(v % 2 === 0){
        //被改变的值,会按改变后的值检测
        ++arr[i + 1];
        //后来动态添加的元素项,不会参与测试
        arr.push(20)
    }
    console.log(arr)
    return v > 10;
})
/*
    VM88:6 (5) [1, 2, 3, 4, 5]
    VM88:6 (6) [1, 2, 4, 4, 5, 20]
    VM88:6 (7) [1, 2, 4, 5, 5, 20, 20]
    VM88:6 (7) [1, 2, 4, 5, 5, 20, 20]
    VM88:6 (7) [1, 2, 4, 5, 5, 20, 20]
    false
*/
//判断两个数组是否有交集
function isIntersection(arr1, arr2){
    return arr1.some(function(v, i, arr1){
        return arr2.some(function(vv, ii, arr2){
            return v === vv
        });
    });
}

Array.prototype.find

  1. find函数也未必会遍历所有的元素项,当遇到符合条件的元素项时,就会立刻返回这个元素项,并不再继续遍历后面的元素,如果遍历完所有的项没有符合要求的值则返回undefined
  2. find不会改变原来的数组
  3. IE系列均不支持
  4. 某个索引上的值未定义也会被遍历当作undefined来处理
  5. 在第一次调用 callback 函数时会确定元素的索引范围,因此在 find 方法开始执行之后添加到数组的新元素将不会被 callback 函数访问到。如果数组中一个尚未被callback函数访问到的元素的值被callback函数所改变,那么当callback函数访问到它时,它的值是将是根据它在数组中的索引所访问到的当前值。被删除的元素仍旧会被访问到。
    此处第三条是mdn上的原话,但是我在尝试之后发现如果先动态的添加一个元素,然后再把此元素动态的删除,这个元素被遍历了,不知道这个是不是bug
    终于知道这个问题怎么回事了:这个是不是bug我不清楚,只是知道先删除再添加,它等同于修改了原本索引上对应的值,所以被修改的值后来也被遍历了
// https://tc39.github.io/ecma262/#sec-array.prototype.find
if (!Array.prototype.find) {
  Object.defineProperty(Array.prototype, 'find', {
    value: function(predicate) {
     // 1. Let O be ? ToObject(this value).
      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }

      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If IsCallable(predicate) is false, throw a TypeError exception.
      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }

      // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
      //决定以谁的名义调用回调函数
      var thisArg = arguments[1];

      // 5. Let k be 0.
      var k = 0;

      // 6. Repeat, while k < len
      while (k < len) {
        // a. Let Pk be ! ToString(k).
        // b. Let kValue be ? Get(O, Pk).
        // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
        // d. If testResult is true, return kValue.
        var kValue = o[k];
        //如果以(thisArg, kValue, kindex, arr)调用检测函数返回了true则直接返回这个kValue,否则继续循环
        if (predicate.call(thisArg, kValue, k, o)) {
          return kValue;
        }
        // e. Increase k by 1.
        k++;
      }

      // 7. Return undefined.
      return undefined;
    }
  });
}
[1,2,3,4,5].find(function(v, i, arr){
    if(v % 2 === 0){
        //被改变的值,会按改变后的值检测
        ++arr[i + 1];
        //后来动态添加的元素项,不会参与测试
        arr.pop();
        arr.push(20)
    }
    console.log(arr)
    return v > 10;
})
/*
    VM344:9 (5) [1, 2, 3, 4, 5]
    VM344:9 (5) [1, 2, 4, 4, 20]
    VM344:9 (5) [1, 2, 4, 5, 20]
    VM344:9 (5) [1, 2, 4, 5, 20]
    VM344:9 (6) [1, 2, 4, 5, 20, 20]
    20  这里最后居然输出了20,有点匪夷所思
*/

Array.prototype.findIndex

这个函数和find基本一模一样,只是在最后返回的时候find返回的是vaule,findIndex返回的index(找到第一个满足条件的元素则直接返回这个元素的索引值,如果均不满足则返回-1)

[1,2,3,4,5].find(function(v, i, arr){
    if(v % 2 === 0){
        //被改变的值,会按改变后的值检测
        ++arr[i + 1];
        //后来动态添加的元素项,不会参与测试
        arr.pop();
        arr.push(20)
    }
    console.log(arr)
    return v > 10;
})
/*
    VM344:9 (5) [1, 2, 3, 4, 5]
    VM344:9 (5) [1, 2, 4, 4, 20]
    VM344:9 (5) [1, 2, 4, 5, 20]
    VM344:9 (5) [1, 2, 4, 5, 20]
    VM344:9 (6) [1, 2, 4, 5, 20, 20]
    4
*/

Array.prototype.filter

  1. filter函数返回一个包含满足检测条件的所有元素的新数组,如果没有满足条件的元素项,则返回一个空数组
  2. filter不修改原数组
  3. IE9+支持filter函数
  4. filter函数会遍历所有的元素项(某个索引未定义值则会被跳过)
  5. filter 遍历的元素范围在第一次调用 callback 之前就已经确定了。在调用 filter 之后被添加到数组中的元素不会被 filter 遍历到。如果已经存在的元素被改变了,则他们传入 callback 的值是 filter 遍历到它们那一刻的值。被删除或从来未被赋值的元素不会被遍历到。
if (!Array.prototype.filter)
{
  Array.prototype.filter = function(fun /* , thisArg*/)
  {
    "use strict";

    if (this === void 0 || this === null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== "function")
      throw new TypeError();

    var res = [];
    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++)
    {
      if (i in t)
      {
        var val = t[i];

        // NOTE: Technically this should Object.defineProperty at
        //       the next index, as push can be affected by
        //       properties on Object.prototype and Array.prototype.
        //       But that method's new, and collisions should be
        //       rare, so use the more-compatible alternative.
        if (fun.call(thisArg, val, i, t))
          res.push(val);
      }
    }

    return res;
  };
}

Array.prototype.fill

  1. fill函数会改变调用的数组,而不是返回新数组,当一个对象被传递给 fill 方法的时候, 填充数组的是这个对象的引用
  2. IE系列不支持
  3. 需要注意一点是,调用fill函数,数组一定要有一个确定的length,如果指定的位置超出了数组的边界是不会被填充的
    也就是说fill不会改变数组的length属性
if (!Array.prototype.fill) {
  Object.defineProperty(Array.prototype, 'fill', {
    value: function(value) {

      // Steps 1-2.
      if (this == null) {
        throw new TypeError('this is null or not defined');
      }

      var O = Object(this);

      // Steps 3-5.
      var len = O.length >>> 0;

      // Steps 6-7.
      var start = arguments[1];
      //如果没有第二个参数,默认为0
      var relativeStart = start >> 0;

      // Step 8.
      //按照数组的长度来扶正start值赋值给k 
      var k = relativeStart < 0 ?
        Math.max(len + relativeStart, 0) :
        Math.min(relativeStart, len);

      // Steps 9-10.
      var end = arguments[2];
      //如果没传第三个值,则默认为数组的length
      var relativeEnd = end === undefined ?
        len : end >> 0;

      // Step 11.
      //按照数组的长度来扶正end值赋值给final
      var final = relativeEnd < 0 ?
        Math.max(len + relativeEnd, 0) :
        Math.min(relativeEnd, len);

      // Step 12.
      //开始索引小于结束索引,开进行循环
      while (k < final) {
        O[k] = value;
        k++;
      }

      // Step 13.
      return O;
    }
  });
}
//当传入的填充参数是个引用类型的值,则填充的都是这个参数的引用
var arr = new Array(10).fill({name: "dao-keer", age: 18});
arr[0].name = "dao keer go";
console.log(arr)
//每个元素项都被修改成了:{name: "dao keer go", age: 18},填充引用类型的参数时需要特别小心

//超出边界的位置不会被填充
[1,2,3,4,5].fill(88, 1, 10);
//[1, 88, 88, 88, 88]

Array.prototype.forEach

  1. forEach会遍历整个数组,但是如果某个索引上没有定义,则该索引不会被遍历,没有返回一个新数组! & 没有返回值
  2. 没有办法中止或者跳出 forEach 循环,除了抛出一个异常
  3. forEach() 为每个数组元素执行callback函数,它总是返回 undefined值,并且不可链式调用
  4. forEach 遍历的范围在第一次调用 callback 前就会确定。调用forEach 后添加到数组中的项不会被 callback 访问到。如果已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值。已删除的项不会被遍历到。如果已访问的元素在迭代时被删除了(例如使用 shift()) ,之后的元素将被跳过
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {
  Array.prototype.forEach = function(callback, thisArg) {
    var T, k;

    if (this == null) {
      throw new TypeError(' this is null or not defined');
    }

    // 1. Let O be the result of calling toObject() passing the
    // |this| value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get() internal
    // method of O with the argument "length".
    // 3. Let len be toUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If isCallable(callback) is false, throw a TypeError exception. 
    // See: http://es5.github.com/#x9.11
    if (typeof callback !== "function") {
      throw new TypeError(callback + ' is not a function');
    }

    // 5. If thisArg was supplied, let T be thisArg; else let
    // T be undefined.
    if (arguments.length > 1) {
      T = thisArg;
    }

    // 6. Let k be 0
    k = 0;

    // 7. Repeat, while k < len
    while (k < len) {

      var kValue;

      // a. Let Pk be ToString(k).
      //    This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty
      //    internal method of O with argument Pk.
      //    This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {

        // i. Let kValue be the result of calling the Get internal
        // method of O with argument Pk.
        kValue = O[k];

        // ii. Call the Call internal method of callback with T as
        // the this value and argument list containing kValue, k, and O.
        callback.call(T, kValue, k, O);
      }
      // d. Increase k by 1.
      k++;
    }
    // 8. return undefined
  };
}
//对应的索引上如果没有定义,则不会被遍历到
[1, 2, , 4, 5, ,7].forEach(function(v,i,arr){
    console.log("index: " + i + ", value: " + v)
});
/*
    VM99:2 index: 0, value: 1
    VM99:2 index: 1, value: 2
    VM99:2 index: 3, value: 4
    VM99:2 index: 4, value: 5
    VM99:2 index: 6, value: 7
*/

//删除和修改元素
[1,2,3,4,5,6,7,8].forEach(function(v, i, arr){
    if(i === 2){
        arr.shift();
    }
    if(i === 3){
        arr[i + 1] = 88;
    }
    console.log(v)
});
/*
    VM114:8 1
    VM114:8 2
    VM114:8 3  //执行到这里的时候,删除了第一个元素,后面的元素4被跳过了
    VM114:8 5
    VM114:8 88
    VM114:8 7
    VM114:8 8
*/

Array.prototype.map

  1. map方法总是返回一个新的数组,不改变length属性,某个索引上未定义则会跳过不执行回调函数
  2. 原数组中新增加的元素将不会被 callback 访问到;若已经存在的元素被改变或删除了,则它们的传递到 callback 的值是 map 方法遍历到它们的那一时刻的值;而被删除的元素将不会被访问到
  3. map是根据元素组中的元素项进行的浅拷贝赋值,如果改变了新数组里的引用类型的值,会影响原数组
// 实现 ECMA-262, Edition 5, 15.4.4.19
// 参考: http://es5.github.com/#x15.4.4.19
if (!Array.prototype.map) {
  Array.prototype.map = function(callback, thisArg) {
    var T, A, k;

    if (this == null) {
      throw new TypeError(" this is null or not defined");
    }

    // 1. 将O赋值为调用map方法的数组.
    var O = Object(this);

    // 2.将len赋值为数组O的长度.
    var len = O.length >>> 0;

    // 3.如果callback不是函数,则抛出TypeError异常.
    if (Object.prototype.toString.call(callback) != "[object Function]") {
      throw new TypeError(callback + " is not a function");
    }

    // 4. 如果参数thisArg有值,则将T赋值为thisArg;否则T为undefined.
    if (thisArg) {
      T = thisArg;
    }

    // 5. 创建新数组A,长度为原数组O长度len
    A = new Array(len);

    // 6. 将k赋值为0
    k = 0;

    // 7. 当 k < len 时,执行循环.
    while(k < len) {

      var kValue, mappedValue;

      //遍历O,k为原数组索引
      if (k in O) {

        //kValue为索引k对应的值.
        kValue = O[ k ];

        // 执行callback,this指向T,参数有三个.分别是kValue:值,k:索引,O:原数组.
        mappedValue = callback.call(T, kValue, k, O);

        // 返回值添加到新数组A中.
        A[ k ] = mappedValue;
      }
      // k自增1
      k++;
    }

    // 8. 返回新数组A
    return A;
  };      
}
//修改引用类型的值,将影响原数组,修改引用类型的值需要特别注意
var arr = [1,2,3,4,{name: "dao-keer"},6], res;
res = arr.map(function(v, i, arr){
    if(typeof v === "number"){
        console.log(v);
    }else{
        v.name = "dao keer go";
    }
    //map的回调函数需要有返回值
    return v;
});
console.log(arr);
console.log(res);
//{name: "dao keer go"} 两个数组的引用类型的值相同
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值