摘要:我這里有個不夠準確但容易理解的說法,就是檢查一個對象是否為另一個構造函數的實例,為了更容易理解,下面將全部以是的實例的方式來說。
underscore源碼分析之整體架構
最近打算好好看看underscore源碼,一個是因為自己確實水平不夠,另一個是underscore源碼比較簡單,比較易讀。
本系列打算對underscore1.8.3中關鍵函數源碼進行分析,希望做到最詳細的源碼分析。
今天是underscore源碼剖析系列第一篇,主要對underscore整體架構和基礎函數進行分析。
首先,我們先來簡單的看一下整體的代碼:
// 這里是一個立即調用函數,使用call綁定了外層的this(全局對象) (function() { var root = this; // 保存當前環境中已經存在的_變量(在noConflict中用到) var previousUnderscore = root._; // 用變量保存原生方法的引用,以防止這些方法被重寫,也便于壓縮 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeBind = FuncProto.bind, nativeCreate = Object.create; var Ctor = function(){}; // 內部實現省略 var _ = function(obj) {}; // 這里是各種方法的實現(省略) // 導出underscore方法,如果有exports則用exports導出,如果 沒有,則將其設為全局變量 if (typeof exports !== "undefined") { if (typeof module !== "undefined" && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } // 版本號 _.VERSION = "1.8.3"; // 用amd的形式導出 if (typeof define === "function" && define.amd) { define("underscore", [], function() { return _; }); } }.call(this))全局對象
這段代碼整體比較簡單,不過我看后來的underscore版本有一些小改動,主要是將var root = this;替換為下面這句:
var root = typeof self == "object" && self.self === self && self || typeof global == "object" && global.global === global && global || this;
這里增加了對self和global的判斷,self屬性可以返回對窗口自身的引用,等價于window,這里主要是為了兼容web worker,因為web worker中是沒有window的,global則是為了兼容node,而且在嚴格模式下,立即執行函數內部的this是undefined。
void(0) ? undefined掃一眼源碼,我們會發現在源碼中并沒有見到undefined的出現,反而是用void(0)或者void 0來代替的,那么這個void到底是什么?為什么不能直接用undefined呢?
關于void的解釋,我們可以看這里:MDN
void 運算符通常只用于獲取 undefined的原始值,一般使用void(0),因為undefined不是保留字,在低版本瀏覽器或者局部作用域中是可以被當做變量賦值的,這樣就會導致我們拿不到正確的undefined值,在很多壓縮工具中都是將undefined用void 0來代替掉了。
其實這里不僅是void 0可以拿到undefined,還有其他很多方法也可以拿到,比如0["ygy"]、Object.__undefined__、Object.__ygy__,這些原理都是訪問一個不存在的屬性,所以最后一定會返回undefined
也許有時候我們會碰到這樣一種情況,_已經被當做一個變量聲明了,我們引入underscore后會覆蓋這個變量,但是又不想這個變量被覆蓋,還好underscore提供了noConflict這個方法。
_.noConflict = function() { root._ = previousUnderscore; return this; }; var underscore = _.noConflict();
顯而易見,這里正常保留原來的_變量,并返回了underscore這個方法(this就是_方法)
_接下來講到了本文的重點,關于_方法的分析,在看源碼之前,我們先熟悉一下_的用法。
這里總結的是我日常的用法,如果有遺漏,希望大家補充。
一種是直接調用_上的方法,比如_.map([1, 2, 3]),另一種是通過實例訪問原型上的方法,比如_([1, 2, 3]).map(),這里和jQuery的用法很像,$.extend調用jQuery對象上的方法,而$("body").click()則是調用jQuery原型上的方法。
既然_可以使用原型上面的方法,那么說明執行_函數的時候肯定會返回一個實例。
這里來看源碼:
// instanceof 運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的 prototype 屬性。 // 我這里有個不夠準確但容易理解的說法,就是檢查一個對象是否為另一個構造函數的實例,為了更容易理解,下面將全部以XXX是XXX的實例的方式來說。 var _ = function(obj) { // 如果obj是_的實例(這種情況我真的沒碰到過) if (obj instanceof _) return obj; // 如果this不是_構造函數的實例,那就以obj為參數 new一個實例(相等于修改了_函數) if (!(this instanceof _)) return new _(obj); // 對應_([1,2,3])這種情況 this._wrapped = obj; };
我先從源碼上來解釋,這里可以看出來_是一個構造函數,我們都知道,我既可以在構造函數上面增加方法,還可以在原型上面增加方法,前者只能通過構造函數本身訪問到,后者由于原型鏈的存在,可以在構造函數的實例上面訪問到。
var Person = function() { this.name = "ygy"; this.age = 22; } Person.say = function() { console.log("hello") } Person.prototype.say = function() { console.log("world") } var ygy = new Person(); Person.say(); // hello ygy.say(); // world
所以我們平時用的_.map就是Person.say()這種用法,而_([1, 2, 3]).map則是ygy.say()這種用法。
在繼續講這個之前,我們再來復習一下原型的知識,當我們new一個實例的時候到處發生了什么?
首先,這里會先創建一個空對象,這個空對象繼承了構造函數的原型(或者理解為空對象上增加一個指向構造函數原型的指針__proto__),之后會根據實例傳入的參數執行一遍構造函數,將構造函數內部的this綁定到這個新對象中,最后返回這個對象,過程和如下類似:
var ygy = {}; ygy.__proto__ = Person.prototype // 或者var ygy = Object.create(Person.prototype) Person.call(ygy);
這樣就很好理解了,要是想調用原型上面的方法,必須先new一個實例出來。我們再來分析_方法的源碼:
_接收一個對象作為參數,如果這個對象是_的一個實例,那么直接返回這個對象。(這種情況我倒是沒見過)
如果this不是_的實例,那么就會返回一個新的實例new _(obj),這個該怎么理解?
我們需要結合例子來看這句話,在_([1, 2, 3])中,obj肯定是指[1, 2, 3]這個數組,那么this是指什么呢?我覺得this是指window,不信你直接執行一下上面例子中的Person()?你會發現在全局作用域中是可以拿到name和age兩個屬性的。
那么既然this指向window,那么this肯定不是_的實例,所以this instanceof _必然會返回false,這樣的話就會return一個new _([1, 2, 3]),所以_([1, 2, 3])就是new _([1, 2, 3]),從我們前面對new的解釋來看,這個過程表現如下:
var obj = {} obj.__proto__ = _.prototype // 此時_函數中this的是new _(obj),this instanceof _是true,所以就不會重新return一個new _(obj),這樣避免了循環調用 _.call(obj) // 實際上做了這一步: obj._wrapped = [1, 2, 3]
這樣我們就理解了為什么_([1, 2, 3]).map中map是原型上的方法,因為_([1, 2, 3])是一個實例。
我這里再提供一個自己實現的_思路,和jQuery的實現類似,這里就不作解釋了:
var _ = function(obj) { return new _.prototype.init(obj) } _.prototype = { init: function(obj) { this.__wrapped = obj return this }, name: function(name) { console.log(name) } } _.prototype.init.prototype = _.prototype; var a = _([1, 2, 3]) a.name("ygy"); // ygy
underscore中所有方法都是在_方法上面直接掛載的,并且用mixin方法將這些方法再一次掛載到了原型上面。不過,由于篇幅有限,mixin方法的實現會在后文中給大家講解。
如果本文有錯誤和不足之處,希望大家指出。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93476.html
摘要:在上篇文章整體架構分析中,我們講過上面的方法有兩種掛載方式,一個是掛載到構造函數上以的形式直接調用在后文上統稱構造函數調用,另一種則是掛到上以的形式被實例調用在后文上統稱原型調用。 underscore源碼分析之基礎方法 本文是underscore源碼剖析系列的第二篇,主要介紹underscore中一些基礎方法的實現。 mixin 在上篇文章underscore整體架構分析中,我們講...
摘要:譯立即執行函數表達式處理支持瀏覽器環境微信小程序。學習整體架構,利于打造屬于自己的函數式編程類庫。下一篇文章可能是學習的源碼整體架構。也可以加微信,注明來源,拉您進前端視野交流群。 前言 上一篇文章寫了jQuery整體架構,學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫 雖然看過挺多underscore.js分析類的文章,但總感覺少點什么。這也許就是紙上得來終覺淺,絕知此...
摘要:支持形式的調用這其實是非常經典的無構造,其實就是一個構造函數,的結果就是一個對象實例,該實例有個屬性,屬性值是。 前言 終于,樓主的「Underscore 源碼解讀系列」underscore-analysis 即將進入尾聲,關注下 timeline 會發現樓主最近加快了解讀速度。十一月,多事之秋,最近好多事情搞的樓主心力憔悴,身心俱疲,也想盡快把這個系列完結掉,也好了卻一件心事。 本文...
摘要:的不能算作深復制,但它至少比直接賦值來得深一些,它創建了一個新的對象。它們的主要用途是對存在環的對象進行深復制。比如源對象中的子對象在深復制以后,對應于。希望這篇文章對你們有幫助深復制方法所謂擁抱未來的深復制實現參考資料 本文最初發布于我的個人博客:咀嚼之味 一年前我曾寫過一篇 Javascript 中的一種深復制實現,當時寫這篇文章的時候還比較稚嫩,有很多地方沒有考慮仔細。...
閱讀 3963·2021-11-11 10:58
閱讀 3337·2021-09-26 09:46
閱讀 1917·2019-08-30 15:55
閱讀 980·2019-08-30 13:52
閱讀 1950·2019-08-29 13:11
閱讀 3031·2019-08-29 11:27
閱讀 1522·2019-08-26 18:18
閱讀 2627·2019-08-23 14:17