上篇文章讲述了 underscore 的基本结构搭建,本文继续讲链式调用与混入。

如果你还没看过第一篇文章,请点击 “underscore 诞生记(一)—— 基本结构搭建”

链式调用

在 JQuery 中,我们经常使用到链式调用,如:

$('.div')  .css('color', 'red')  .show();

那么在 underscore 中,是否支持链式调用呢?答案是支持的,只不过默认不开启链式调用罢了。

想要实现链式调用,通常我们会在支持链式调用的函数中返回对象本身:

let car = {  run(name) {    console.log(`${name}老司机开车啦喂!`);    return this;  },  stop() {    console.log('车停了');  },};car.run('奔驰').stop();// 奔驰老司机开车啦喂!// 车停了

那么在每个 _ 方法下都 return this , 显然不大优雅缺乏可控性!尝试着写个通用方法 chain() 开启链式调用。

_.chain = function(obj) {  // 获得一个经underscore包裹后的实例  var instance = _(obj);  // 标识当前实例支持链式调用  instance._chain = true;  return instance;};// 小试牛刀_.chain([1, 2, 3]);/* {    _chain: true,    _wrapped: [1, 2, 3]} */

返回的为一个实例对象,后面的方法判断 _chain 属性是否为 true,为 true 的话再调用一次 chain() 方法再返回原来实例即可。我们在之前用于给 prototype 复制方法的 each() 函数加入判断吧

var ArrayProto = Array.prototype;var push = ArrayProto.push;_.each(_.functions(_), function(name) {  var func = _[name];  _.prototype[name] = function() {    var args = [this._wrapped];    // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现    push.apply(args, arguments);    return this._chain ? _(func.apply(_, args)).chain() : func.apply(_, args);  };});

有点冗长,将 return this._chain ? _(func.apply(_, args)).chain() : func.apply(_, args); 改造下,

// 判断是否需要链式调用var chainResult = function(instance, obj) {  return instance._chain ? _(obj).chain() : obj;};var ArrayProto = Array.prototype;var push = ArrayProto.push;_.each(_.functions(_), function(name) {  var func = _[name];  _.prototype[name] = function() {    var args = [this._wrapped];    // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现    push.apply(args, arguments);    return chainResult(this, func.apply(_, args));  };});

好了,试试看效果:

_.chain([1, 2, 3])  .each(function(item) {    console.log(item);  })  .each(function(item) {    console.log(item);  });// 1 2 3 1 2 3// {_wrapped: [1,2,3], _chain: true}

混入(mixin)

underscore 很强大,功能也很齐全,但有时候也不能满足所有人的需求。我们想创建一些方法,让它挂载在 _ 上,这样我们全局也可以调用到这些方法,作为一款强大的方法库,也应该提供这种接口,让用户自定添加方法,ok, let us do it !

我们先定义一个 mixin 方法

_.mixin = function(obj) {};// `obj` 为一个类似 `_` 的对象。传入的这个对象,也需要遍历一次,并且复制方法于 prototype 属性上。详细代码如下:_.mixin = function(obj) {  _.each(_.functions(obj), function(name) {    var func = (_[name] = obj[name]);    _.prototype[name] = function() {      var args = [this._wrapped];      push.apply(args, arguments);      // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现      return chainResult(this, func.apply(_, args));    };  });  return _;};

看到这里,你会发现,我们在方法的最后遍历赋值给_.prototype方法,其实就是一次mixin() 的调用.

_.each(_.functions(_), function(name) {  var func = _[name];  _.prototype[name] = function() {    var args = [this._wrapped];    // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现    push.apply(args, arguments);    return func.apply(_, args);  };});// 简化为_.mixin(_);

最终代码

(function() {  // root 为挂载对象,为 self 或 global 或 this 或 {}  var root =    (typeof self == 'object' && self.self === self && self) ||    (typeof global == 'object' && global.global === global && global) ||    this ||    {};  var _ = function(obj) {    // 如果传入的是实例后对象,返回它    if (obj instanceof _) return obj;    // 如果还没有实例化,new _(obj)    if (!(this instanceof _)) return new _(obj);    this._wrapped = obj;  };  // 最大数值  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;  var ArrayProto = Array.prototype;  var push = ArrayProto.push;  // 判断是否为数组  var isArrayLike = function(collection) {    var length = collection.length;    return (      typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX    );  };  // 判断是否需要链式调用  var chainResult = function(instance, obj) {    return instance._chain ? _(obj).chain() : obj;  };  root._ = _;  _.VERSION = '1.9.1'; // 给我们的 underscore 一个版本号吧  /**   * 字符串倒装   */  _.reverse = function(string) {    return string      .split('')      .reverse()      .join('');  };  /**   * 判断是否为 function   */  _.isFunction = function(obj) {    return typeof obj == 'function' || false;  };  // 链式调用方法  _.chain = function(obj) {    // 获得一个经underscore包裹后的实例    var instance = _(obj);    // 标识当前实例支持链式调用    instance._chain = true;    return instance;  };  /**   * 获取_的所有属性函数名   */  _.functions = function(obj) {    var names = [];    for (var key in obj) {      if (_.isFunction(obj[key])) names.push(key);    }    return names.sort();  };  /**   * 数组或对象遍历方法,并返回修改后的对象或数组   * @param iteratee 回调函数   * @param context 回调函数中this的指向   */  _.map = function(obj, iteratee, context) {    var length = obj.length,      results = Array(length);    for (var index = 0; index < length; index++) {      results[index] = iteratee.call(context, obj[index], index, obj);    }    return results;  };  /**   * 数组或对象遍历方法   */  _.each = function(obj, callback) {    var length,      i = 0;    if (isArrayLike(obj)) {      // 数组      length = obj.length;      for (; i < length; i++) {        //   这里隐式的调用了一次 callback.call(obj[i], obj[i], i);        if (callback.call(obj[i], obj[i], i) === false) {          break;        }      }    } else {      // 对象      for (i in obj) {        if (callback.call(obj[i], obj[i], i) === false) {          break;        }      }    }    return obj;  };  /*   * 混入方法 mixin   */  _.mixin = function(obj) {    _.each(_.functions(obj), function(name) {      var func = (_[name] = obj[name]);      _.prototype[name] = function() {        var args = [this._wrapped];        push.apply(args, arguments);        // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现        return chainResult(this, func.apply(_, args));      };    });    return _;  };  _.mixin(_);})();

未完待续,静待下篇

前端进阶小书(advanced_front_end)

前端每日一题(daily-question)

webpack4 搭建 Vue 应用(createVue)