[关闭]
@gyyin 2018-03-17T17:33:46.000000Z 字数 2525 阅读 309

underscore源码分析之基础方法

underscore


本文是underscore源码剖析系列的第二篇,主要介绍underscore中一些基础方法的实现。

mixin

在上篇文章underscore整体架构分析中,我们讲过_上面的方法有两种挂载方式,一个是挂载到_构造函数上以_.map(arr)的形式直接调用(在后文上统称构造函数调用),另一种则是挂到_.prototype上以_(arr).map()的形式被实例调用(在后文上统称原型调用)

翻一遍underscore源码你会发现underscore中的方法都是直接挂到_构造函数上实现的,但是会通过mixin方法来将_上面的方法扩展到_.prototype上面,这样这些方法既可以直接调用,又可以通过实例来调用。

  1. _.mixin = function(obj) {
  2. // 遍历obj上所有的方法
  3. _.each(_.functions(obj), function(name) {
  4. // 保存方法的引用
  5. var func = _[name] = obj[name];
  6. _.prototype[name] = function() {
  7. // 将一开始传入的值放到数组中
  8. var args = [this._wrapped];
  9. // 将方法的参数一起push到数组中(这里处理的很好,保证了func方法参数的顺序)
  10. push.apply(args, arguments);
  11. // 这里先用apply方法执行了func,并将结果传给了result
  12. return result(this, func.apply(_, args));
  13. };
  14. });
  15. };
  16. _.mixin(_);

从这段代码中我们可以看出,mixin方法将_上的所有方法通过遍历的形式挂载到了_.prototype上面。

细心观察一下,构造函数调用和原型调用的区别在哪里?
没错,区别就在于调用方式和传参,构造函数调用时一般会把要处理的值当做第一个参数传入,而原型调用的时候会把要处理的值传入_构造函数来创建一个实例。

  1. var arr = [1, 2, 3]
  2. var func = function(item) {
  3. console.log(item);
  4. }
  5. // 构造函数调用时arr被传入第一个参数
  6. _.each(arr, func)
  7. // 原型调用的时候,arr被当做参数传给_方法来创建一个实例
  8. _(arr).each(func)
  9. // 链式调用,和上面类似
  10. _.chain(arr).each(func)

从上一节中我们知道,在创建一个_的实例时,会用this._wrapped将传入的值保存起来,所以在mixin里面这一句:var args = [this._wrapped];是将我们传给_的值放到args数组第一项中,之后再将arguments也放入args数组中,借助apply方法执行当前遍历的方法(在这个例子中是each),这个时候传给each方法的是arr和func,正好和原来直接_.each调用each传入参数的顺序是一样的(underscore中的方法第一项基本上都是要处理的数据)。

链式调用

那么上面最后return result(this, func.apply(_, args)),result又是做什么的呢?

首先来看result源码:

  1. var result = function(instance, obj) {
  2. // 首先判断是否使用链式调用,如果是,那就继续将刚刚执行后返回的结果链式调用一下,如果不是,则直接返回执行后的结果
  3. return instance._chain ? _(obj).chain() : obj;
  4. };
  5. _.chain = function(obj) {
  6. // 创建一个实例
  7. var instance = _(obj);
  8. // 给这个实例加个_chain属性来表明这是链式调用
  9. instance._chain = true;
  10. return instance;
  11. };

我们知道underscore中也是有和jQuery类似的链式调用,来看一下链式调用的例子:

  1. var arr = [1, 2, 3]
  2. var newArr = _.chain(a).map(function(item) {
  3. return item + 1
  4. }).filter(function(item) {
  5. return item > 2
  6. }).value()

链式调用的关键在于每次执行方法后都需要返回一个实例,以确保能够继续调用其他方法。
chain方法会用传入的obj创建一个_的实例,这个实例可以调用原型上的方法。从上面mixin的实现来看,每次调用原型方法后会将执行后的结果传给result方法,在result内部会判断你是否使用了链式调用(chain),如果是链式的,那么就会将返回结果链式化(传入chain中创建新的实例)。
链式调用一定要在结尾执行value方法,不然最后返回的是一个对象(最后一次创建的_实例)

数组函数

underscore构造方法上面并没有直接对push、pop、shift等数组方法进行实现,但是链式调用的时候往往需要用到这些方法,所以在原型上对这些方法做了一些封装,实现方法和mixin类似,这里不再多做解释。

  1. _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
  2. var method = ArrayProto[name];
  3. _.prototype[name] = function() {
  4. var obj = this._wrapped;
  5. method.apply(obj, arguments);
  6. // 这句是好像是为了解决ie上的bug?
  7. if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
  8. return result(this, obj);
  9. };
  10. });
  11. _.each(['concat', 'join', 'slice'], function(name) {
  12. var method = ArrayProto[name];
  13. _.prototype[name] = function() {
  14. return result(this, method.apply(this._wrapped, arguments));
  15. };
  16. });
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注