博客 / 詳情

返回

underscore 誕生記(二)—— 鏈式調用與混入(mixin)

clipboard.png

上篇文章講述了 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)

user avatar zzd41 頭像 huishou 頭像 ziyeliufeng 頭像 flymon 頭像 shaochuancs 頭像 tingzhong666 頭像 niumingxin 頭像 pangsir8983 頭像 icezero 頭像 gfeteam 頭像 beilee 頭像 tofrankie 頭像
36 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.