學習underscore源碼總體架構,打造屬於本身的函數式編程類庫

前言node

上一篇文章寫了 jQuery總體架構,學習 jQuery 源碼總體架構,打造屬於本身的 js 類庫git

雖然看過挺多 underscore.js分析類的文章,但總感受少點什麼。這也許就是紙上得來終覺淺,絕知此事要躬行吧。因而決定本身寫一篇學習 underscore.js總體架構的文章。github

本文章學習的版本是 v1.9.1。unpkg.com源碼地址:https://unpkg.com/underscore@1.9.1/underscore.js面試

雖然不少人都沒用過 underscore.js,但看下官方文檔都應該知道如何使用。編程

從一個官方文檔 _.chain簡單例子看起:小程序

  1. _.chain([1, 2, 3]).reverse().value();微信小程序

  2. // => [3, 2, 1]數組

看例子中能夠看出,這是支持鏈式調用。瀏覽器

讀者也能夠順着文章思路,自行打開下載源碼進行調試,這樣印象更加深入。微信

鏈式調用

_.chain 函數源碼:

  1. _.chain = function(obj) {

  2. var instance = _(obj);

  3. instance._chain = true;

  4. return instance;

  5. };

這個函數比較簡單,就是傳遞 obj調用 _()。但返回值變量居然是 instance實例對象。添加屬性 _chain賦值爲 true,並返回 intance對象。但再看例子,實例對象居然能夠調用 reverse方法,再調用 value方法。猜想支持 OOP(面向對象)調用。

帶着問題,筆者看了下定義 _ 函數對象的代碼。

_ 函數對象 支持 OOP

  1. var _ = function(obj) {

  2. if (obj instanceof _) return obj;

  3. if (!(this instanceof _)) return new _(obj);

  4. this._wrapped = obj;

  5. };

若是參數 obj已是 _的實例了,則返回 obj。若是 this不是 _的實例,則手動 new_(obj); 再次 new調用時,把 obj對象賦值給 _wrapped這個屬性。也就是說最後獲得的實例對象是這樣的結構 {_wrapped:'參數obj',}它的原型 _(obj).__proto__ 是 _.prototype;

若是對這塊不熟悉的讀者,能夠看下如下這張圖(以前寫面試官問:JS的繼承畫的圖)。watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

繼續分析官方的 _.chain例子。這個例子拆開,寫成三步。

  1. var part1 = _.chain([1, 2, 3]);

  2. var part2 = part1.reverse();

  3. var part3 = part2.value();

  4.  

  5. // 沒有後續part1.reverse()操做的狀況下

  6. console.log(part1); // {__wrapped: [1, 2, 3], _chain: true}

  7.  

  8. console.log(part2); // {__wrapped: [3, 2, 1], _chain: true}

  9.  

  10. console.log(part3); // [3, 2, 1]

思考問題:reverse本是 Array.prototype上的方法呀。爲啥支持鏈式調用呢。搜索 reverse,能夠看到以下這段代碼:

並將例子代入這段代碼可得(怎麼有種高中作數學題的既視感^_^):

  1. _.chain([1,2,3]).reverse().value()s

  1. var ArrayProto = Array.prototype;

  2. // 遍歷 數組 Array.prototype 的這些方法,賦值到 _.prototype 上

  3. _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {

  4. // 這裏的`method`是 reverse 函數

  5. var method = ArrayProto[name];

  6. _.prototype[name] = function() {

  7. // 這裏的obj 就是數組 [1, 2, 3]

  8. var obj = this._wrapped;

  9. // arguments 是參數集合,指定reverse 的this指向爲obj,參數爲arguments, 並執行這個函數函數。執行後 obj 則是 [3, 2, 1]

  10. method.apply(obj, arguments);

  11. if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];

  12. // 重點在於這裏 chainResult 函數。

  13. return chainResult(this, obj);

  14. };

  15. });

  1. // Helper function to continue chaining intermediate results.

  2. var chainResult = function(instance, obj) {

  3. // 若是實例中有_chain 爲 true 這個屬性,則返回實例 支持鏈式調用的實例對象 { _chain: true, this._wrapped: [3, 2, 1] },不然直接返回這個對象[3, 2, 1]。

  4. return instance._chain ? _(obj).chain() : obj;

  5. };

if((name==='shift'||name==='splice')&&obj.length===0)deleteobj[0];提一下上面源碼中的這一句,看到這句是百思不得其解。因而趕忙在 github中搜索這句加上 ""雙引號。表示所有搜索。

搜索到兩個在官方庫中的 ISSUE,大概意思就是兼容IE低版本的寫法。有興趣的能夠點擊去看看。

I don't understand the meaning of this sentence.

why delete obj[0]

基於流的編程

至此就算是分析完了鏈式調用 _.chain()和 _ 函數對象。這種把數據存儲在實例對象 {_wrapped:'',_chain:true} 中, _chain判斷是否支持鏈式調用,來傳遞給下一個函數處理。這種作法叫作 基於流的編程。

最後數據處理完,要返回這個數據怎麼辦呢。underscore提供了一個 value的方法。

  1. _.prototype.value = function(){

  2. return this._wrapped;

  3. }

順便提供了幾個別名。toJSON、 valueOf。_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

還提供了 toString的方法。

  1. _.prototype.toString = function() {

  2. return String(this._wrapped);

  3. };

這裏的 String() 和 newString() 效果是同樣的。能夠猜想內部實現和 _函數對象相似。

  1. var String = function(){

  2. if(!(this instanceOf String)) return new String(obj);

  3. }

  1. var chainResult = function(instance, obj) {

  2. return instance._chain ? _(obj).chain() : obj;

  3. };

細心的讀者會發現 chainResult函數中的 _(obj).chain(),是怎麼實現實現鏈式調用的呢。

而 _(obj)是返回的實例對象 {_wrapped:obj}呀。怎麼會有 chain()方法,確定有地方掛載了這個方法到 _.prototype上或者其餘操做,這就是 _.mixin()。

_.mixin 掛載全部的靜態方法到 _.prototype, 也能夠掛載自定義的方法

_.mixin 混入。但侵入性太強,常常容易出現覆蓋之類的問題。記得以前 React有 mixin功能, Vue也有 mixin功能。但版本迭代更新後基本都是慢慢的都不推薦或者不支持 mixin。

  1. _.mixin = function(obj) {

  2. // 遍歷對象上的全部方法

  3. _.each(_.functions(obj), function(name) {

  4. // 好比 chain, obj['chain'] 函數,自定義的,則賦值到_[name] 上,func 就是該函數。也就是說自定義的方法,不只_函數對象上有,並且`_.prototype`上也有

  5. var func = _[name] = obj[name];

  6. _.prototype[name] = function() {

  7. // 處理的數據對象

  8. var args = [this._wrapped];

  9. // 處理的數據對象 和 arguments 結合

  10. push.apply(args, arguments);

  11. // 鏈式調用 chain.apply(_, args) 參數又被加上了 _chain屬性,支持鏈式調用。

  12. // _.chain = function(obj) {

  13. // var instance = _(obj);

  14. // instance._chain = true;

  15. // return instance;

  16. };

  17. return chainResult(this, func.apply(_, args));

  18. };

  19. });

  20. // 最終返回 _ 函數對象。

  21. return _;

  22. };

  23.  

  24. _.mixin(_);

_mixin(_) 把靜態方法掛載到了 _.prototype上,也就是 _.prototype.chain方法 也就是 _.chain方法。

因此 _.chain(obj)和 _(obj).chain()效果同樣,都能實現鏈式調用。

關於上述的鏈式調用,筆者畫了一張圖,所謂一圖勝千言。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

_.mixin 掛載自定義方法

掛載自定義方法:舉個例子:

  1. _.mixin({

  2. log: function(){

  3. console.log('哎呀,我被調用了');

  4. }

  5. })

  6. _.log() // 哎呀,我被調用了

  7. _().log() // 哎呀,我被調用了

_.functions(obj)

  1. _.functions = _.methods = function(obj) {

  2. var names = [];

  3. for (var key in obj) {

  4. if (_.isFunction(obj[key])) names.push(key);

  5. }

  6. return names.sort();

  7. };

_.functions 和 _.methods 兩個方法,遍歷對象上的方法,放入一個數組,而且排序。返回排序後的數組。

underscore.js 究竟在 _和 _.prototype掛載了多少方法和屬性

再來看下 underscore.js究竟掛載在 _函數對象上有多少靜態方法和屬性,和掛載 _.prototype上有多少方法和屬性。

使用 forin循環一試遍知。看以下代碼:

  1. var staticMethods = [];

  2. var staticProperty = [];

  3. for(var name in _){

  4. if(typeof _[name] === 'function'){

  5. staticMethods.push(name);

  6. }

  7. else{

  8. staticProperty.push(name);

  9. }

  10. }

  11. console.log(staticProperty); // ["VERSION", "templateSettings"] 兩個

  12. console.log(staticMethods); // ["after", "all", "allKeys", "any", "assign", ...] 138個

  1. var prototypeMethods = [];

  2. var prototypeProperty = [];

  3. for(var name in _.prototype){

  4. if(typeof _.prototype[name] === 'function'){

  5. prototypeMethods.push(name);

  6. }

  7. else{

  8. prototypeProperty.push(name);

  9. }

  10. }

  11. console.log(prototypeProperty); // []

  12. console.log(prototypeMethods); // ["after", "all", "allKeys", "any", "assign", ...] 152個

根據這些,筆者又畫了一張圖 underscore.js 原型關係圖,畢竟一圖勝千言。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

總體架構概覽

匿名函數自執行

  1. (function(){

  2.  

  3. }());

這樣保證不污染外界環境,同時隔離外界環境,不是外界影響內部環境。

外界訪問不到裏面的變量和函數,裏面能夠訪問到外界的變量,但裏面定義了本身的變量,則不會訪問外界的變量。匿名函數將代碼包裹在裏面,防止與其餘代碼衝突和污染全局環境。關於自執行函數不是很瞭解的讀者能夠參看這篇文章。[譯] JavaScript:當即執行函數表達式(IIFE)

root 處理

  1. var root = typeof self == 'object' && self.self === self && self ||

  2. typeof global == 'object' && global.global === global && global ||

  3. this ||

  4. {};

支持 瀏覽器環境、 node、 WebWorker、 node vm、 微信小程序。

導出

  1. if (typeof exports != 'undefined' && !exports.nodeType) {

  2. if (typeof module != 'undefined' && !module.nodeType && module.exports) {

  3. exports = module.exports = _;

  4. }

  5. exports._ = _;

  6. } else {

  7. root._ = _;

  8. }

關於 root處理和 導出的這兩段代碼的解釋,推薦看這篇文章冴羽:underscore 系列之如何寫本身的 underscore,講得真的太好了。筆者在此就不贅述了。總之, underscore.js做者對這些處理也不是一蹴而就的,也是慢慢積累,和其餘人提 ISSUE以後不斷改進的。

支持 amd 模塊化規範

  1. if (typeof define == 'function' && define.amd) {

  2. define('underscore', [], function() {

  3. return _;

  4. });

  5. }

_.noConflict 防衝突函數

源碼:

  1. // 暫存在 root 上, 執行noConflict時再賦值回來

  2. var previousUnderscore = root._;

  3. _.noConflict = function() {

  4. root._ = previousUnderscore;

  5. return this;

  6. };

使用:

  1. <script>

  2. var _ = '我就是我,不同的煙火,其餘可不要覆蓋我呀';

  3. </script>

  4. <script src="https://unpkg.com/underscore@1.9.1/underscore.js">

  5. </script>

  6. <script>

  7. var underscore = _.noConflict();

  8. console.log(_); // '我就是我,不同的煙火,其餘可不要覆蓋我呀'

  9. underscore.isArray([]) // true

  10. </script>

總結

全文根據官網提供的鏈式調用的例子, _.chain([1,2,3]).reverse().value();較爲深刻的調試和追蹤代碼,分析鏈式調用( _.chain() 和 _(obj

).chain())、 OOP、基於流式編程、和 _.mixin(_)在 _.prototype掛載方法,最後總體架構分析。學習 underscore.js總體架構,利於打造屬於本身的函數式編程類庫。

文章分析的源碼總體結構。

  1. (function() {

  2. var root = typeof self == 'object' && self.self === self && self ||

  3. typeof global == 'object' && global.global === global && global ||

  4. this ||

  5. {};

  6. var previousUnderscore = root._;

  7.  

  8. var _ = function(obj) {

  9. if (obj instanceof _) return obj;

  10. if (!(this instanceof _)) return new _(obj);

  11. this._wrapped = obj;

  12. };

  13.  

  14. if (typeof exports != 'undefined' && !exports.nodeType) {

  15. if (typeof module != 'undefined' && !module.nodeType && module.exports) {

  16. exports = module.exports = _;

  17. }

  18. exports._ = _;

  19. } else {

  20. root._ = _;

  21. }

  22. _.VERSION = '1.9.1';

  23.  

  24. _.chain = function(obj) {

  25. var instance = _(obj);

  26. instance._chain = true;

  27. return instance;

  28. };

  29.  

  30. var chainResult = function(instance, obj) {

  31. return instance._chain ? _(obj).chain() : obj;

  32. };

  33.  

  34. _.mixin = function(obj) {

  35. _.each(_.functions(obj), function(name) {

  36. var func = _[name] = obj[name];

  37. _.prototype[name] = function() {

  38. var args = [this._wrapped];

  39. push.apply(args, arguments);

  40. return chainResult(this, func.apply(_, args));

  41. };

  42. });

  43. return _;

  44. };

  45.  

  46. _.mixin(_);

  47.  

  48. _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {

  49. var method = ArrayProto[name];

  50. _.prototype[name] = function() {

  51. var obj = this._wrapped;

  52. method.apply(obj, arguments);

  53. if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];

  54. return chainResult(this, obj);

  55. };

  56. });

  57.  

  58. _.each(['concat', 'join', 'slice'], function(name) {

  59. var method = ArrayProto[name];

  60. _.prototype[name] = function() {

  61. return chainResult(this, method.apply(this._wrapped, arguments));

  62. };

  63. });

  64.  

  65. _.prototype.value = function() {

  66. return this._wrapped;

  67. };

  68.  

  69. _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

  70.  

  71. _.prototype.toString = function() {

  72. return String(this._wrapped);

  73. };

  74.  

  75. if (typeof define == 'function' && define.amd) {

  76. define('underscore', [], function() {

  77. return _;

  78. });

  79. }

  80. }());

下一篇文章多是學習 lodash的源碼總體架構。

 

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

相關文章
相關標籤/搜索