摘要:由于一般所有的原型鏈最終都會指向頂端的,所以它們都是的。好了現在了,成了所有對象原型鏈的。
JavaScript里任何東西都是對象,任何一個對象內部都有另一個對象叫__proto__,即原型,它可以包含任何東西讓對象繼承。當然__proto__本身也是一個對象,它自己也有自己的__proto__,這樣一級一級向上,就構成了一個__proto__鏈,即原型鏈。當然原型鏈不會無限向上,它有個終點,可以稱為原型鏈的頂端,或者root,它是一個特殊的對象,它的__proto__為null。
obj.__proto__.__proto__......__proto__ === null;
但是對象并不是憑空產生的,它一般是某一個class,或者確切說是構造函數的實例。JavaScript和其它面向對象的語言不太一樣,它沒有所謂的class,而是用函數來模擬class。定義了一個函數,實際上就定義了一個class,函數本身就是class的constructor,例如:
function foo() {} var a = new foo();
這里創建了一個對象a,是foo的一個實例。既然a是一個對象,它就有原型__proto__,那a的__proto__是怎么設定的?這里就引出了prototype,它是函數才有的一個屬性,也是一個對象,一個函數創建的實例的__proto__就指向這個函數的prototype。所以a的__proto__被設定為foo.prototype,即:
a.__proto__ === foo.prototype;
當然foo.prototype也是個對象,它自然也有__proto__,默認指向Object.prototype,即:
foo.prototype.__proto__ === Object.prototype;
而Object.prototype已經沒有原型了,它的__proto__是null。這是JavaScript里一個很特殊的對象,它就是原型鏈的頂端。
以上就構成了由對象a開始的原型鏈:
a.__proto__ === foo.prototype; a.__proto__.__proto__ === Object.prototype;
JavaScript因此而推斷出:
a instanceof foo === true; a instanceof Object === true;
注意,這就是JavaScript判斷一個對象是否instanceof某個函數的依據,即對象a的原型鏈上有沒有一個__proto__是這個函數的prototype,如果有,那么a就是這個函數的instance。由于一般所有的原型鏈最終都會指向頂端的Object.prototype,所以它們都是Object的instance。
prototype和__proto__有什么作用我們可以設定
foo.prototype = { x : 2, y : 3, }
這里用字面量創建一個對象賦給了foo.prototype,這樣foo所創建出來的任何對象的__proto__就都指向了它,因此它們就可以訪問里面的field,或者說可以認為它們繼承了這些field,例如:
var a = new foo(); a.x === 2; a.y === 3;
不只是foo.prototype,繼續往上foo.prototype的__proto__,以及原型鏈上所有的__proto__都會被這個對象繼承。一般的對象的很多常用方法如toString,都是從原型鏈頂端的Object.prototype里繼承來的。
函數也是對象上面說了,JavaScript里任何東西都是對象,包括函數,可以稱為函數對象。所以foo也是對象,那foo的原型__proto__是什么?它是誰的instance?
JavaScript里定義了一個特殊的函數叫Function,可以稱作是所有函數的爸爸,所有的函數都是它的實例,因此你可以認為,定義foo的時候發生了這樣的事情:
var foo = new Function(args, function_body);
于是我們有:
foo.__proto__ === Function.prototype; foo instanceof Function === true;
注意這里的Function.prototype,這也是JavaScript里一個特殊的對象,Chrome的console里要是輸入Function.prototype,根本什么也打印不出來,什么native code,就是說它是內部實現的。
這個原型鏈還沒到頂,Function.prototype仍然有原型__proto__,指向Object.prototype,所以我們最終有:
foo.__proto__.__proto__ === Object.prototype; foo instanceof Object === true;
現在有個問題來了,那Function自己呢?它其實也是個函數,也是個對象,它的__proto__指向誰?答案是它自己的prototype,即:
Function.__proto__ === Function.prototype; Function instanceof Function === true; Function.__proto__.__proto__ === Object.prototype; Function instanceof Object === true;
總結一下:所有的函數都是Function的instance,Function自己也是它自己的instance,不過后者嚴格來說并不準確,Function并不是它自己創造自己的,而應該看作JavaScript里原生的一個函數對象,只不過它的__proto__指向了它自己的prototype而已。
Function和Object的關系這是JavaScript比較奇葩的一個地方,也是不太讓人容易接受的一點。
我們知道一般任何對象都是Object的instance,因為原型鏈的頂端都指向Object.prototype。那么Object本身是什么?Object也是個函數,而任何函數都是Function的實例對象,比如Array,String,當然Object也包括在內,它也是Function的實例,即:
Object.__proto__ === Function.prototype; Object instanceof Function === true
同時,Function是個對象,它的原型是Function.__proto__,指向Function.prototype,并且這個原型鏈向上繼續指向Object.prototype,即:
Function.__proto__.__proto__ === Object.prototype; Function instanceof Object === true
這樣就有了一個JavaScript里經常說到的蛋雞問題:
Object instanceof Function === true Function instanceof Object === true
到底誰先誰后,誰主誰次?關于這一點網上已經有很多解釋,這里首先陳述我的觀點,是先有Function,它是主;后有Object,是次。以下是我個人的理解,可能并不準確。要看待這個問題,我們可以從JavaScript創造世界開始想象:
我們知道Object.prototype是原型鏈的root。但首先,現在世界上還沒有Object,更沒有Object.prototype。現在只有個特殊的對象,姑且稱它為root_prototype,里面定義了些基本的field和method比如toString之類的,以后我們要讓所有的原型鏈都最終指向它。注意它沒有原型,它的__proto__是null,這也是它和所有其它JavaScript對象的區別,使它與眾不同,能有資格成為原型鏈的root。
然后定義Function。先看Function的prototype,我們只要知道這是一個特殊的對象,它的原型__proto__指向剛才的root_prototype,就是說Function.prototype.__proto__ === root_prototype,這樣它就算連上了原型鏈的root。
上面已經講過了,Function也是個對象,也有__proto__,指向Function自己的prototype,所以說白了Function也是個奇葩,是JavaScript里規定的一個特殊的東西。而Function.prototype的原型__proto__繼續指向root_prototype,所以Function也連上了原型鏈root。
所有的函數,什么Array之類的,包括Object也是函數,都是繼承Function的,就是說,任意函數foo.__proto__ === Function.prototype,所以我們自然有Object instanceof Function。
然后再看Object,它本來就是個函數而已,和其它函數沒什么區別,都繼承了Function。可是現在最關鍵的一步是,強行設定讓Object.prototype = root_prototype,這樣Object.prototype就成了原型鏈的root!注意這里的邏輯,是先有了root_prototype,然后規定Object.prototype等于它,這一步是人為規定的,這就是Object的特殊之處。你要是規定bar.prototype也等于root_prototype,那bar.prototype也成了原型鏈的的頂端。所以JavaScript里__proto__這個東西其實是很隨意的,放在哪個函數的prototype里,哪個函數就成了你爹。
好了現在Object.prototype === root_prototype了,成了所有對象原型鏈的root。那么由第3步的結論,Function也是對象,是連上了root_prototype的,而現在root_prototype給Object.prototype了,那Function自然就是instanceof Object。
- 首先沒雞沒蛋,先有一個特殊對象root_prototype,它是上帝。 - 接下來應該是先有Function,并且定義它的prototype和__proto__,都連上了root_prototype。 - 然后才有了Object,它是Function的instance,繼承了Function。這時候Object仍然只是個普通的函數。 - 然后規定Object.prototype = root_prototype,這時候Object才開始顯得特殊,成為了原型鏈的頂端,否則它和其它函數根本沒什么區別。 - 于是所有的東西,包括Function,都成了Object的instance了。
這里要強調Object和其它函數的不同之處。Object之所以特殊,就是因為Object的prototype被設定為了root_prototype,僅此而已;而其它函數例如foo,它的prototype只是一個普通的對象,這個對象的__proto__默認情況下指向root_prototype。至于為什么這樣設定,為什么Object會特殊化,大概只是因為Object這個名字起得好,而foo,bar沒那么特殊。所以說白了Object函數只是一個盛放root_prototype的容器而已,從而使它晉升為一個特殊的函數。
另外值得注意的是,obj instanceof function 并不意味著obj就是這個function創建出來的,只不過是obj的原型鏈上有function.prototype而已。
所以所謂的 Object instanceof Function 和 Function instanceof Object 的蛋雞問題,前者應該來說是自然而然、不容置疑的,可以認為Object函數是Function創造出來的;而后者說白了只是因為強行規定了Object函數的特殊性,而導致的一個推論,而Function并非是Object創建的。
當然這些概念繞來繞去討論其實我感覺沒什么很大意義,無非只是自圓其說而已。大家寫代碼時也不會太糾結這些問題,重點還是要把原型鏈搞清楚。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90361.html
摘要:而作為構造函數,需要有個屬性用來作為以該構造函數創造的實例的繼承。 歡迎來我的博客閱讀:「JavaScript 原型中的哲學思想」 記得當年初試前端的時候,學習JavaScript過程中,原型問題一直讓我疑惑許久,那時候捧著那本著名的紅皮書,看到有關原型的講解時,總是心存疑慮。 當在JavaScript世界中走過不少旅程之后,再次萌發起研究這部分知識的欲望,翻閱了不少書籍和資料,才搞懂...
摘要:對應的關系圖如下講解了構造函數和原型對象之間的關系,那么實例對象和原型對象之間的關系又是怎么樣的呢下面講解。原型對象的指向的是構造函數和本身沒有屬性,但是其原型對象有該屬性,因此也能獲取到構造函數。 JavaScript進階 - 1. 原型和原型鏈的概念 我們好多經常會被問道JavaScript原型和原型鏈的概念,還有關于繼承,new操作符相關的概念。本文就專門整理了原型和原型鏈的概念...
摘要:如果要理清原型和原型鏈的關系,首先要明確一下幾個概念中的所有東西都是對象,函數也是對象而且是一種特殊的對象中所有的東西都由衍生而來即所有東西原型鏈的終點指向對象都有一個隱藏的屬性,他指向創建它的構造函數的原型,但是有一個例外,指向的是。 首先要搞明白幾個概念: 函數(function) 函數對象(function object) 本地對象(native object) 內置對象(bu...
摘要:而和的存在就是為了建立這種子類與父類間的聯系。創建一個基本對象建立新對象與原型我把它理解為類之間的連接執行構造函數小結可以理解為類,也就是存儲一類事物的基本信息。原型原型鏈和繼承之間的關系。 原型 原型的背景 首先,你應該知道javascript是一門面向對象語言。 是對象,就具有繼承性。 繼承性,就是子類自動共享父類的數據結構和方法機制。 而prototype 和 __proto__...
摘要:在中,主要有兩種創建對象的方法分別是對象字面量以及調用構造函數對象字面量調用構造函數其實上述兩種創建對象的方法,本質上是一樣的,都是引擎調用對象的構造函數來新建出一個對象。 原型與原型鏈是javascript里面最最核心的內容,如果不能理解它們之間的存在關系的話,那么我們是不能理解這門語言的。 在JS中,主要有兩種創建對象的方法, 分別是對象字面量以及調用構造函數 //對象字面量 va...
閱讀 2684·2021-10-22 09:55
閱讀 2008·2021-09-27 13:35
閱讀 1267·2021-08-24 10:02
閱讀 1478·2019-08-30 15:55
閱讀 1198·2019-08-30 14:13
閱讀 3471·2019-08-30 13:57
閱讀 1975·2019-08-30 11:07
閱讀 2447·2019-08-29 17:12