上一篇文章寫了jQuery總體架構
,學習 jQuery 源碼總體架構,打造屬於本身的 js 類庫前端
雖然看過挺多underscore.js
分析類的文章,但總感受少點什麼。這也許就是紙上得來終覺淺,絕知此事要躬行吧。因而決定本身寫一篇學習underscore.js
總體架構的文章。node
本文章學習的版本是v1.9.1
。unpkg.com
源碼地址:https://unpkg.com/underscore@...git
雖然不少人都沒用過underscore.js
,但看下官方文檔都應該知道如何使用。github
從一個官方文檔_.chain
簡單例子看起:面試
_.chain([1, 2, 3]).reverse().value(); // => [3, 2, 1]
看例子中能夠看出,這是支持鏈式調用。編程
讀者也能夠順着文章思路,自行打開下載源碼進行調試,這樣印象更加深入。小程序
_.chain
函數源碼:segmentfault
_.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; };
這個函數比較簡單,就是傳遞obj
調用_()
。但返回值變量居然是instance
實例對象。添加屬性_chain
賦值爲true
,並返回intance
對象。但再看例子,實例對象居然能夠調用reverse
方法,再調用value
方法。猜想支持OOP
(面向對象)調用。微信小程序
帶着問題,筆者看了下定義 _
函數對象的代碼。api
_
函數對象 支持OOP
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; };
若是參數obj
已是_
的實例了,則返回obj
。
若是this
不是_
的實例,則手動 new _(obj)
;
再次new
調用時,把obj
對象賦值給_wrapped
這個屬性。
也就是說最後獲得的實例對象是這樣的結構
`{
_wrapped: '參數obj',
}`
它的原型_(obj).__proto__
是 _.prototype
;
若是對這塊不熟悉的讀者,能夠看下如下這張圖(以前寫面試官問:JS的繼承
畫的圖)。
繼續分析官方的_.chain
例子。這個例子拆開,寫成三步。
var part1 = _.chain([1, 2, 3]); var part2 = part1.reverse(); var part3 = part2.value(); // 沒有後續part1.reverse()操做的狀況下 console.log(part1); // {__wrapped: [1, 2, 3], _chain: true} console.log(part2); // {__wrapped: [3, 2, 1], _chain: true} console.log(part3); // [3, 2, 1]
思考問題:reverse
本是Array.prototype
上的方法呀。爲啥支持鏈式調用呢。
搜索reverse
,能夠看到以下這段代碼:
並將例子代入這段代碼可得(怎麼有種高中作數學題的既視感^_^):
_.chain([1,2,3]).reverse().value()
var ArrayProto = Array.prototype; // 遍歷 數組 Array.prototype 的這些方法,賦值到 _.prototype 上 _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { // 這裏的`method`是 reverse 函數 var method = ArrayProto[name]; _.prototype[name] = function() { // 這裏的obj 就是數組 [1, 2, 3] var obj = this._wrapped; // arguments 是參數集合,指定reverse 的this指向爲obj,參數爲arguments, 並執行這個函數函數。執行後 obj 則是 [3, 2, 1] method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; // 重點在於這裏 chainResult 函數。 return chainResult(this, obj); }; });
// Helper function to continue chaining intermediate results. var chainResult = function(instance, obj) { // 若是實例中有_chain 爲 true 這個屬性,則返回實例 支持鏈式調用的實例對象 { _chain: true, this._wrapped: [3, 2, 1] },不然直接返回這個對象[3, 2, 1]。 return instance._chain ? _(obj).chain() : obj; };
if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
提一下上面源碼中的這一句,看到這句是百思不得其解。因而趕忙在github
中搜索這句加上""
雙引號。表示所有搜索。
搜索到兩個在官方庫中的ISSUE
,大概意思就是兼容IE低版本的寫法。有興趣的能夠點擊去看看。
I don't understand the meaning of this sentence.
[why delete obj[0]](https://github.com/jashkenas/...
至此就算是分析完了鏈式調用_.chain()
和_
函數對象。這種把數據存儲在實例對象{_wrapped: '', _chain: true}
中,_chain
判斷是否支持鏈式調用,來傳遞給下一個函數處理。這種作法叫作 基於流的編程。
最後數據處理完,要返回這個數據怎麼辦呢。underscore
提供了一個value
的方法。
_.prototype.value = function(){ return this._wrapped; }
順便提供了幾個別名。toJSON
、valueOf
。
_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
還提供了 toString
的方法。
_.prototype.toString = function() { return String(this._wrapped); };
這裏的String()
和new String()
效果是同樣的。
能夠猜想內部實現和 _
函數對象相似。
var String = function(){ if(!(this instanceOf String)) return new String(obj); }
var chainResult = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; };
細心的讀者會發現chainResult
函數中的_(obj).chain()
,是怎麼實現實現鏈式調用的呢。
而_(obj)
是返回的實例對象{_wrapped: obj}
呀。怎麼會有chain()
方法,確定有地方掛載了這個方法到_.prototype
上或者其餘操做,這就是_.mixin()
。
_.mixin
掛載全部的靜態方法到 _.prototype
, 也能夠掛載自定義的方法_.mixin
混入。但侵入性太強,常常容易出現覆蓋之類的問題。記得以前React
有mixin
功能,Vue
也有mixin
功能。但版本迭代更新後基本都是慢慢的都不推薦或者不支持mixin
。
_.mixin = function(obj) { // 遍歷對象上的全部方法 _.each(_.functions(obj), function(name) { // 好比 chain, obj['chain'] 函數,自定義的,則賦值到_[name] 上,func 就是該函數。也就是說自定義的方法,不只_函數對象上有,並且`_.prototype`上也有 var func = _[name] = obj[name]; _.prototype[name] = function() { // 處理的數據對象 var args = [this._wrapped]; // 處理的數據對象 和 arguments 結合 push.apply(args, arguments); // 鏈式調用 chain.apply(_, args) 參數又被加上了 _chain屬性,支持鏈式調用。 // _.chain = function(obj) { // var instance = _(obj); // instance._chain = true; // return instance; }; return chainResult(this, func.apply(_, args)); }; }); // 最終返回 _ 函數對象。 return _; }; _.mixin(_);
_mixin(_)
把靜態方法掛載到了_.prototype
上,也就是_.prototype.chain
方法 也就是 _.chain
方法。
因此_.chain(obj)
和_(obj).chain()
效果同樣,都能實現鏈式調用。
關於上述的鏈式調用,筆者畫了一張圖,所謂一圖勝千言。
掛載自定義方法:
舉個例子:
_.mixin({ log: function(){ console.log('哎呀,我被調用了'); } }) _.log() // 哎呀,我被調用了 _().log() // 哎呀,我被調用了
_.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); };
_.functions
和 _.methods
兩個方法,遍歷對象上的方法,放入一個數組,而且排序。返回排序後的數組。
underscore.js
究竟在_
和_.prototype
掛載了多少方法和屬性再來看下underscore.js
究竟掛載在_函數對象
上有多少靜態方法和屬性,和掛載_.prototype
上有多少方法和屬性。
使用for in
循環一試便知。看以下代碼:
var staticMethods = []; var staticProperty = []; for(var name in _){ if(typeof _[name] === 'function'){ staticMethods.push(name); } else{ staticProperty.push(name); } } console.log(staticProperty); // ["VERSION", "templateSettings"] 兩個 console.log(staticMethods); // ["after", "all", "allKeys", "any", "assign", ...] 138個
var prototypeMethods = []; var prototypeProperty = []; for(var name in _.prototype){ if(typeof _.prototype[name] === 'function'){ prototypeMethods.push(name); } else{ prototypeProperty.push(name); } } console.log(prototypeProperty); // [] console.log(prototypeMethods); // ["after", "all", "allKeys", "any", "assign", ...] 152個
根據這些,筆者又畫了一張圖underscore.js
原型關係圖,畢竟一圖勝千言。
(function(){ }());
這樣保證不污染外界環境,同時隔離外界環境,不是外界影響內部環境。
外界訪問不到裏面的變量和函數,裏面能夠訪問到外界的變量,但裏面定義了本身的變量,則不會訪問外界的變量。
匿名函數將代碼包裹在裏面,防止與其餘代碼衝突和污染全局環境。
關於自執行函數不是很瞭解的讀者能夠參看這篇文章。
[[譯] JavaScript:當即執行函數表達式(IIFE)](https://segmentfault.com/a/11...
var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this || {};
支持瀏覽器環境
、node
、Web Worker
、node vm
、微信小程序
。
if (typeof exports != 'undefined' && !exports.nodeType) { if (typeof module != 'undefined' && !module.nodeType && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; }
關於root處理
和導出
的這兩段代碼的解釋,推薦看這篇文章冴羽:underscore 系列之如何寫本身的 underscore,講得真的太好了。筆者在此就不贅述了。
總之,underscore.js
做者對這些處理也不是一蹴而就的,也是慢慢積累,和其餘人提ISSUE
以後不斷改進的。
amd
模塊化規範if (typeof define == 'function' && define.amd) { define('underscore', [], function() { return _; }); }
源碼:
// 暫存在 root 上, 執行noConflict時再賦值回來 var previousUnderscore = root._; _.noConflict = function() { root._ = previousUnderscore; return this; };
使用:
<script> var _ = '我就是我,不同的煙火,其餘可不要覆蓋我呀'; </script> <script src="https://unpkg.com/underscore@1.9.1/underscore.js"> </script> <script> var underscore = _.noConflict(); console.log(_); // '我就是我,不同的煙火,其餘可不要覆蓋我呀' underscore.isArray([]) // true </script>
全文根據官網提供的鏈式調用的例子, _.chain([1, 2, 3]).reverse().value();
較爲深刻的調試和追蹤代碼,分析鏈式調用(_.chain()
和 _(obj).chain()
)、OOP
、基於流式編程、和_.mixin(_)
在_.prototype
掛載方法,最後總體架構分析。學習Underscore.js
總體架構,利於打造屬於本身的函數式編程類庫。
文章分析的源碼總體結構。
(function() { var root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || this || {}; var previousUnderscore = root._; var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; if (typeof exports != 'undefined' && !exports.nodeType) { if (typeof module != 'undefined' && !module.nodeType && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } _.VERSION = '1.9.1'; _.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; }; var chainResult = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; _.mixin = function(obj) { _.each(_.functions(obj), function(name) { var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return chainResult(this, func.apply(_, args)); }; }); return _; }; _.mixin(_); _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; return chainResult(this, obj); }; }); _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return chainResult(this, method.apply(this._wrapped, arguments)); }; }); _.prototype.value = function() { return this._wrapped; }; _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; _.prototype.toString = function() { return String(this._wrapped); }; if (typeof define == 'function' && define.amd) { define('underscore', [], function() { return _; }); } }());
下一篇文章但是學習lodash
的源碼總體架構。學習 lodash 源碼總體架構,打造屬於本身的函數式編程類庫
讀者發現有不妥或可改善之處,歡迎評論指出。另外以爲寫得不錯,能夠點贊、評論、轉發,也是對筆者的一種支持。
underscorejs.org 官網
undersercore-analysis
underscore 系列之如何寫本身的 underscore
學習 jQuery 源碼總體架構,打造屬於本身的 js 類庫
面試官問:JS的繼承
面試官問:JS的this指向
面試官問:可否模擬實現JS的call和apply方法
面試官問:可否模擬實現JS的bind方法
面試官問:可否模擬實現JS的new操做符
前端使用puppeteer 爬蟲生成《React.js 小書》PDF併合並
做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。
我的博客 https://lxchuan12.github.io
github blog,相關源碼和資源都放在這裏,求個star
^_^~
可能比較有趣的微信公衆號,長按掃碼關注。也能夠加微信 lxchuan12
,註明來源,拉您進【前端視野交流羣】。