教你认清这8大杀手锏

媒介

underscore.js源码剖析第三篇,前两篇地点分别是

那些不起眼的小工具?

(void 0)与undefined之间的小九九

本篇原文链接

源码地点

?看了许多篇手艺文章,却依旧写不好前端。

《教你认清这8大杀手锏》

从步入递次猿这个大坑最先到现在,已看过数不清的手艺文章和书本,有的是零星的学问,有的是系列威望的教程,但为毛还写不好挚爱的前端,听说过一句话,这个天下又不是只需你一个人深爱而不得。但纵使云云,我也要手艺这条路上一同走到黑。直到天际迷了路,海角翻了船。

<!–more–>

最先

本日想说几个相似我们寻常的工作中常常用到的几个宝贝,权且把他叫做杀手锏好了,由于实在是迥殊好用呀,他们分别是…

  1. each

  2. map

  3. reduce

  4. reduceRight

  5. find

  6. filter

  7. every

  8. some

接下来我们从下划线underscore.js的视角,一步步看他们的内部运转的道理是什么….

1 _.each(list, iteratee, [context])

遍历list中的一切元素,按递次用遍历输出每一个元素,假如通报了context,则将iteratee函数中的this绑定到context上。

先来看一下如何运用


let arr = ['name', 'sex']
let obj = {
  name: 'qianlongo',
  sex: 'boy'
}

// 不传入context
// 遍历数组
_.each(arr, console.log) 
// name 0 (2) ["name", "sex"]
// sex 1 (2) ["name", "sex"]

// 遍历对象
_.each(obj, console.log)
// qianlongo name {name: "qianlongo", sex: "boy"}
// boy sex  {name: "qianlongo", sex: "boy"}


// 传入context
_.each(arr, function (val, key, arr) {
  console.log(this[val])
}, obj)
// qianlongo
// boy

能够看出下划线的each和原生的数组forEach有些相似也有差别的处所

原生的forEach只能够遍历数组,而下划线的each还能够遍历对象。接下来你想不想一同看下下划线是如何完成的。come on!!!

源码

_.each = _.forEach = function(obj, iteratee, context) {
  // 优化遍历函数iteratee,将iteratee中的this动态设置为context
  iteratee = optimizeCb(iteratee, context); 
  var i, length;
  if (isArrayLike(obj)) { // 假如是类数组范例的obj
    for (i = 0, length = obj.length; i < length; i++) {
      // iteratee吸收的三个参数分别是 数组的值,数组的索引,以及数组本身
      iteratee(obj[i], i, obj); 
    }
  } else { // 支撑对象范例的数据迭代
    var keys = _.keys(obj); // 拿到obj本身的一切keys
    for (i = 0, length = keys.length; i < length; i++) {
      // iteratee吸收的三个参数分别是 obj的属性值,obj的属性,obj本身
      iteratee(obj[keys[i]], keys[i], obj);
    }
  }
  return obj; // 末了将obj返回
};

?,实在也没有那末难明白是吧!最先map函数之旅吧

2 _.map(list, iteratee, [context])

经由历程iteratee将list中的每一个值映照到一个新的数组中(注:发生一个新的数组。y = f(x),相似高中学过的学问,将x经由历程f()映照为一个新的数

运用案例


let arr = ['qianlongo', 'boy']
let obj = {
  name: 'qianlongo',
  sex: 'boy'
}

// list是个数组的时刻
_.map(arr, (val, index) => {
  return `hello : ${val}`
})
// ["hello : qianlongo", "hello : boy"]

// list是个对象的时刻
_.map(obj, (val, key, obj) => {
  return `hello : ${val}`
})
// ["hello : qianlongo", "hello : boy"]

固然还能够传入第三个参数context,其本质如each平常,也是让iteratee函数中的this动态设置为context

源码


 _.map = _.collect = function(obj, iteratee, context) {
  // 能够将这里的内部cb函数明白为绑定iteratee的this到context
  iteratee = cb(iteratee, context);
  // 非类数组对象就猎取obj的keys,这里假如是类数组末了获得的keys为undefined
  var keys = !isArrayLike(obj) && _.keys(obj),
      length = (keys || obj).length,
      results = Array(length); // 建立一个和obj长度空间一样的数组
  for (var index = 0; index < length; index++) {
    // 注重这里,keys存在则代表obj是个对象,所以要拿到keys中的值,不然是类数组的话,直接用index索引就好了
    var currentKey = keys ? keys[index] : index;
    // 看到了吗,这里将iteratee实行后的返回值塞到了results数组中
    results[index] = iteratee(obj[currentKey], currentKey, obj);
  }
  return results; // 末了将映照以后的数组返回
};

经由历程源码能够看到map的完成思绪

  1. 建立一个行将返回的数组

  2. 遍历list(能够为数组也能够为对象),将list的元素输入到传进来的iteratee函数中,并将其实行后的返回值添补进数组。这个iteratee担任映照划定规矩

3 _.every(list, [predicate], [context])

当list中的一切的元素都能够经由历程predicate的检测,那末效果返回true,不然false

运用案例

let arr = [-1, -3, -6, 0, 3, 6, 9]
let obj = {
  name: 'qianlongo',
  sex: 'boy'
}

let result = _.every(arr, (val, key, arr) => {
  return val > 0
})
// false

let result2 = _.every(obj, (val, key, obj) => {
  return val.indexOf('o') > -1
})
// true

运用起来蛮简朴的,传入一个谓词函数(返回值是一个布尔值的函数),末了获得true或许false。

源码

_.every = _.all = function(obj, predicate, context) {
  // 能够将这里的内部cb函数明白为绑定iteratee的this到context
  predicate = cb(predicate, context);
  // 短路写法,非类数组则猎取其keys
  var keys = !isArrayLike(obj) && _.keys(obj),
      length = (keys || obj).length;
  for (var index = 0; index < length; index++) {
    // keys若能转化为"真" 则申明obj是对象范例
    var currentKey = keys ? keys[index] : index; 
    // 只需有一个不满足就返回false,中缀迭代
    if (!predicate(obj[currentKey], currentKey, obj)) return false;
  }
  return true; // 不然一切元素都经由历程推断返回true
};

4 _.some(list, [predicate], [context])

假如list中有任何一个元素经由历程 predicate的检测就返回true。不然返回false,和every正好有点相反的意义。

运用案例

let arr = [-1, -3, -6, 0, 3, 6, 9]
let obj = {
  name: 'qianlongo',
  sex: ''
}

let result = _.some(arr, (val, key, arr) => {
  return val > 0
})
// true 由于至少有一个元素 >0

let result2 = _.some(obj, (val, key, obj) => {
  return val.indexOf('o') > -1
})
// true 两个都包括'o' 固然返回true

源码中是如何完成的呢,与every唯一差别的处所在返回true照样falase的地方?

源码

_.some = _.any = function(obj, predicate, context) {
  predicate = cb(predicate, context);
  var keys = !isArrayLike(obj) && _.keys(obj),
      length = (keys || obj).length;
  for (var index = 0; index < length; index++) {
    var currentKey = keys ? keys[index] : index;
    if (predicate(obj[currentKey], currentKey, obj)) return true; // 只需有一个满足前提就返回true
  }
  return false; // 一切都不满足则返回false
};

5 _.find(list, predicate, [context])

遍历list中的元素,返回第一个经由历程predicate函数检测的值。

运用案例


let arr = [-1, -3, -6, 0, 3, 6, 9]
let obj = {
  sex: 'boy',
  name: 'qianlongo'
}
let result = _.find(arr, (val, key, arr) => {
  return val > 0
})
// 3
let result2 = _.find(obj, (val, key, obj) => {
  return val.indexOf('o') > -1
})
// boy

源码


_.find = _.detect = function(obj, predicate, context) {
  var key;
  if (isArrayLike(obj)) {
    // 当传入的是类数组的时刻,挪用findIndex要领,效果是>= -1的数组
    key = _.findIndex(obj, predicate, context);
  } else {
    // 当传入的是一个对象的时刻,挪用findKey,效果是一个字符串属性或许undefined
    key = _.findKey(obj, predicate, context);
  }
  // 返回相符前提的value,不然没有返回值,即默许的undefined
  if (key !== void 0 && key !== -1) return obj[key]; 
};

_.findIndex_.findKey在后面会逐一剖析,现在明白find函数晓得他们如何用就好。

6 _.filter(list, predicate, [context])

遍历list,返回包括一切经由历程predicate检测的元素(效果是个数组)

运用案例


let arr = [-1, -3, -6, 0, 3, 6, 9]
let obj = {
  sex: 'boy',
  name: 'qianlongo',
  age: 100
}
let result = _.filter(arr, (val, key, arr) => {
  return val > 0
})
// [3, 6, 9]
let result2 = _.filter(obj, (val, key, obj) => {
  return `${val}`.indexOf('o') > -1 // 运用模板字符串是防备100没有indexOf要领而报错
})
// ["boy", "qianlongo"]

智慧的你是否是已想到了源码是如何完成的了 ?

源码

_.filter = _.select = function(obj, predicate, context) {
  var results = [];
  // 绑定predicate的this作用域到context
  predicate = cb(predicate, context);
  // 用each要领对obj举行遍历
  _.each(obj, function(value, index, list) {
    // 相符predicate过滤前提的,就把对应的值塞到results数组中
    if (predicate(value, index, list)) results.push(value);
  });
  return results; // 末了返回
};

末了是reduce和reduceRight,两个相对来说更难一些的api,虽然已过了12点了,手动疲乏?, 我们咬咬牙对峙一下,把末了两个说完

7 _.reduce(list, iteratee, [memo], [context]),

别名为 inject 和 foldl, reduce要领把list中元素归结为一个零丁的数值。Memo是reduce函数的初始值,reduce的每一步都需要由iteratee返回。这个迭代通报4个参数:memo, value 和 迭代的index(或许 key)和末了一个援用的全部 list

8 _.reduceRight(list, iteratee, memo, [context])

reducRight是从右侧最先组合的元素的reduce函数

运用案例

var arr = [0, 1, 2, 3, 4, 5],
  sum = _.reduce(arr, (init, cur, i, arr) => {
    return init + cur;
  });    
  
  // 15

我们来看一下上面的实行历程是如何的。

第一回合

// 由于initialValue没有传入所以回调函数的第一个参数为数组的第一项
init = 0;
cur = 1;
=> init + cur = 1;

第二回合

init = 1;
cur = 2;
=> init + cur = 3;

第三回合

init = 3;
cur = 3;
=> init + cur = 6;

第四回合

init = 6;
cur = 4;
=> init + cur = 10;

第五回合

init = 10;
cur = 5;
=> init + cur = 15;

?妈妈啊,终究实行完了,这么多回合才完毕,哪像人家肉搏高手霎时就把太极巨匠整挂了

晓得了一步步实行流程,我们来看下源码究竟是如何完成的。

源码

// 源码照样经由历程挪用createReduce天生的,所以主如果看createReduce这个函数
_.reduce = _.foldl = _.inject = createReduce(1);

这尼玛看起来好吓人啊,不怕,我们一点点来剖析

function createReduce(dir) {
    // Optimized iterator function as using arguments.length
    // in the main function will deoptimize the, see #1991.
    function iterator(obj, iteratee, memo, keys, index, length) { // 真正实行迭代的处所
      for (; index >= 0 && index < length; index += dir) {
        var currentKey = keys ? keys[index] : index; // 假如keys存在则认为是obj情势的参数,所以读取keys中的属性值,不然类数组只需要读取索引index即可
        memo = iteratee(memo, obj[currentKey], currentKey, obj); // 接着就是实行外部传入的回调了,并将效果赋值为memo,也就是我们末了要到的值
      }
      return memo;
    }

    return function(obj, iteratee, memo, context) {
      iteratee = optimizeCb(iteratee, context, 4); // 起首绑定一下this作用域
      var keys = !isArrayLike(obj) && _.keys(obj), // 假如不是类数组就读取其keys
          length = (keys || obj).length,
          index = dir > 0 ? 0 : length - 1; // 默许最先迭代的位置,从左侧第一个最先照样右侧第一个
      // Determine the initial value if none is provided.
      if (arguments.length < 3) { // 假如没有传入初始化值,则将第一个值(左侧第一个或许右侧第一个)作为初始值
        memo = obj[keys ? keys[index] : index];
        index += dir; // 从索引为1最先或许索引为length - 2最先迭代
      }
      return iterator(obj, iteratee, memo, keys, index, length); // 接着最先进入自定义的迭代函数,请往上看
    };
  }

结语

夜深人静,有点疲乏了。愿望这篇文章对人人有点作用。假如对前几篇源码剖析的文章感兴趣,迎接前去顶部地点检察

不介意的话,在文章开首的源码地点那边点一个小星星吧?

不介意的话,在文章开首的源码地点那边点一个小星星吧?

不介意的话,在文章开首的源码地点那边点一个小星星吧?

    原文作者:谦龙
    原文地址: https://segmentfault.com/a/1190000009371089
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞