学习underscore源码整体架构,打造属于自己的函数式编程类库
前言
上一篇文章寫了 jQuery整體架構(gòu),學習 jQuery 源碼整體架構(gòu),打造屬于自己的 js 類庫
雖然看過挺多 underscore.js分析類的文章,但總感覺少點什么。這也許就是紙上得來終覺淺,絕知此事要躬行吧。于是決定自己寫一篇學習 underscore.js整體架構(gòu)的文章。
本文章學習的版本是 v1.9.1。unpkg.com源碼地址:https://unpkg.com/underscore@1.9.1/underscore.js
雖然很多人都沒用過 underscore.js,但看下官方文檔都應該知道如何使用。
從一個官方文檔 _.chain簡單例子看起:
_.chain([1, 2, 3]).reverse().value(); // => [3, 2, 1]看例子中可以看出,這是支持鏈式調(diào)用。
讀者也可以順著文章思路,自行打開下載源碼進行調(diào)試,這樣印象更加深刻。
鏈式調(diào)用
_.chain 函數(shù)源碼:
_.chain = function(obj) {var instance = _(obj);instance._chain = true;return instance; };這個函數(shù)比較簡單,就是傳遞 obj調(diào)用 _()。但返回值變量竟然是 instance實例對象。添加屬性 _chain賦值為 true,并返回 intance對象。但再看例子,實例對象竟然可以調(diào)用 reverse方法,再調(diào)用 value方法。猜測支持 OOP(面向?qū)ο?#xff09;調(diào)用。
帶著問題,筆者看了下定義 _ 函數(shù)對象的代碼。
_?函數(shù)對象 支持?OOP
var _ = function(obj) {if (obj instanceof _) return obj;if (!(this instanceof _)) return new _(obj);this._wrapped = obj; };如果參數(shù) obj已經(jīng)是 _的實例了,則返回 obj。如果 this不是 _的實例,則手動 new_(obj); 再次 new調(diào)用時,把 obj對象賦值給 _wrapped這個屬性。也就是說最后得到的實例對象是這樣的結(jié)構(gòu) {_wrapped:'參數(shù)obj',}它的原型 _(obj).__proto__ 是 _.prototype;
如果對這塊不熟悉的讀者,可以看下以下這張圖(之前寫面試官問:JS的繼承畫的圖)。
繼續(xù)分析官方的 _.chain例子。這個例子拆開,寫成三步。
var part1 = _.chain([1, 2, 3]); var part2 = part1.reverse(); var part3 = part2.value(); // 沒有后續(xù)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上的方法呀。為啥支持鏈式調(diào)用呢。搜索 reverse,可以看到如下這段代碼:
并將例子代入這段代碼可得(怎么有種高中做數(shù)學題的既視感^_^):
_.chain([1,2,3]).reverse().value()s var ArrayProto = Array.prototype; // 遍歷 數(shù)組 Array.prototype 的這些方法,賦值到 _.prototype 上 _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {// 這里的`method`是 reverse 函數(shù)var method = ArrayProto[name];_.prototype[name] = function() {// 這里的obj 就是數(shù)組 [1, 2, 3]var obj = this._wrapped;// arguments 是參數(shù)集合,指定reverse 的this指向為obj,參數(shù)為arguments, 并執(zhí)行這個函數(shù)函數(shù)。執(zhí)行后 obj 則是 [3, 2, 1]method.apply(obj, arguments);if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];// 重點在于這里 chainResult 函數(shù)。return chainResult(this, obj);}; }); // Helper function to continue chaining intermediate results. var chainResult = function(instance, obj) {// 如果實例中有_chain 為 true 這個屬性,則返回實例 支持鏈式調(diào)用的實例對象 { _chain: true, this._wrapped: [3, 2, 1] },否則直接返回這個對象[3, 2, 1]。return instance._chain ? _(obj).chain() : obj; };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]
基于流的編程
至此就算是分析完了鏈式調(diào)用 _.chain()和 _ 函數(shù)對象。這種把數(shù)據(jù)存儲在實例對象 {_wrapped:'',_chain:true} 中, _chain判斷是否支持鏈式調(diào)用,來傳遞給下一個函數(shù)處理。這種做法叫做 基于流的編程。
最后數(shù)據(jù)處理完,要返回這個數(shù)據(jù)怎么辦呢。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() 和 newString() 效果是一樣的。可以猜測內(nèi)部實現(xiàn)和 _函數(shù)對象類似。
var String = function(){if(!(this instanceOf String)) return new String(obj); } var chainResult = function(instance, obj) {return instance._chain ? _(obj).chain() : obj; };細心的讀者會發(fā)現(xiàn) chainResult函數(shù)中的 _(obj).chain(),是怎么實現(xiàn)實現(xiàn)鏈式調(diào)用的呢。
而 _(obj)是返回的實例對象 {_wrapped:obj}呀。怎么會有 chain()方法,肯定有地方掛載了這個方法到 _.prototype上或者其他操作,這就是 _.mixin()。
_.mixin?掛載所有的靜態(tài)方法到?_.prototype, 也可以掛載自定義的方法
_.mixin 混入。但侵入性太強,經(jīng)常容易出現(xiàn)覆蓋之類的問題。記得之前 React有 mixin功能, Vue也有 mixin功能。但版本迭代更新后基本都是慢慢的都不推薦或者不支持 mixin。
_.mixin = function(obj) {// 遍歷對象上的所有方法_.each(_.functions(obj), function(name) {// 比如 chain, obj['chain'] 函數(shù),自定義的,則賦值到_[name] 上,func 就是該函數(shù)。也就是說自定義的方法,不僅_函數(shù)對象上有,而且`_.prototype`上也有var func = _[name] = obj[name];_.prototype[name] = function() {// 處理的數(shù)據(jù)對象var args = [this._wrapped];// 處理的數(shù)據(jù)對象 和 arguments 結(jié)合push.apply(args, arguments);// 鏈式調(diào)用 chain.apply(_, args) 參數(shù)又被加上了 _chain屬性,支持鏈式調(diào)用。// _.chain = function(obj) {// var instance = _(obj);// instance._chain = true;// return instance;};return chainResult(this, func.apply(_, args));};});// 最終返回 _ 函數(shù)對象。return _; }; _.mixin(_);_mixin(_) 把靜態(tài)方法掛載到了 _.prototype上,也就是 _.prototype.chain方法 也就是 _.chain方法。
所以 _.chain(obj)和 _(obj).chain()效果一樣,都能實現(xiàn)鏈式調(diào)用。
關于上述的鏈式調(diào)用,筆者畫了一張圖,所謂一圖勝千言。
_.mixin 掛載自定義方法
掛載自定義方法:舉個例子:
_.mixin({log: function(){console.log('哎呀,我被調(diào)用了');} }) _.log() // 哎呀,我被調(diào)用了 _().log() // 哎呀,我被調(diào)用了_.functions(obj)
_.functions = _.methods = function(obj) {var names = [];for (var key in obj) {if (_.isFunction(obj[key])) names.push(key);}return names.sort(); };_.functions 和 _.methods 兩個方法,遍歷對象上的方法,放入一個數(shù)組,并且排序。返回排序后的數(shù)組。
underscore.js?究竟在?_和?_.prototype掛載了多少方法和屬性
再來看下 underscore.js究竟掛載在 _函數(shù)對象上有多少靜態(tài)方法和屬性,和掛載 _.prototype上有多少方法和屬性。
使用 forin循環(huán)一試遍知。看如下代碼:
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個根據(jù)這些,筆者又畫了一張圖 underscore.js 原型關系圖,畢竟一圖勝千言。
整體架構(gòu)概覽
匿名函數(shù)自執(zhí)行
(function(){ }());這樣保證不污染外界環(huán)境,同時隔離外界環(huán)境,不是外界影響內(nèi)部環(huán)境。
外界訪問不到里面的變量和函數(shù),里面可以訪問到外界的變量,但里面定義了自己的變量,則不會訪問外界的變量。匿名函數(shù)將代碼包裹在里面,防止與其他代碼沖突和污染全局環(huán)境。關于自執(zhí)行函數(shù)不是很了解的讀者可以參看這篇文章。[譯] JavaScript:立即執(zhí)行函數(shù)表達式(IIFE)
root 處理
var root = typeof self == 'object' && self.self === self && self ||typeof global == 'object' && global.global === global && global ||this ||{};支持 瀏覽器環(huán)境、 node、 WebWorker、 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?模塊化規(guī)范
if (typeof define == 'function' && define.amd) {define('underscore', [], function() {return _;}); }_.noConflict 防沖突函數(shù)
源碼:
// 暫存在 root 上, 執(zhí)行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>總結(jié)
全文根據(jù)官網(wǎng)提供的鏈式調(diào)用的例子, _.chain([1,2,3]).reverse().value();較為深入的調(diào)試和追蹤代碼,分析鏈式調(diào)用( _.chain() 和 _(obj
).chain())、 OOP、基于流式編程、和 _.mixin(_)在 _.prototype掛載方法,最后整體架構(gòu)分析。學習 underscore.js整體架構(gòu),利于打造屬于自己的函數(shù)式編程類庫。
文章分析的源碼整體結(jié)構(gòu)。
(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的源碼整體架構(gòu)。
讀者發(fā)現(xiàn)有不妥或可改善之處,歡迎評論指出。另外覺得寫得不錯,可以點贊、評論、轉(zhuǎn)發(fā),也是對筆者的一種支持。
關于
作者:常以若川為名混跡于江湖。前端路上 | PPT愛好者 | 所知甚少,唯善學。
個人博客 http://lxchuan12.cn
https://github.com/lxchuan12/blog,相關源碼和資源都放在這里,求個 star^_^~
微信交流群,加我微信lxchuan12,注明來源,拉您進前端視野交流群
下圖是公眾號二維碼:若川視野,一個可能比較有趣的前端開發(fā)類公眾號
往期文章
工作一年后,我有些感悟(寫于2017年)
高考七年后、工作三年后的感悟
學習 jQuery 源碼整體架構(gòu),打造屬于自己的 js 類庫
由于公眾號限制外鏈,點擊閱讀原文,或許閱讀體驗更佳
總結(jié)
以上是生活随笔為你收集整理的学习underscore源码整体架构,打造属于自己的函数式编程类库的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(3067):vue+eleme
- 下一篇: react学习(21)---接口加回调显