underscore作為開發(fā)中比較常用的一個javascript工具庫,提供了一套豐富的函數(shù)式編程功能垮兑,該庫并沒有拓展原有的javascript原生對象,而是在自定義的
_
對象上漱挎,提供了100多個方法函數(shù)系枪。在這個系列中,將從uderscore源碼角度磕谅, 打造一個自己的underscore框架
一,框架設(shè)計(jì)
1.1 自執(zhí)行函數(shù)
現(xiàn)代js 庫的框架設(shè)計(jì)私爷,一般都是以自執(zhí)行函數(shù)的形式,自執(zhí)行函數(shù)一般有兩種形式
(function(){
// 形式一
}())
(function(){
// 形式二
})()
我們知道膊夹,函數(shù)聲明的形式會掛載到window對象作為方法存在衬浑,而函數(shù)表達(dá)式的形式則會掛載在window對象作為屬性存在,這都會造成變量污染放刨,而自執(zhí)行函數(shù)的好處在于可以防止變量的污染工秩,函數(shù)執(zhí)行完后便立刻銷毀掉。
1.2 使用風(fēng)格
underscore有兩種風(fēng)格形式可以使用,一種是面向?qū)ο箢愋屯刂睿硪环N是函數(shù)類型侵佃。
// 例子
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });
因此,在定義underscore類的時候需要考慮對象和函數(shù)兩種場景奠支。當(dāng)以函數(shù)的形式調(diào)用時需要把 _ 當(dāng)作一個構(gòu)造函數(shù)并返回他的實(shí)例化馋辈。代碼如下
(function(root){
var _ = function (obj) {
if (!(this instanceof _)) {
return new _(obj)
}
}
root._ = _
}(this))
1.3 使用環(huán)境
現(xiàn)在前端開發(fā)重視模塊化,以node服務(wù)端而論倍谜, 我們有commonjs規(guī)范迈螟,以客戶端而論,我們有AMD 和 CMD規(guī)范尔崔,對應(yīng)的模塊加載器為 requirejs 和 seajs答毫。目前通行的javascript模塊規(guī)范主要集中在commonjs 和 AMD,因此為了讓定義的underscore庫能夠適用于各種規(guī)范季春。在框架的定義時需檢測使用環(huán)境并兼容各種規(guī)范洗搂。
- 服務(wù)端:commonjs規(guī)范,檢測module.exports 是否存在载弄,滿足時通過 module.exports = {} 將 underscore暴露出來耘拇,不滿足則 通過window對象暴露出來。
- 客戶端: AMD 規(guī)范宇攻, 檢測 define.amd 是否存在惫叛,滿足時通過
define('**', [], function(){ return '***' })
暴露模塊
(function (root) {
var _ = function (obj) {
if (!(this instanceof _)) {
return new _(obj)
}
}
// commonjs 規(guī)范 檢測 module.exports 是否存在
if ((typeof module !== 'undefined' && module.exports)) {
module.exports = {
_: _
}
} else {
root._ = _;// window 對象暴露方法
}
// amd 規(guī)范,檢測 define.amd 是否存在
if (typeof define == 'function' && define.amd) {
define('underscore', [], function () {
return _;
});
}
}(this))
1.3.1 服務(wù)端使用
// commonjs
const _ = require('./underscore.js')
console.log(_)
1.3.2 客戶端使用
// AMD
require(['underscore'], function (underscore) {
console.log(underscore)
})
1.4 方法定義
underscore的調(diào)用逞刷,既可以通過_.unique()
,也可以通過 _().unique()
嘉涌,兩種方法效果相同卻需要在框架設(shè)計(jì)時定義兩套方法,一套是定義 _ 對象的靜態(tài)方法夸浅,另一套是擴(kuò)展 _對象原型鏈上的方法仑最。
_.uniqe = function() {}
_.prototype.unique = function() {}
為了避免冗余代碼,可以將定義好的靜態(tài)方法復(fù)制一份成為原型鏈上的方法
(function(root){
···
_.mixins = function() {
// 復(fù)制靜態(tài)方法到原型上
}
_.mixins() // 執(zhí)行方法
}(this))
mixins 方法的實(shí)現(xiàn)题篷,需要遍歷 underscore 對象上所有的靜態(tài)方法词身,因此需要先完成對 遍歷方法 _.each 的實(shí)現(xiàn)
1.41 _.each
_.each(list, iteratee, [context]) Alias: forEach
遍歷list中的所有元素,按順序用每個元素當(dāng)做參數(shù)調(diào)用 iteratee 函數(shù)番枚。如果傳遞了context參數(shù)法严,則把iteratee綁定到context對象上。每次調(diào)用iteratee都會傳遞三個參數(shù):(element, index, list)葫笼。如果list是個JavaScript對象深啤,iteratee的參數(shù)是 (value, key, list))。返回list以方便鏈?zhǔn)秸{(diào)用路星。
each 的第一個參數(shù)按照文檔可以支持 數(shù)組溯街,類數(shù)組诱桂,對象三種類型,數(shù)組類數(shù)組和對象在遍歷時的處理方式不同呈昔。前者回調(diào)函數(shù)處理的是 值和下標(biāo)挥等,后者處理的是 值和屬性。
// 判斷數(shù)組堤尾,類數(shù)組方法
(function(root) {
···
_.each = function (list, callback, context) {
// context 存在會改變callback 中this 的指向
var i = 0;
var key;
if (isArrayLikeLike(list)) { // 數(shù)組肝劲,類數(shù)組
for (var i = 0; i < list.length; i++) {
context ? callback.call(context, list[i], i, list) : callback(list[i], i, list)
}
} else { // 對象
for (key in list) {
context ? callback.call(context, list[key], key) : callback(list[key], key)
}
}
}
var isArrayLike = function (collection) {
// 返回參數(shù) collection 的 length 屬性值
var length = collection.length;
// length是數(shù)值,非負(fù)郭宝,且小于等于MAX_ARRAY_INDEX
// MAX_ARRAY_INDEX = Math.pow(2, 53) - 1
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
}
}(this))
1.4.2 _.mixin
mixin方法的設(shè)計(jì)辞槐,目的是為了在underscore原型對象上擴(kuò)展更多的方法,它既可以用來擴(kuò)展用戶自定義的方法粘室,比如
_.mixin({
capitalize: function(string) {
return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
}
});
_("fabio").capitalize();
=> "Fabio"
當(dāng)然也可以用來內(nèi)部拷貝靜態(tài)方法到原型鏈的方法上榄檬。
(function(root){
···
var push = Array.prototype.push
var _ = function (obj) {
if (!(this instanceof _)) {
return new _(obj)
}
this.wrap = obj // 存儲實(shí)例對象傳過來的參數(shù)
}
_.mixins = function (obj) {
_.each(obj, function (value, key) {
_.prototype[key] = function () {
var args = [this.wrap]
push.apply(args, arguments)
return value.apply(this, args)
}
})
}
_.mixins(_)
}(this))
其中關(guān)注點(diǎn)在arguments 的處理上,靜態(tài)方法需要傳遞目標(biāo)源作為方法的參數(shù) 即_.unique(目標(biāo)源, 回調(diào)函數(shù))
衔统,而實(shí)例方法的目標(biāo)源存儲在構(gòu)造對象的屬性中 鹿榜,即_(目標(biāo)源).unique(回調(diào)函數(shù))
,因此定義實(shí)例方法時需要合并屬性和回調(diào)函數(shù)。即Array.prorotype.push.apply([this.wrap], arguments)
,之后將他作為參數(shù)傳遞給靜態(tài)方法并返回處理結(jié)果缰冤。
將類數(shù)組轉(zhuǎn)成數(shù)組的方法
Array.prototype.slice.call(類數(shù)組)
var a = []; Array.prototype.push.apply(a, 類數(shù)組); console.log(a);
var a = []; Array.prototype.concat.apply(a, 類數(shù)組); console.log(a);
- ES6方法
Array.from(類數(shù)組)
- ES6擴(kuò)展運(yùn)算符
var args = [...類數(shù)組]
1.5 鏈?zhǔn)秸{(diào)用
1.5.1 _.chain()
返回一個封裝的對象. 在封裝的對象上調(diào)用方法會返回封裝的對象本身, 直道 value 方法調(diào)用為止犬缨。
underscore中方法的調(diào)用返回的是處理后的值,因此無法支持方法的鏈?zhǔn)秸{(diào)用棉浸。如果需要鏈?zhǔn)秸{(diào)用,需要使用chain()方法刺彩,chain的使用會使每次調(diào)用方法后返回underscore的實(shí)例對象迷郑,直到 調(diào)用value方法才停止返回。
(function(root){
···
// chain方法會返回 _ 實(shí)例创倔,并且標(biāo)注該實(shí)例是否允許鏈?zhǔn)秸{(diào)用的
_.chain = function(obj) {
var instance = _(obj);
instance.chain = true;
return instance
}
// 增加是否支持鏈?zhǔn)秸{(diào)用的判斷嗡害,如果支持,則返回該實(shí)例畦攘,不支持則直接返回結(jié)果霸妹,
var chainResult = function (instance, obj) {
return instance.chain ? _(obj).chain() : obj
}
_.mixins = function (obj) {
_.each(obj, function (value, key) {
_.prototype[key] = function () {
var args = [this.wrap]
push.apply(args, arguments)
return chainResult(this, value.apply(this, args)) // 修改實(shí)例方法的返回值,返回值通過chainResult 包裝知押,根據(jù)chainResult的判斷結(jié)果改變返回值
}
})
}
}(this))
1.5.2 value()
因?yàn)殒準(zhǔn)秸{(diào)用會使underscore的方法返回他的實(shí)例對象叹螟,所以當(dāng)需要結(jié)束這一調(diào)用行為時,需要使用value()台盯。 value()方法會返回調(diào)用的結(jié)果罢绽。
(function(root){
···
_.value = function(instance) {
return instance.wrap
}
}(this))
未完待續(xù)。静盅。良价。
本文為博主原創(chuàng)文章,轉(zhuǎn)載請注明出處 https://www.cnblogs.com/kidflash/p/10077643.html