上篇文章講述了 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(_);
})();
未完待續,靜待下篇