學習underscore源碼整體架構,打造屬于自己的函數式編程類庫

前言

上一篇文章寫了 jQuery整體架構,學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫

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

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

雖然很多人都沒用過 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(面向對象)調用。

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

_?函數對象 支持?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()s
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)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的方法。

_.prototype.value = function(){return this._wrapped;
}

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

還提供了 toString的方法。

_.prototype.toString = function() {return String(this._wrapped);
};

這里的 String()newString() 效果是一樣的。可以猜測內部實現和 _函數對象類似。

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 混入。但侵入性太強,經常容易出現覆蓋之類的問題。記得之前 Reactmixin功能, 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上有多少方法和屬性。

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

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)

root 處理

var root = typeof self == 'object' && self.self === self && self ||typeof global == 'object' && global.global === global && global ||this ||{};

支持 瀏覽器環境nodeWebWorkernode 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 _;});
}

_.noConflict 防沖突函數

源碼:

// 暫存在 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的源碼整體架構。

讀者發現有不妥或可改善之處,歡迎評論指出。另外覺得寫得不錯,可以點贊、評論、轉發,也是對筆者的一種支持。

關于

作者:常以若川為名混跡于江湖。前端路上 | PPT愛好者 | 所知甚少,唯善學。
個人博客 http://lxchuan12.cn
https://github.com/lxchuan12/blog,相關源碼和資源都放在這里,求個 star^_^~

微信交流群,加我微信lxchuan12,注明來源,拉您進前端視野交流群

下圖是公眾號二維碼:若川視野,一個可能比較有趣的前端開發類公眾號

往期文章

工作一年后,我有些感悟(寫于2017年)

高考七年后、工作三年后的感悟

學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫

由于公眾號限制外鏈,點擊閱讀原文,或許閱讀體驗更佳

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276520.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276520.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276520.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python xlsx 大文件_Python這樣操作能存儲100多萬行的xlsx文件!Python讓你事半功倍!

(1) 如果excel文件是xls&#xff0c;2003版的&#xff0c;使用xlrd和xlwt庫來對xls文件進行操作(2) 如果excel文件是xlsx&#xff0c;2007以上版的&#xff0c;使用openpyxl庫來對xlsx文件進行操作Tips:xlrd、xlwt和openpyxl非python自帶庫&#xff0c;需要進行安裝&#xff0c…

linux 如何在命令行下改系統時間

我們一般使用“date -s”命令來修改系統時間。比如將系統時間設定成2009年6月1日的命令如下。   #date -s 06/01/2009 或#date -s 20090601 將系統時間設定成下午15點43分0秒的命令如下。   #date -s 15:43:00   注意&#xff0c;這里說的是系統…

拓撲目的 1.Pc9通過van3訪問pc10 2.Pc9通過Vlan1\Vlan2訪問pc11

1拓撲圖2設置路由器R12的接口的IPint g0/0/0ip address 192.168.20.254 24undo shutdown int g0/0/01ip address 192.168.1.1 24undo shutdownint g2/0/00ip address 192.168.3.1 24undo shutdown 3設置路由器R10的接口的IPint g0/0/0ip address 192.168.2.1 24undo shutdownin…

PHP 發送Email的幾種方法

轉載鏈接&#xff1a;http://blog.009it.com/php/75.html 在php中發送Email可以直接調用系統的mail()函數來完成&#xff0c;但是前提是你在php.ini文件中對mail都已經配置好了&#xff0c;以下為相關的配置信息&#xff1a; [mail function] ; For Win32 only. SMTP localho…

學習 lodash 源碼整體架構,打造屬于自己的函數式編程類庫

前言這是 學習源碼整體架構系列第三篇。整體架構這詞語好像有點大&#xff0c;姑且就算是源碼整體結構吧&#xff0c;主要就是學習是代碼整體結構&#xff0c;不深究其他不是主線的具體函數的實現。文章學習的是打包整合后的代碼&#xff0c;不是實際倉庫中的拆分的代碼。上上篇…

python數據庫模糊查詢_Python操作mongodb數據庫進行模糊查詢操作示例

本文實例講述了Python操作mongodb數據庫進行模糊查詢操作。分享給大家供大家參考&#xff0c;具體如下&#xff1a;# -*- coding: utf-8 -*-import pymongoimport refrom pymongo import MongoClient#創建連接#10.20.66.106client MongoClient(10.20.4.79,27017)#client Mong…

推薦一個快速反射調用的類

使用傳統的.net反射機制&#xff0c;調用類的方法時&#xff0c;在調用頻率大的情況下&#xff0c;會感覺速度很慢。最近瀏覽盧彥的博客時&#xff0c;找到一個他改進后的反射調用類。試用以后感覺效率明顯提高&#xff0c;特推薦給大家。作者重新實現了&#xff0c;反射調用方…

CMake 構建項目Android NDK項目基礎知識

本篇文章將介紹如何使用 CMake 構建實現你的第一個 NDK 項目。 ##前言 你好&#xff01;歡迎來到我的的學習筆記分享系列&#xff0c;第一次給大家分享的是 Android NDK 開發的學習筆記&#xff0c;讓我們先開始了解 NDK 的構建方式吧&#xff01; NDK 構建方式有兩種&#xff…

linux installaccess Nessus-5.2.4

1、Download: http://www.tenable.com/products/nessus/select-your-operating-system 2、Current version&#xff1a;Nessus-5.2.4-debian6_i386.deb 3、Install&#xff1a;dpkg -i Nessus-5.2.4-debian6_i386.deb # dpkg -i Nessus-5.2.4-debian6_i386.deb Selecting p…

面試官問:JS的繼承

原文作者若川&#xff0c;掘金鏈接&#xff1a;https://juejin.im/post/5c433e216fb9a049c15f841b寫于2019年2月20日&#xff0c;現在發到公眾號聲明原創&#xff0c;之前被《前端大全》公眾號等轉載閱讀量超1w&#xff0c;知乎掘金等累計閱讀量超過1w。導讀&#xff1a;文章主…

qt 快速按行讀取文件_這是知識點之Linux下分割文件并保留文件頭

點擊上方"開發者的花花世界"&#xff0c;選擇"設為星標"技術干貨不定時送達&#xff01;這是一個知識點方便快捷的給結構化數據文件分割大小并保留文件的表頭&#xff0c;幾十個G的結構化文件不僅閱讀編輯麻煩&#xff0c;而且使用受限&#xff0c;因此高效…

mono 調用windows webService

1. 實現linux mono Develop中調用windows 中的webService l linux 與 windows 在一個局域網的網段中 l windows 的IIs中發布webService 2. windows 中的設置 l webService 的代碼 using System; using System.Collections.Generic; using System.Linq; using S…

Linux 內存機制

轉載鏈接&#xff1a;http://blog.csdn.net/tianlesoftware/article/details/5463790 一. 內存使用說明 Free 命令相對于top 提供了更簡潔的查看系統內存使用情況&#xff1a; [rootrac1 ~]# free total used free shared buffers cached Mem: …

network中的請求信息,headers中的每一項分別是什么意義?

這里是修真院前端小課堂&#xff0c;每篇分享文從 【背景介紹】【知識剖析】【常見問題】【解決方案】【編碼實戰】【擴展思考】【更多討論】【參考文獻】 八個方面深度解析前端知識/技能&#xff0c;本篇分享的是&#xff1a; 【network中的請求信息&#xff0c;headers中的每…

學習 sentry 源碼整體架構,打造屬于自己的前端異常監控SDK

前言這是學習源碼整體架構第四篇。整體架構這詞語好像有點大&#xff0c;姑且就算是源碼整體結構吧&#xff0c;主要就是學習是代碼整體結構&#xff0c;不深究其他不是主線的具體函數的實現。文章學習的是打包整合后的代碼&#xff0c;不是實際倉庫中的拆分的代碼。其余三篇分…

巴西龜吃什么

1、活蝦&#xff0c;哈哈&#xff0c;巴西龜最喜歡的食物&#xff0c;超市很多雞尾蝦買的&#xff0c;就那種&#xff0c;要活的&#xff0c;鍛煉它們的天性&#xff0c;一次一只可以吃一、兩天&#xff1b; 2、蚶子&#xff0c;貝殼類&#xff0c;活的&#xff0c;整個扔進去&…

綁定dictionary 給定關鍵字不再字典中_VBA代碼集錦-利用字典做兩列數據的對比并對齊...

源數據&#xff1a;代碼&#xff1a;Sub 對比()Dim arr, brr, crrDim i, j, n, lastrowA, lastrowB As Integer建立字典對象Set d CreateObject("scripting.dictionary")獲取數據區域最后一行的行數lastrowA Sheets("對比對齊兩列數據").Cells(Rows.Coun…

linux啟動時掛載rootfs的幾種方式 .

轉載鏈接&#xff1a;http://blog.csdn.net/zuokong/article/details/9022707 根文件系統&#xff08;在樣例錯誤消息中名為 rootfs&#xff09;是 Linux 的最基本的組件。根文件系統包含支持完整的 Linux 系統所需的所有內容。它包含所有應用程序、配置、設備、數據等 Linux 中…

PHP 手冊

by:Mehdi AchourFriedhelm BetzAntony DovgalNuno LopesHannes MagnussonGeorg RichterDamien SeguyJakub Vrana其他貢獻者2018-06-19Edited By: Peter Cowburn中文翻譯人員&#xff1a;肖盛文洪建家穆少磊宋琪黃嘯宇王遠之肖理達喬楚戴劼褚兆瑋周夢康袁玉強段小強© 1997-…

前端使用puppeteer 爬蟲生成《React.js 小書》PDF并合并

前端也可以爬蟲&#xff0c;寫于2018年08月29日&#xff0c;現在發布到微信公眾號申明原創。掘金若川 本文章鏈接&#xff1a;https://juejin.im/post/5b86732451882542af1c80821、 puppeteer 是什么&#xff1f;puppeteer: Google 官方出品的 headless Chrome node 庫puppetee…