上篇文章講述了 underscore 的基本結構搭建,本文繼續講鏈式調用與混入。css
若是你還沒看過第一篇文章,請點擊 「underscore 誕生記(一)—— 基本結構搭建」前端
在 JQuery 中,咱們常常使用到鏈式調用,如:webpack
$('.div') .css('color', 'red') .show();
那麼在 underscore 中,是否支持鏈式調用呢?答案是支持的,只不過默認不開啓鏈式調用罷了。git
想要實現鏈式調用,一般咱們會在支持鏈式調用的函數中返回對象自己:github
let car = { run(name) { console.log(`${name}老司機開車啦喂!`); return this; }, stop() { console.log('車停了'); }, }; car.run('奔馳').stop(); // 奔馳老司機開車啦喂! // 車停了
那麼在每一個 _
方法下都 return this
, 顯然不大優雅缺少可控性!嘗試着寫個通用方法 chain()
開啓鏈式調用。web
_.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() 函數加入判斷吧segmentfault
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)); }; });
好了,試試看效果:app
_.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}
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(_); })();