你好,我是若川。這是
學習源碼總體架構
第二篇。總體架構這詞語好像有點大,姑且就算是源碼總體結構吧,主要就是學習是代碼總體結構,不深究其餘不是主線的具體函數的實現。本篇文章學習的是打包整合後的代碼,不是實際倉庫中的拆分的代碼。前端
學習源碼總體架構
系列文章以下:vue
1.學習 jQuery 源碼總體架構,打造屬於本身的 js 類庫
2.學習 underscore 源碼總體架構,打造屬於本身的函數式編程類庫
3.學習 lodash 源碼總體架構,打造屬於本身的函數式編程類庫
4.學習 sentry 源碼總體架構,打造屬於本身的前端異常監控SDK
5.學習 vuex 源碼總體架構,打造屬於本身的狀態管理庫
6.學習 axios 源碼總體架構,打造屬於本身的請求庫
7.學習 koa 源碼的總體架構,淺析koa洋蔥模型原理和co原理
8.學習 redux 源碼總體架構,深刻理解 redux 及其中間件原理node
感興趣的讀者能夠點擊閱讀。ios
雖然看過挺多underscore.js
分析類的文章,但總感受少點什麼。這也許就是紙上得來終覺淺,絕知此事要躬行吧。因而決定本身寫一篇學習underscore.js
總體架構的文章。git
本文章學習的版本是v1.9.1
。 unpkg.com
源碼地址:https://unpkg.com/underscore@1.9.1/underscore.jsgithub
雖然不少人都沒用過underscore.js
,但看下官方文檔都應該知道如何使用。面試
從一個官方文檔_.chain
簡單例子看起:vuex
_.chain([1, 2, 3]).reverse().value(); // => [3, 2, 1] 複製代碼
看例子中能夠看出,這是支持鏈式調用。編程
讀者也能夠順着文章思路,自行打開下載源碼進行調試,這樣印象更加深入。redux
_.chain
函數源碼:
_.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; }; 複製代碼
這個函數比較簡單,就是傳遞obj
調用_()
。但返回值變量居然是instance
實例對象。添加屬性_chain
賦值爲true
,並返回intance
對象。但再看例子,實例對象居然能夠調用reverse
方法,再調用value
方法。猜想支持OOP
(面向對象)調用。
帶着問題,筆者看了下定義 _
函數對象的代碼。
_
函數對象 支持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.
至此就算是分析完了鏈式調用_.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
原型關係圖,畢竟一圖勝千言。
uunderscore.js
原型關係圖
(function(){ }()); 複製代碼
這樣保證不污染外界環境,同時隔離外界環境,不是外界影響內部環境。
外界訪問不到裏面的變量和函數,裏面能夠訪問到外界的變量,但裏面定義了本身的變量,則不會訪問外界的變量。 匿名函數將代碼包裹在裏面,防止與其餘代碼衝突和污染全局環境。 關於自執行函數不是很瞭解的讀者能夠參看這篇文章。 [譯] JavaScript:當即執行函數表達式(IIFE)
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
面試官問:JS的繼承
面試官問:JS的this指向
面試官問:可否模擬實現JS的call和apply方法
面試官問:可否模擬實現JS的bind方法
面試官問:可否模擬實現JS的new操做符
前端使用puppeteer 爬蟲生成《React.js 小書》PDF併合並
做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。
若川的博客,用vuepress
重構了,閱讀體驗可能好些
掘金專欄,歡迎關注~
segmentfault
前端視野專欄,開通了前端視野專欄,歡迎關注~
知乎前端視野專欄,開通了前端視野專欄,歡迎關注~
語雀前端視野專欄,新增語雀專欄,歡迎關注~
github blog,相關源碼和資源都放在這裏,求個star
^_^~
可能比較有趣的微信公衆號,長按掃碼關注。也能夠加微信 ruochuan12
,註明來源,拉您進【前端視野交流羣】。
本文使用 mdnice 排版