摘要:譯立即執行函數表達式處理支持瀏覽器環境微信小程序。學習整體架構,利于打造屬于自己的函數式編程類庫。下一篇文章可能是學習的源碼整體架構。也可以加微信,注明來源,拉您進前端視野交流群。
前言
上一篇文章寫了jQuery整體架構,學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫
雖然看過挺多underscore.js分析類的文章,但總感覺少點什么。這也許就是紙上得來終覺淺,絕知此事要躬行吧。于是決定自己寫一篇學習underscore.js整體架構的文章。
本文章學習的版本是v1.9.1。
unpkg.com源碼地址:https://unpkg.com/underscore@...
雖然很多人都沒用過underscore.js,但看下官方文檔都應該知道如何使用。
從一個官方文檔_.chain簡單例子看起:
_.chain([1, 2, 3]).reverse().value(); // => [3, 2, 1]
看例子中可以看出,這是支持鏈式調用。
讀者也可以順著文章思路,自行打開下載源碼進行調試,這樣印象更加深刻。
鏈式調用_.chain 函數源碼:
_.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; };
這個函數比較簡單,就是傳遞obj調用_()。但返回值變量竟然是instance實例對象。添加屬性_chain賦值為true,并返回intance對象。但再看例子,實例對象竟然可以調用reverse方法,再調用value方法。猜測支持OOP(面向對象)調用。
帶著問題,筆者看了下定義 _ 函數對象的代碼。
_ 函數對象 支持OOPvar _ = 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 掛載自定義方法掛載自定義方法:
舉個例子:
_.mixin({ log: function(){ console.log("哎呀,我被調用了"); } }) _.log() // 哎呀,我被調用了 _().log() // 哎呀,我被調用了_.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 兩個方法,遍歷對象上的方法,放入一個數組,并且排序。返回排序后的數組。
underscore.js 究竟在_和_.prototype掛載了多少方法和屬性再來看下underscore.js究竟掛載在_函數對象上有多少靜態方法和屬性,和掛載_.prototype上有多少方法和屬性。
使用for in循環一試遍知??慈缦麓a:
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之后不斷改進的。
if (typeof define == "function" && define.amd) { define("underscore", [], function() { return _; }); }_.noConflict 防沖突函數
源碼:
// 暫存在 root 上, 執行noConflict時再賦值回來 var previousUnderscore = root._; _.noConflict = function() { root._ = previousUnderscore; return this; };
使用:
總結
全文根據官網提供的鏈式調用的例子, _.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的源碼整體架構。
讀者發現有不妥或可改善之處,歡迎評論指出。另外覺得寫得不錯,可以點贊、評論、轉發,也是對筆者的一種支持。
推薦閱讀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,注明來源,拉您進【前端視野交流群】。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106558.html
摘要:匿名函數將代碼包裹在里面,防止與其他代碼沖突和污染全局環境。學習整體架構,打造屬于自己的函數式編程類庫讀者發現有不妥或可改善之處,歡迎評論指出。 雖然現在基本不怎么使用jQuery了,但jQuery流行10多年的JS庫,還是有必要學習它的源碼的。也可以學著打造屬于自己的js類庫,求職面試時可以增色不少。 本文章學習的是v3.4.1 版本。unpkg.com源碼地址:https://un...
摘要:目前通行的模塊規范主要集中在和,因此為了讓定義的庫能夠適用于各種規范。在框架的定義時需檢測使用環境并兼容各種規范。服務端規范,檢測是否存在,滿足時通過將暴露出來,不滿足則通過對象暴露出來。前者回調函數處理的是值和下標,后者處理的是值和屬性。 本文為博主原創文章,轉載請注明出處 https://www.cnblogs.com/kidfl... underscore作為開發中比較常用的一個...
摘要:他指示了一個對象的屬性,返回的將用來獲得該屬性對應的值在上面的分析中,我們知道,當傳入的是一個函數時,還要經過一個叫的內置函數才能獲得最終的所以此處的必然是優化回調的作用了。 開篇說明 對的,讓你所見,又開始造輪子了。哈哈,造輪子我們是認真的~ 源碼閱讀是必須的,Underscore是因為剛剛學習整理了一波函數式編程,加上自己曾經沒有太多閱讀源碼的經驗,先拿Underscore練練手,...
摘要:插件開發前端掘金作者原文地址譯者插件是為應用添加全局功能的一種強大而且簡單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內優雅的實現文件分片斷點續傳。 Vue.js 插件開發 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應用添加全局功能的一種強大而且簡單的方式。插....
閱讀 2241·2023-04-26 01:50
閱讀 706·2021-09-22 15:20
閱讀 2579·2019-08-30 15:53
閱讀 1585·2019-08-30 12:49
閱讀 1704·2019-08-26 14:05
閱讀 2700·2019-08-26 11:42
閱讀 2298·2019-08-26 10:40
閱讀 2587·2019-08-26 10:38