资讯专栏INFORMATION COLUMN

underscore源码学习(二)

Berwin / 1614人阅读

摘要:在中,真值检测函数的参数被命名为,有断言的意思,非常形象。函数的功能是检测一个对象或数组是否包含指定的某个元素。

顺着underscore源码的顺序读下来,弄懂了之前underscore的基本结构,接下来看看underscore为我们提供的一些关于集合的API。

迭代

关于迭代,我们都知道ES5原生方法也提供了迭代函数供我们使用,而在underscore中的迭代则是对原生的迭代函数进行了封装优化升级。在underscore中,迭代的对象不仅仅是数组对象,还支持Array,Object的迭代,对Object的迭代的依据是对象的键值对(key-value),看看 underscore中_.each是如何实现的:

</>复制代码

  1. /**
  2. * each方法将ES5的forEach换为了函数式表达
  3. * @param obj 待迭代集合
  4. * @param iteratee 迭代过程中每个被迭代元素的回调函数
  5. * @param context 上下文
  6. * @example
  7. * // 数组迭代
  8. * _.each([1, 2, 3], alert);
  9. * // 对象迭代
  10. * _.each({one: 1, two: 2, three: 3}, alert);
  11. */
  12. _.each = _.forEach = function (obj, iteratee, context) {
  13. //优化回调
  14. iteratee = optimizeCb(iteratee, context);
  15. var i, length;
  16. // 判断是数组还是对象
  17. if (isArrayLike(obj)) {
  18. for (i = 0, length = obj.length; i < length; i++) {
  19. iteratee(obj[i], i, obj)
  20. }
  21. } else {
  22. var keys = _.keys(obj)
  23. for (i = 0, length = keys.length; i < length; i++) {
  24. iteratee(obj[keys[i]], keys[i], obj)
  25. }
  26. }
  27. // 返回对象自身 以便于链式调用
  28. return obj
  29. };

看以上源码可知,_.each传入三个参数,主要的是第二个iteratee回调函数,然后再通过optimizeCb优化回调,返回对应的回调(optimizeCb可以查看第一部分)。
array迭代的是数组的每个元素,传入的三个参数分别为数组的值,对应值的下标,数组本身Object迭代的元素是对象的每个键值对key-value,传入的参数为对象的key所对应的值,对象的key值,对象本身

map-reduce

ES5原生方法也提供map和reduce方法,它们提供了一种对列表操作的思路,是函数式编程重要组成部分。具体map和reduce可以去MDN上查看相关API。

map在underscore中的实现

它的实现思路是:

返回一个新的列表或元素

对列表中的值进行遍历,用指定函数func作用于每个遍历的元素,输出一个新的值放到新的列表中

</>复制代码

  1. _.map = _.collect = function(obj, iteratee, context) {
  2. iteratee = cb(iteratee, context)
  3. //考虑数组和对象
  4. var keys = !isArrayLike(obj) && _.keys(obj),
  5. length = (keys || obj).length,
  6. results = Array(length) // 初始化定长数组
  7. for (var index = 0; index < length; index++) {
  8. var currentKey = keys ? keys[index] : index
  9. results[index] = iteratee(obj[currentKey], currentKey, obj)
  10. }
  11. return results
  12. }

使用用例:
对数组使用

</>复制代码

  1. var res = _.map([1,2,3], function(elem, index, array) {
  2. return elem * 2
  3. })
  4. // => [2,4,6]

对对象使用

</>复制代码

  1. var obj = {
  2. name: "lzb",
  3. age: "20",
  4. sex: "male"
  5. }
  6. var res = _.map(obj, function(value, key, obj) {
  7. return key
  8. })
  9. // => name age sex
reduce在underscore中的实现

reduce相对于map的实现复杂了一些,underscore首先在外部实现了reduce函数的工厂函数createReduce,这个函数实现了以下功能:

区分reduce的开始方向(参数dir),是从首端开始末端开始

memo记录最新的结果

reduce的执行过程大概是:

设置一个memo变量缓存当前规约过程的结果

如果用户为初始化memo,则memo的值为序列的第一个值

遍历当前集合,对当前遍历到的元素按传入的func进行规约操作,刷新memo

遍历结束,返回memo

createReduce的实现:

</>复制代码

  1. /**
  2. * reduce函数的工厂函数, 用于生成一个reducer, 通过参数决定reduce的方向
  3. * @param dir 方向 left or right
  4. * @returns {function}
  5. */
  6. function createReduce(dir) {
  7. function iterator(obj, iteratee, memo, keys, index, length) {
  8. for (; idnex > 0 && index < length; index += dir) {
  9. var currentKey = keys ? keys[index] : index
  10. // memo 用来记录最新的 reduce 结果
  11. // 执行 reduce 回调, 刷新当前值
  12. memo = iteratee(memo, obj[currentKey], currentKey, obj)
  13. }
  14. }
  15. /**
  16. * @param obj 传入的对象
  17. * @param iteratee 回调函数
  18. * @param memo 初始化累加器的值
  19. * @param context 执行上下文
  20. */
  21. return function (obj, iteratee, memo, context) {
  22. iteratee = optimizeCb(iteratee, context, 4)
  23. var keys = !isArrayLike(obj) && _.keys(obj),
  24. length = (keys || obj).length
  25. index = dir > 0 ? 0 : length - 1
  26. // 如果没有传入memo初始值 则从左第一个为初始值 从右则最后一个为初始值
  27. if (arguments.length < 3) {
  28. memo = obj[keys ? keys[index] : index]
  29. index += dir
  30. }
  31. return iterator(obj, iteratee, memo, keys, index, length)
  32. }
  33. }

最后,underscore暴露了两个供使用的方法

</>复制代码

  1. // 由左至右进行规约
  2. _.reduce = _.foldl = _.inject = createReduce(1);
  3. // 由右至左进行规约
  4. _.reduceRight = _.foldr = createReduce(-1);

使用用例:
对数组使用

</>复制代码

  1. var sum = _.reduce([1,2,3,4], function(prev, current, index, arr) {
  2. return prev + current
  3. }, 0)
  4. // => 10

对对象使用

</>复制代码

  1. var scores = {
  2. english: 93,
  3. math: 88,
  4. chinese: 100
  5. };
  6. var total = _.reduce(scores, function(prev, value, key, obj){
  7. return prev+value;
  8. }, 0);
  9. // => total: 281
真值检测函数

在underscore中,除了提供_.each,_.map._.reduce等函数操作集合,还提供了_.filter, _.reject, _.every, _.some这几个基于逻辑判断的集合操作函数。这些API都依赖于用户提供的真值检测函数来返回对应的结果。
在underscore中,真值检测函数的参数被命名为predicatepredicate有断言的意思,非常形象。当然,predicate依旧会通过cb优化。

_.filter

看看_.filter的实现

</>复制代码

  1. /**
  2. * 根据真值检测函数 过滤对象
  3. * 检测通过符合条件 保留元素
  4. * @param obj
  5. * @param predicate
  6. * @param context
  7. * @example
  8. * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
  9. * => [2, 4, 6]
  10. */
  11. _.filter = _.select = function (obj, predicate, context) {
  12. var results = []
  13. // 优化回调
  14. predicate = cb(predicate, context)
  15. _.each(obj, function (value, index, list) {
  16. if (predicate(value, index, list)) results.push(value)
  17. })
  18. return results
  19. }

根据传入的元素信息,检测并返回对应boolean值,决定当前元素要被保留。

_.reject

上面的_.filter函数是元素符合检测条件就保留,而_.reject函数则是与_.filter相反的功能
我们来看看underscore中_.reject的实现

</>复制代码

  1. /**
  2. * filter的反运算,
  3. * 如果真值检测通过, 元素被丢弃
  4. */
  5. _.reject = function (obj, predicate, context) {
  6. return _.filter(obj, _negate(cb(predicate)), context)
  7. }

可以看到,这个函数只有一行代码,非常简短。那么,这其中的_.negate函数又是什么呢?猜测下,negate在英语中有否定的意思,那么跟_.reject的功能就有了一定的联系, 下面看看_.negate的实现

</>复制代码

  1. _.negate = function(predicate) {
  2. return function() {
  3. return !predicate.apply(this, arguments)
  4. }
  5. }

可以看到,_.negate得到了反义predicate的执行结果,减少了大量重复的代码,值得学习。

_.every

迭代对象里的每个元素,只有每个元素都通过真值检测函数,才返回true

</>复制代码

  1. /**
  2. * @param obj
  3. * @param predicate
  4. * @param context
  5. * @example
  6. * _.every([true, 1, null, "yes"], _.identity);
  7. * => false
  8. */
  9. _.every = _.all = function (obj, predicate, context) {
  10. predicate = cb(predicate, context)
  11. var keys = !isArrayLike(obj) && _.keys(obj),
  12. length = (keys || obj).length
  13. for (var index = 0; index < length; index++) {
  14. var currentKey = keys ? keys[index] : index
  15. if (!predicate(obj[currentKey], currentKey, obj)) return false
  16. }
  17. return true
  18. }
_.some

这个API跟_.every差不多,从英语单词的含义我们也可以猜出它的功能,即迭代对象的所有元素,如果有任意一个通过真值检测,则返回true

</>复制代码

  1. /**
  2. * @param obj
  3. * @param predicate
  4. * @param context
  5. * @example
  6. * _.some([null, 0, "yes", false]);
  7. * => true
  8. */
  9. _.some = _.any = function (obj, predicate, context) {
  10. predicate = cb(predicate, context)
  11. var keys = !isArrayLike(obj) && _.keys(obj),
  12. length = (keys || obj).length
  13. for (var index = 0; index < length; index++) {
  14. var currentKey = keys ? keys[index] : index
  15. if (predicate(obj[currentKey], currentKey, obj)) return true
  16. }
  17. return false
  18. }
_.contains

_.contains函数的功能是检测一个对象或数组是否包含指定的某个元素。

</>复制代码

  1. /**
  2. * @param obj 待检测对象
  3. * @param item 指定的元素
  4. * @param fromIndex 从哪个位置开始找
  5. * @param guard
  6. * @example
  7. * _.contains([1,2,3], 3)
  8. * => true
  9. */
  10. _.contains = _.includes = _.include = function (obj, item, fromIndex, guard) {
  11. if (!isArrayLike(obj)) obj = _.values(obj)
  12. if (typeof fromIndex != "number" || guard) fromIndex = 0
  13. return _.indexOf(obj, item.fromIndex) >= 0
  14. }

从代码上看,还是比较容易理解的,这里主要用到了underscore内部提供的两个函数,_.values_.indexOf,从名字上我们也可以猜出它们之间的功能,如果传入的对象,则取出该对象所有的值,然后再进行查找比较,看看_values的实现:

</>复制代码

  1. /**
  2. * 获得一个对象的所有value
  3. * @param obj 对象
  4. * @returns {Array} 值序列
  5. * @example
  6. * _.values({one: 1, two: 2, three: 3});
  7. * // => [1, 2, 3]
  8. */
  9. _.values = function (obj) {
  10. var keys = _.keys(obj)
  11. var length = keys.length
  12. var values = Array(length)
  13. for (var i = 0; i < length; i++) {
  14. values[i] = obj[keys[i]]
  15. }
  16. return values
  17. }

_,indexOf的实现就比较复杂了,这是underscore中提供的关于查找的API,详细介绍将在下一篇总结写出。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88627.html

相关文章

  • 前端资源系列(4)-前端学习资源分享&前端面试资源汇总

    摘要:特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 本以为自己收藏的站点多,可以很快搞定,没想到一入汇总深似海。还有很多不足&遗漏的地方,欢迎补充。有错误的地方,还请斧正... 托管: welcome to git,欢迎交流,感谢star 有好友反应和斧正,会及时更新,平时业务工作时也会不定期更...

    princekin 评论0 收藏0
  • underscore源码学习(一)

    摘要:所以,刚开始,我从源码比较短的包含注释只有行开始学习起。一般,在客户端浏览器环境中,即为,暴露在全局中。学习以后判断直接使用看起来也优雅一点滑稽脸。在的函数视线中,的作用执行一个传入函数次,并返回由每次执行结果组成的数组。 前言 最近在社区浏览文章的时候,看到了一位大四学长在寻求前端工作中的面经,看完不得不佩服,掌握知识点真是全面,无论是前端后台还是其他,都有涉猎。 在他写的文章中,有...

    gclove 评论0 收藏0
  • underscore源码该如何阅读?

    摘要:所以它与其他系列的文章并不冲突,完全可以在阅读完这个系列后,再跟着其他系列的文章接着学习。如何阅读我在写系列的时候,被问的最多的问题就是该怎么阅读源码我想简单聊一下自己的思路。感谢大家的阅读和支持,我是冴羽,下个系列再见啦 前言 别名:《underscore 系列 8 篇正式完结!》 介绍 underscore 系列是我写的第三个系列,前两个系列分别是 JavaScript 深入系列、...

    weknow619 评论0 收藏0
  • Underscore源码解析(四)

    摘要:本文同步自我得博客我在这个系列的第一篇文章说过,我学是为了在学的时候少一些阻碍,从第一篇的写作时间到今天,大概也有个十几二十天,感觉拖得有点久,所以今天将会是源码解析系列的最后一篇文章,我会在这篇文章中介绍剩下的所有函数。 本文同步自我得博客:http://www.joeray61.com 我在这个系列的第一篇文章说过,我学underscore是为了在学backbone的时候少一些阻碍...

    高胜山 评论0 收藏0
  • Underscore源码解析(一)

    摘要:本文同步自我得博客最近准备折腾一下,在事先了解了之后,我知道了对这个库有着强依赖,正好之前也没使用过,于是我就想先把彻底了解一下,这样之后折腾的时候也少一点阻碍。 本文同步自我得博客:http://www.joeray61.com 最近准备折腾一下backbone.js,在事先了解了backbone之后,我知道了backbone对underscore这个库有着强依赖,正好undersc...

    neu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<