继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

MySQL存储过程

米脂
关注TA
已关注
手记 492
粉丝 88
获赞 590

通过underscore系列的一和二,我们掌握了一些underscore框架设计的思想以及基础的数据类型诊断,从这一节开始,我们将慢慢进入underscore的难点和核心。这个系列的三和四,我们会全方位的了解underscore中对迭代器的实现。

三, 迭代器

3.1 迭代器的基本概念

javascript的迭代器我们并不陌生。我们可以这样理解,迭代器提供了一种方法按顺序访问集合中的每一项,而这种集合可以是数组,也可以是对象。我们先回忆一下,在ES6之前,javascript有7种数组的迭代器,分别是

  • for 循环,它是最常规,也是最基础的迭代

var arr = [1,2,4] for(var i=0;i<arr.length;i++) {     console.log(i) } 复制代码
  • forEach 接收一个函数作为参数,对数组中的每一个元素使用该函数。

function add(num) {console.log(num + 1)} var arr = [1,2,3] arr.forEach(add) 复制代码
  • every 接收一个返回值为boolean 类型的函数,对数组中的每一个元素使用该函数,当所有的元素使用该方法后都返回true, 则最终结果返回true

function judge(a) {return a > 1} var arr = [1,23,4]; console.log(arr.every(judge)); // false 复制代码
  • some 接收一个返回值为boolean 类型的函数,对数组中的每一个元素使用该函数,只要有一个元素使用该函数后返回结果为true,则最终结果返回true。

function judge(a) {return a > 1} var arr = [1,23,4]; console.log(arr.some(judge)); // true 复制代码
  • filter 接收一个返回值为boolean 类型的函数,对数组中的每一个元素使用该函数,返回使用该函数后返回值为true的新数组集合。

function filterNum(a) {return a> 2} var arr = [1,45,56,2,5] console.log(arr.filter(filterNum)) // [45, 56, 5] 复制代码
  • reduce 接收一个函数,返回一个值。该方法会从一个累加值开始,不断对累加值和数组中的后续元素调用该函数,直到数组中的最后一个元素,最后返回得到的累加值。(reduceRight 遍历的顺序为倒序)

function add(a, b) {return a +b } var arr = [1,3,4,5,6] console.log(arr.reduce(add, 0))  //19 复制代码
  • map 接收一个函数作为参数,对数组中的每一个元素使用该函数,返回一个执行了该函数的数组集合,该方法也不改变原数组

function mapNum(a) {return a + 1 } var arr = [2,4,56] console.log(arr.map(mapNum))   // [3,5,57] 复制代码

针对对象 ,我们经常使用for in进行迭代, 也可以使用 Object.keys等方式进行迭代

var a = {b: 1, c: 2} for(var i in a) {     console.log(i) //b, c } Object.keys(a) // b, c 复制代码
在设计underscore框架的迭代器时我们需要考虑的是, 如何扩展方法让迭代器适用于数组,对象,或者带有length属性的arguments类数组以及字符串。并且从实现角度讲,为了兼容低版本的浏览器,我们需要抛弃ES5规范下便捷的方法,使用最常规的for循环进行数组,类数组,字符串的遍历。同样,也可以通过for循环来遍历对象。
3.2 _.reduce - _.reduce(list, iteratee, [memo], [context])

首先从最复杂的reduce入手,reduce的基本功能前面在数组的迭代器方法中已经介绍,我们只重点关注实现的细节。其中iteratee 为迭代器函数,该函数有四个参数memo,value 和 迭代的index(或者 key)和最后一个引用的整个 list。

reduce 和 reduceRight 唯一的区别在于遍历顺序,一个从左往右, 一个从右往左,因此可以用同一个函数来设计reduce。其中 context 改变this的指向我们稍后分析。

 _.reduce = createReduce(1); _.reduceRight = createReduce(-1); var createReduce = function(dir) {     // dir 来区分     return function (obj, iteratee, meno, context) {         // 两种类型,对象和类数组需要区别处理方便for循环遍历, 对象我们需要拿到所有的属性集合,数组,类数组我们关注的是下标。         // 巧妙点:当为数组,类数组时 keys = false, 当为对象时 keys = 属性数组          var keys = !isArrayLike(obj) && _.keys(obj)         var lengths = (keys || obj).length;                  // 处理遍历方向,即参数dir的值         var index = dir > 0 ? 0 : length-1;         for (; index >= 0 && index < lengths; index += dir) {             // 如果是数组,类数组则取下标,如果是对象则取属性值             var currentKey = keys ? keys[index] : index;             // 执行迭代器函数,并把返回值赋值给meno,继续循环迭代             meno = iteratee(meno, obj[currentKey], currentKey, obj);         }         return meno     } } 复制代码

reduce 函数在使用的时候,meno是可选项,如果没有传递meno, 则自动会把list 中的第一个元素赋值给meno。因此我们可以将处理的核心代码抽离为一个独立的函数,并将是否有meno的初始值做独立判断。

_.reduce = createReduce(1); _.reduceRight = createReduce(-1); var createReduce = function(dir) {     // dir 来区分     var reducer = function (obj, iteratee, meno, context, initial) {         ···         // 增加meno 的初始值判断赋值         if (!initial) {           memo = obj[keys ? keys[index] : index];           index += dir;         }         ···     }     return function (obj, iteratee, meno, context) {         // 记录是否有meno传值         var initial = arguments.length >=3;         return reducer(obj, iteratee, meno, context, initial)     } } 复制代码

reduce的实现已经基本完成,然而依然留着一个悬念,那就是context可以改变this的指向。underscore源码中单独将context改变this指向的方法抽离成一个独立的函数optimizeCb,该方法可以兼容underscore中 所有需要改变this指向的过程, reduce 为其中一种情形,函数调用call方法改变this指向所传递的参数分别为 meno, value, index, list

var createReduce = function(dir) {     ···     return function(obj, iteratee, meno, context) {         ···         return reducer(obj, optimizeCb(iteratee, context, 4), initial) //optimizeCb优化     } } var optimizeCb = function(func, context, argCount) {     // 当没有特定的this指向时, 返回原函数     if(context == void 0) return func;     switch(argCount) {         // 针对reduce函数的context指向         case 4: return function(context, meno, value, index, list) {             return func.call(context, meno, value, index, list);         }     } } 复制代码
3.3 map - _.map(list, iteratee, [context])

underscore 中map 方法原理上会生成一个新的数组,该数组与目标源数组,类数组的length属性相同,或者与对象的自身可枚举属性个数相同,因此有了reduce 的基础,我们可以简单的实现map 方法

_.map = function (obj, iteratee, context) {     var keys = !isArrayLike(obj) && _.keys(obj)     var lengths = (keys || obj).length;     // 生成一个个数和目标源数组个数相同,或者目标源对象自身可枚举属性个数相同的数组     var result = new Array(lengths);     for(var i=0; i<lengths;i++) {         var currentKey = keys ? keys[i] : i;         results =  iteratee(obj[currentKey], currentKey, obj)     } } 复制代码

对于map的使用,我们可以不传递iteratee迭代器,可以传递一个函数类型的迭代器,也可以传递一个对象类型作为迭代器,因此需要在进入迭代过程之前做一层过滤,根据不同的迭代器类型做不同的操作。

_.map = function(obj, iteratee, context) {     // 迭代器类型分类     iteratee = cb(iteratee, context);     ··· } 复制代码
  • 1.当不传递迭代器时,如_.map(obj) 会返回obj本身,因此需要定义另一个方法,该方法返回与传入参数相等的值。

var cb = function(iteratee, context) {     if(iteratee == null) return _.identity } _.identity = function(value) {     return value } 复制代码
  • 2.当传递的迭代器为函数时,可以直接进入optimizeCb的迭代器优化过程。

var cb = function(iteratee, context) {     if( _.isFunction(iteratee) ) return optimizeCb(iteratee, context, 3) // 此时类型为3 } // 完善optimizeCb 函数 var optimizeCb = function(func, context, argCount) {     // 当没有特定的this指向时, 返回原函数     if(context == void 0) return func;     switch(argCount) {         case 3: return function(context, value, index, list) {             return func.call(context, value, index, list);         }         case 4: return function(context, meno, value, index, list) {             return func.call(context, meno, value, index, list);         }      } } 复制代码
  • 3.当传递的迭代器为对象时会返回一个断言函数,这个具体的内容我们将在以后的篇幅分析。

var cb = function(iteratee, context) {     if (_.isObject(value) && !_.isArray(value)) return _.matcher(value); // 返回断言函数 } 复制代码
3.4 _.times - _.times(n, iteratee, [context])

_.times也是underscore提供的迭代器,它会调用迭代器n次,每次调用时传递index作为参数,最终结果返回一个执行结果的数组。例如:

console.log(_.times(n, function(i) {return i * 2 })) // [0, 1, 4] 复制代码

简单的实现如下,关键点在注释中说明:

_.times = function(n, iteratee, context) {     // 必须保证n的值是比0 大的数字     var n = Math.max(0, n);     // 创建一个个数为n的新数组     var arr = new Array(n);     for(var i = 0; i<n;i++) {         arr[i] = iteratee(i)         }     return arr      } 复制代码

同样,涉及context 改变this指向,我们同样可以通过optimizeCb进行优化,此时完善optimizeCb函数

_.times = function(n, iteratee, context) {     ···     iteratee = optimizeCb(iteratee, context, 1) } // 完善optimizeCb 函数 var optimizeCb = function(func, context, argCount) {     // 当没有特定的this指向时, 返回原函数     if(context == void 0) return func;     switch(argCount) {         case 1: return function(context, value) {             return func.call(context, value)         }         case 3: return function(context, value, index, list) {             return func.call(context, value, index, list);         }         case 4: return function(context, meno, value, index, list) {             return func.call(context, meno, value, index, list);         }      } } 复制代码

通过列举三种迭代器的设计,我们不但掌握了underscore迭代器设计的基本思想,也对中间核心optimizeCb函数设计的可能进行了枚举,underscore中optimizeCb函数优化的类型只有三种,对应的数值分别为1,3,4(注意:并没有2的类别)。我们也对迭代器的三种类型进行了枚举,在cb函数中分别区分了不传递迭代器,迭代器自身为数组,对象时的处理。掌握了optimizeCb和cb两个函数的设计思想,在设计剩余的迭代器时难度便小了很多。 由于篇幅过长,underscore中其他迭代器的实现我们放到下一节阐述。


作者:不做祖国的韭菜
原文链接:https://juejin.im/post/5c466ee1e51d45520a76b225

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP