• apply


    ES5中新增了写数组方法,如下:

    forEach (js v1.6)
    map (js v1.6)
    filter (js v1.6)
    some (js v1.6)
    every (js v1.6)
    indexOf (js v1.6)
    lastIndexOf (js v1.6)
    reduce (js v1.8)
    reduceRight (js v1.8)
    浏览器支持

    Opera 11+
    Firefox 3.6+
    Safari 5+
    Chrome 8+
    Internet Explorer 9+
    对于让人失望很多次的IE6-IE8浏览器,Array原型扩展可以实现以上全部功能,例如forEach方法:

    // 对于古董浏览器,如IE6-IE8

    if (typeof Array.prototype.forEach != "function") {
      Array.prototype.forEach = function () {
        /* 实现 */
      };
    }
    二、一个一个来
    forEach
    forEach是Array新方法中最基本的一个,就是遍历,循环。例如下面这个例子:
    1. [1, 2 ,3, 4].forEach(alert);
    等同于下面这个传统的for循环:
    1. var array = [1, 2, 3, 4];
    2. for (var k = 0, length = array.length; k < length; k++) {
    3. alert(array[k]);
    4. }

    Array在ES5新增的方法中,参数都是function类型,默认有传参,这些参数分别是?见下面:

    [1, 2 ,3, 4].forEach(console.log);
    1. // 1, 0, [1, 2, 3, 4] //遍历的数组内容 对应数组索引 数组本身
    2. // 2, 1, [1, 2, 3, 4]
    3. // 3, 2, [1, 2, 3, 4]
    4. // 4, 3, [1, 2, 3, 4]

    显而易见,forEach方法中的function回调支持3个参数,第1个是遍历的数组内容;第2个是对应的数组索引,第3个是数组本身。

    因此,我们有:
    1. [].forEach(function(value, index, array) {
    2. // ...
    3. });
     
    对比jQuery中的$.each方法:
    1. $.each([], function(index, value, array) {
    2. // ...
    3. });

    会发现,第1个和第2个参数正好是相反的,大家要注意了,不要记错了。后面类似的方法,例如$.map也是如此。

    现在,我们就可以使用forEach卖弄一个稍显完整的例子了,数组求和:
    1. var sum = 0;
    2. [1, 2, 3, 4].forEach(function (item, index, array) {
    3. console.log(array[index] == item); // true
    4. sum += item;
    5. });
    6. alert(sum);//10
     
    再下面,更进一步,forEach除了接受一个必须的回调函数参数,还可以接受一个可选的上下文参数(改变回调函数里面的this指向)(第2个参数)。
    1. 上下文参数

    array.forEach(callback,[ thisObject])
    例子更能说明一切:

    1. var database = {
    2. users: ["张三", "李四", "王五"],
    3. sendEmail: function (user) {
    4. if (this.isValidUser(user)) {//this
    5. console.log('你好' + user);
    6. } else {
    7. console.log("你不好" + user);
    8. }
    9. },
    10. isValidUser: function (user) {
    11. return /^张/.test(user);
    12. }
    13. };
    14. database.users.forEach(database.sendEmail,database);
    15. //使用database代替this

    如果这第2个可选参数不指定,则使用全局对象代替(在浏览器是为window),严格模式下甚至是undefined.
    另外,forEach不会遍历纯粹“占着官位吃空饷”的元素的,例如下面这个例子:

    1. var array = [1, 2, 3];
    2. delete array[1]; // 移除 2
    3. alert(array); // "1,,3"
    4. alert(array.length); // but the length is still 3
    5. array.forEach(alert); // 弹出的仅仅是1和3
    6. 综上全部规则,我们就可以对IE6-IE8进行仿真扩展了,如下代码:
    7. // 对于古董浏览器,如IE6-IE8
    8. if (typeof Array.prototype.forEach != "function") {
    9. Array.prototype.forEach = function (fn, context) {
    10. for (var k = 0, length = this.length; k < length; k++) {
    11. if (typeof fn === "function" && Object.prototype.hasOwnProperty.call(this, k)) {
    12. fn.call(context, this[k], k, this);
    13. }
    14. }
    15. };
    16. }
    17. 现在拿上面“张含韵”的例子测下我们扩展的forEach方法,您可能狠狠地点击这里:兼容处理的forEach方法demo
    18. 例如IE7浏览器下:
    19. IE7forEach方法测试结果截图 张鑫旭-鑫空间-鑫生活
    20. map
    21. 这里的map不是“地图”的意思,而是指“映射”。[].map(); 基本用法跟forEach方法类似:
    22. array.map(callback,[ thisObject]);
    23. callback的参数也类似:
    24. [].map(function(value, index, array) {
    25. // ...
    26. });
    27. map方法的作用不难理解,“映射”嘛,也就是原数组被“映射”成对应新数组。下面这个例子是数值项求平方:
    28. var data = [1, 2, 3, 4];
    29. var arrayOfSquares = data.map(function (item) {
    30. return item * item;
    31. });
    32. alert(arrayOfSquares); // 1, 4, 9, 16
    33. callback需要有return值,如果没有,就像下面这样:
    34. var data = [1, 2, 3, 4];
    35. var arrayOfSquares = data.map(function() {});
    36. arrayOfSquares.forEach(console.log);
    37. 结果如下图,可以看到,数组所有项都被映射成了undefined
    38. 全部项都成了undefined
    39. 在实际使用的时候,我们可以利用map方法方便获得对象数组中的特定属性值们。例如下面这个例子(之后的兼容demo也是该例子):
    40. var users = [
    41. {name: "张含韵", "email": "zhang@email.com"},
    42. {name: "江一燕", "email": "jiang@email.com"},
    43. {name: "李小璐", "email": "li@email.com"}
    44. ];
    45. var emails = users.map(function (user) { return user.email; });
    46. console.log(emails.join(", ")); // zhang@email.com, jiang@email.com, li@email.com
    47. Array.prototype扩展可以让IE6-IE8浏览器也支持map方法:
    48. if (typeof Array.prototype.map != "function") {
    49. Array.prototype.map = function (fn, context) {
    50. var arr = [];
    51. if (typeof fn === "function") {
    52. for (var k = 0, length = this.length; k < length; k++) {
    53. arr.push(fn.call(context, this[k], k, this));
    54. }
    55. }
    56. return arr;
    57. };
    58. }

    您可以狠狠地点击这里:兼容map方法测试demo,map映射方法测试

    filter
    filter为“过滤”、“筛选”之意。指数组filter后,返回过滤后的新数组。用法跟map极为相似:
    array.filter(callback,[ thisObject]);
    filter的callback函数需要返回布尔值true或false. 如果为true则表示,恭喜你,通过啦!如果为false, 只能高歌“我只能无情地将你抛弃……”。

    可能会疑问,一定要是Boolean值吗?我们可以简单测试下嘛,如下:

    var data = [0, 1, 2, 3];
    var arrayFilter = data.filter(function(item) {
        return item;
    });
    console.log(arrayFilter); // [1, 2, 3]
    有此可见,返回值只要是弱等于== true/false就可以了,而非非得返回 === true/false.

    因此,我们在为低版本浏览器扩展时候,无需关心是否返回值是否是纯粹布尔值(见下黑色代码部分):
    1. if (typeof Array.prototype.filter != "function") {
    2. Array.prototype.filter = function (fn, context) {
    3. var arr = [];
    4. if (typeof fn === "function") {
    5. for (var k = 0, length = this.length; k < length; k++) {
    6. fn.call(context, this[k], k, this) && arr.push(this[k]);
    7. }
    8. }
    9. return arr;
    10. };
    11. }

    接着上面map筛选邮件的例子,您可以狠狠地点击这里:兼容处理后filter方法测试demo

    主要测试代码为:

    var emailsZhang = users
      // 获得邮件
      .map(function (user) { return user.email; })
      // 筛选出zhang开头的邮件
      .filter(function(email) {  return /^zhang/.test(email); });

    console.log(emailsZhang.join(", ")); // zhang@email.com
    filter demo页面的结果截图 张鑫旭-鑫空间-鑫生活

    实际上,存在一些语法糖可以实现map+filter的效果,被称之为“数组简约式(Array comprehensions)”。目前,仅FireFox浏览器可以实现,展示下又不会怀孕:

    var zhangEmails = [user.email for each (user in users) if (/^zhang/.test(user.email)) ];

    console.log(zhangEmails); // [zhang@email.com]
    数组简约式(Array comprehensions)实现的map+filter效果

    some
    some意指“某些”,指是否“某些项”合乎条件。与下面的every算是好基友,every表示是否“每一项”都要靠谱。用法如下:
    array.some(callback,[ thisObject]);
    例如下面的简单使用:

    var scores = [5, 8, 3, 10];
    var current = 7;

    function higherThanCurrent(score) {
      return score > current;
    }

    if (scores.some(higherThanCurrent)) {
      alert("朕准了!");
    }
    结果弹出了“朕准了”文字。 some要求至少有1个值让callback返回true就可以了。显然,8 > 7,因此scores.some(higherThanCurrent)值为true.

    我们自然可以使用forEach进行判断,不过,相比some, 不足在于,some只有有true即返回不再执行了。
    IE6-IE8扩展如下:
    1. if (typeof Array.prototype.some != "function") {
    2. Array.prototype.some = function (fn, context) {
    3. var passed = false;
    4. if (typeof fn === "function") {
    5. for (var k = 0, length = this.length; k < length; k++) {
    6. if (passed === true) break;
    7. passed = !!fn.call(context, this[k], k, this);
    8. }
    9. }
    10. return passed;
    11. };
    12. }

    于是,我们就有了“朕准了”的demo,您可以狠狠地点击这里:兼容处理后的some方法demo

    IE7浏览器下some扩展demo结果

    every
    跟some的基友关系已经是公开的秘密了,同样是返回Boolean值,不过,every需要每一个妃子都要让朕满意,否则——“来人,给我拖出去砍了!”
    IE6-IE8扩展(与some相比就是true和false调换一下):

    if (typeof Array.prototype.every != "function") {
      Array.prototype.every = function (fn, context) {
        var passed = true;
        if (typeof fn === "function") {
           for (var k = 0, length = this.length; k < length; k++) {
              if (passed === false) break;
              passed = !!fn.call(context, this[k], k, this);
          }
        }
        return passed;
      };
    }
    还是那个朕的例子,您可以狠狠地点击这里:是否every妃子让朕满意demo

    if (scores.every(higherThanCurrent)) {
      console.log("朕准了!");
    } else {
      console.log("来人,拖出去斩了!");        
    }
    结果是:
    every方法结果

    indexOf
    indexOf方法在字符串中自古就有,string.indexOf(searchString, position)。数组这里的indexOf方法与之类似。
    array.indexOf(searchElement[, fromIndex])
    返回整数索引值,如果没有匹配(严格匹配),返回-1. fromIndex可选,表示从这个位置开始搜索,若缺省或格式不合要求,使用默认值0,我在FireFox下测试,发现使用字符串数值也是可以的,例如"3"和3都可以。

    var data = [2, 5, 7, 3, 5];

    console.log(data.indexOf(5, "x")); // 1 ("x"被忽略)
    console.log(data.indexOf(5, "3")); // 4 (从3号位开始搜索)

    console.log(data.indexOf(4)); // -1 (未找到)
    console.log(data.indexOf("5")); // -1 (未找到,因为5 !== "5")
    兼容处理如下:

    if (typeof Array.prototype.indexOf != "function") {
      Array.prototype.indexOf = function (searchElement, fromIndex) {
        var index = -1;
        fromIndex = fromIndex * 1 || 0;

        for (var k = 0, length = this.length; k < length; k++) {
          if (k >= fromIndex && this[k] === searchElement) {
              index = k;
              break;
          }
        }
        return index;
      };
    }
    一个路子下来的,显然,轮到demo了,您可以狠狠地点击这里:兼容处理后indexOf方法测试demo

    下图为ietester IE6下的截图:
    IE6下indexOf扩展后测试结果截图

    lastIndexOf
    lastIndexOf方法与indexOf方法类似:
    array.lastIndexOf(searchElement[, fromIndex])
    只是lastIndexOf是从字符串的末尾开始查找,而不是从开头。还有一个不同就是fromIndex的默认值是array.length - 1而不是0.

    IE6等浏览器如下折腾:

    if (typeof Array.prototype.lastIndexOf != "function") {
      Array.prototype.lastIndexOf = function (searchElement, fromIndex) {
        var index = -1, length = this.length;
        fromIndex = fromIndex * 1 || length - 1;

        for (var k = length - 1; k > -1; k-=1) {
            if (k <= fromIndex && this[k] === searchElement) {
                index = k;
                break;
            }
        }
        return index;
      };
    }
    于是,则有:

    var data = [2, 5, 7, 3, 5];

    console.log(data.lastIndexOf(5)); // 4
    console.log(data.lastIndexOf(5, 3)); // 1 (从后往前,索引值小于3的开始搜索)

    console.log(data.lastIndexOf(4)); // -1 (未找到)
    懒得截图了,结果查看可狠狠地点击这里:lastIndexOf测试demo

    reduce
    reduce是JavaScript 1.8中才引入的,中文意思为“减少”、“约简”。不过,从功能来看,我个人是无法与“减少”这种含义联系起来的,反而更接近于“迭代”、“递归(recursion)”,擦,因为单词这么接近,不会是ECMA-262 5th制定者笔误写错了吧~~
    此方法相比上面的方法都复杂,用法如下:

    array.reduce(callback[, initialValue])
    callback函数接受4个参数:之前值、当前值、索引值以及数组本身。initialValue参数可选,表示初始值。若指定,则当作最初使用的previous值;如果缺省,则使用数组的第一个元素作为previous初始值,同时current往后排一位,相比有initialValue值少一次迭代。

    var sum = [1, 2, 3, 4].reduce(function (previous, current, index, array) {
      return previous + current;
    });

    console.log(sum); // 10
    说明:

    因为initialValue不存在,因此一开始的previous值等于数组的第一个元素。
    从而current值在第一次调用的时候就是2.
    最后两个参数为索引值index以及数组本身array.
    以下为循环执行过程:

    // 初始设置
    previous = initialValue = 1, current = 2

    // 第一次迭代
    previous = (1 + 2) =  3, current = 3

    // 第二次迭代
    previous = (3 + 3) =  6, current = 4

    // 第三次迭代
    previous = (6 + 4) =  10, current = undefined (退出)
    有了reduce,我们可以轻松实现二维数组的扁平化:

    var matrix = [
      [1, 2],
      [3, 4],
      [5, 6]
    ];

    // 二维数组扁平化
    var flatten = matrix.reduce(function (previous, current) {
      return previous.concat(current);
    });

    console.log(flatten); // [1, 2, 3, 4, 5, 6]
    兼容处理IE6-IE8:

    if (typeof Array.prototype.reduce != "function") {
      Array.prototype.reduce = function (callback, initialValue ) {
         var previous = initialValue, k = 0, length = this.length;
         if (typeof initialValue === "undefined") {
            previous = this[0];
            k = 1;
         }
         
        if (typeof callback === "function") {
          for (k; k < length; k++) {
             this.hasOwnProperty(k) && (previous = callback(previous, this[k], k, this));
          }
        }
        return previous;
      };
    }
    然后,测试整合,demo演示,您可以狠狠地点击这里:兼容IE6的reduce方法测试demo

    IE6浏览器下结果如下图:
    reduce方法测试页面结构示意

    reduceRight
    reduceRight跟reduce相比,用法类似:
    array.reduceRight(callback[, initialValue])
    实现上差异在于reduceRight是从数组的末尾开始实现。看下面这个例子:

    var data = [1, 2, 3, 4];
    var specialDiff = data.reduceRight(function (previous, current, index) {
      if (index == 0) {
        return previous + current;
      }
      return previous - current;
    });

    console.log(specialDiff); // 0
    结果0是如何得到的呢?
    我们一步一步查看循环执行:

    // 初始设置
    index = 3, previous = initialValue = 4, current = 3

    // 第一次迭代
    index = 2, previous = (4- 3) = 1, current = 2

    // 第二次迭代
    index = 1, previous = (1 - 2) = -1, current = 1

    // 第三次迭代
    index = 0, previous = (-1 + 1) = 0, current = undefined (退出)
    为使低版本浏览器支持此方法,您可以添加如下代码:

    if (typeof Array.prototype.reduceRight != "function") {
      Array.prototype.reduceRight = function (callback, initialValue ) {
        var length = this.length, k = length - 1, previous = initialValue;
        if (typeof initialValue === "undefined") {
            previous = this[length - 1];
            k--;
        }
        if (typeof callback === "function") {
           for (k; k > -1; k-=1) {          
              this.hasOwnProperty(k) && (previous = callback(previous, this[k], k, this));
           }
        }
        return previous;
      };
    }
    您可以狠狠地点击这里:reduceRight简单使用demo

    对比FireFox浏览器和IE7浏览器下的结果:
    FireFox浏览器下reduceRight测试结果 对比IE7下reduceRight测试结果截图 张鑫旭-鑫空间-鑫生活

    三、更进一步的应用
    我们还可以将上面这些数组方法应用在其他对象上。

    例如,我们使用forEach遍历DOM元素。

    var eleDivs = document.getElementsByTagName("div");
    Array.prototype.forEach.call(eleDivs, function(div) {
        console.log("该div类名是:" + (div.className || "空"));
    });
    可以输出页面所有div的类名,您可以狠狠地点击这里:Array新方法forEach遍历DOM demo
    等很多其他类数组应用。

    四、最后一点点了
    本文为低版本IE扩展的Array方法我都合并到一个JS中了,您可以轻轻的右键这里或下载或查看:es5-array.js

    以上所有未IE扩展的方法都是自己根据理解写的,虽然多番测试,难免还会有细节遗漏的,欢迎指出来。




  • 相关阅读:
    【Log历练手册】Spring事务管理不能提交异常
    【网络安全】如何使用OpenSSL工具生成根证书与应用证书
    【网络安全】如何使用OpenSSL工具生成根证书与应用证书
    【JAVA笔记——器】Spring Aop 实现Log日志系统——基本实现
    jdbc连接池配置方法
    用于读/写配置的工具,下面列出了各种配置(从最高优先级到最低优先级)
    文件复制Util写法,可以适用于多种条件
    记录一个工作中遇到的问题,svn拉的项目,pom.xml报错
    layui的js写法,部分代码
    JDBCUtil连接数据库的写法
  • 原文地址:https://www.cnblogs.com/itlyh/p/6006244.html
Copyright © 2020-2023  润新知