本文中心:ide
這篇文章比較難懂,因此讀起來比較晦澀。因此,我簡單列一下提綱:函數
在第一部分,從函數原型開始談起,目的是想搞明白,這個屬性是什麼,爲何存在,在建立對象的的時候起到了什麼做用!測試
在第二部分,閱讀的時候,請分清楚__proto__和內置對象的區別;搞清楚這點。而後,咱們再一點點分析__proto__屬性。ui
第三部分,原本不在我寫做的範圍,可是看到網上的不少文章在繼承的時候,使用的方法五花八門。因此來談一下,Object.create()這個方法的好處。this
標題中所謂的特有,指的是只有函數才具備prototype屬性,ECMAScript標準規定每一個函數都擁有一個屬於本身的原型(prototype)。spa
那麼這個函數的原型究竟是什麼,它又有什麼用呢?firefox
console.log(typeof Object.prototype); //"object", 這裏用到了Object()函數。 console.log(Object.prototype instanceof Object) //true
從上面的輸出結果中,咱們得出函數的原型是一個對象。那麼,這個對象自己有什麼屬性呢?
咱們知道,任何一個對象都具備最基本的方法,好比 toString().valueof()...既然函數原型是對象類型,那麼它確定也具備這些基本的方法...
因此這些方法是從哪裏來的呢?要想搞清楚這些,那麼咱們就必需要從Object()的原型談起!
上面這幅圖片,幫咱們認清楚了Object()函數的原型,這個函數原型自己不具備任何屬性,可是其具備一些很基本的方法,這些方法有什麼用,這裏暫且不論。可是到目前爲此,請記住一點:函數原型是一個對象。由於只有知道了最基本的這一點,咱們下面的討論才具備意義。prototype
var object = new Object(); //new 一個對象 console.log(object.toString()); //輸出這個對象,firefox控制檯下輸出結果 [obejct object]
var object = new Object(); console.log(object.__proto__==Object.prototype); //true.
事實再一次證實,世上沒那麼多的巧合!!object.__ptoto__和Object.prototype真的指向的是同一個對象。設計
如今咱們解決了一個問題,就是object.toSring()這個函數,真正的來源是Object.prototype。那麼object對象爲何能訪問Object.prototype中的方法呢...要回答這個問題,須要弄清楚兩件事情:var object=new Object();
var object = new Object(); var proto1 = {}; proto1.name = '張三' object.__proto__ = proto1; console.log(object.name); //張三 var proto2 = {}; proto2.name = '李四' object.__proto__ = proto2; console.log(object.name); //李四
在建立一個新的函數的時候,這個函數的原型中會有一個constructor屬性,那麼這個屬性是否有存在的意義呢?3d
看下面的一段代碼:
var Person=function(){}; console.log(Person.prototype.constructor); //function constructor是一個函數 console.log(Person.prototype.constructor===Person);//true Person.prototype.constructor===Person
上述代碼,證實了constructor這個屬性是真實存在的,且這個屬性的值初始化爲構造函數自己。那麼這個屬性有什麼很重要的意義嗎? 再看下面的碼:
var Person = function () { }; var xiaoming = new Person(); console.log(xiaoming instanceof Person); //true Person.prototype.constructor = null; console.log(xiaoming instanceof Person); //true
由上面例子能夠得出,constructor屬性只是標識原型是歸屬於哪一個函數的,這個屬性雖然是解釋器給咱們默認的,可是相對來講沒有那麼重要,甚至提及來能夠是一點用處都沒有。對於一個函數,在剛建立的時候老是這個樣子的。
有些事情在你出生的那一刻就已經註定要發生。prototype在出生之初就已經註定其宿命。下面讓咱們來談談這所謂的宿命吧!!
根據1.1部分,咱們知道函數的原型,在函數實例化的時候會被賦值給實例的內置屬性的。假設有兩個類A和B,代碼以下:
//A函數以下 var A = function (a) { this.a = a; } A.prototype.getA = function () { return this.a; } // B函數以下 var B = function (a, b) { A.call(this, a); //借用A類構造函數,很重要的一點!!! this.b = b; } B.prototype.getB = function () { return this.b; }
A和B分別是兩個類的構造函數,他們此時在內存中的結構以下圖所示:
如今若是咱們想讓B類成爲A的子類,該如何作呢? 首先,咱們應該認識到一點,若是B是A的子類,那麼B就應該能訪問A中的屬性和方法。父類A中有屬性a和方法getA(),那麼子類B中也應該有屬性a且能訪問方法getA();若是咱們能實現以下圖所示的結構是否就能作到B類繼承A類呢?
與上圖相比,僅僅修改了B.prototype中的【【__proto__】】.而後一切的一切都天然而然的發生了。總之,子類B爲了繼承A作了兩樣活: 子類B類經過A.call();這一步借用A的構造函數擁有的A類的變量,又經過修改原型中的【【__proto__】】才作到能訪問A類中的函數..想到這裏不得不說一句,好偉大的設計。若是隻是爲了實現繼承,有N多種方法能實現,可是請注意,若是考慮內存中的分配狀況以及效率和程序的健壯性,那麼就只有一個函數可以完美的作到圖中所示的那樣。這個函數就是Object.create()函數,這個函數的宿命就是爲了實現繼承。
爲何這麼說呢,請看第二部分慢慢解析!!
若是你認爲本身很瞭解這個屬性,那麼請思考如下幾個問題?
1.這個屬性是什麼性質的屬性? 訪問器屬性 or 數據屬性?
2. 這個屬性存在在哪裏? 是每一個對象都有,仍是在整個內存中僅有一份。
3.這個屬性與內置屬性有什麼關係?
若是你對上面的上個問題很困惑,或者你認爲__proto__就是內置屬性的話,那麼咱們仍是花一點時間正正三觀吧。
看下面一段代碼:
var descriptor=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__"); console.log(descriptor); //輸出結果:
configurable | true |
enumerable | false |
get | function() { [native code] } |
set | function() { [native code] } |
看到上面的輸出結果,你是否已經接受了__proto__就是一個訪問器屬性呢....若是你還不相信..那麼接着看,這只是踐踏你世界觀的開始!!!
從證實1的輸出結果中,咱們知道configurable=true;這也就告訴咱們這個對象是能夠被刪除的...下面看一段代碼:
var object={}; var result=delete object.__proto__; console.log(result); //true console.log(typeof object.__proto__) //object.
請回答? 爲何顯示刪除成功了, typeof object.__proto__仍是輸出 object呢?
ok!! 要理解透徹這些,咱們插入一些delete運算符的知識.
ECMAScript規定:delete 運算符刪除對之前定義的對象屬性或方法的引用,並且delete 運算符不能刪除開發者未定義的屬性和方法。
那麼什麼狀況下,delete會起做用呢?
delelte運算若是想正常操做必須知足三個條件: 1,該屬性是咱們寫的,即該屬性存在。2.刪除的是一個對象的屬性或方法。3.該屬性在配置時是能夠刪除的,即(configurable=true)的狀況下能夠刪除。
那麼,上面的例題中,返回值爲true.,它符合上面的三個條件嗎?
對於1,該屬性咱們是能夠訪問的,因此,證實該屬性存在。
對於2,__proto__是某個對象的屬性。
對於3:由於 configurable=true,因此也是符合的。
ok!上面的三點都符合,在返回值等於true的狀況下,刪除仍是失敗了呢! 由於還有下面一種狀況,就是在對象上刪除一個根本不存在的於自身的屬性也會返回true!
var object = { };
Object.prototype.x={}; var result = delete object.x; console.log(result); //true.console.log(object.x);//object
看到沒有,這兩個例子在輸出結果上很類似呢?由於 __proto__屬性存在於該對象上的原型上面,因此,該對象能夠訪問。可是不能刪除該屬性。若是想刪除該屬性,那麼請在Object.prototype上刪除。這個保證能刪除成功。
爲了證明這一點,咱們再看一下
var object={}; console.log(typeof object.__proto__); //object delete Object.prototype.__proto__; console.log(typeof object.__proto__); //undefined 刪除成功。
咱們能夠發現,在Object.prototype刪除__proto__屬性後。object上也沒法訪問了。這是由於,因此對象都有一個共同的原型Object.prototype.在這個上面刪除__proto__,那麼全部的對象也都不具備這個__proto__屬性了。
這也就證實了,內存中僅存一份__proto__屬性,且該屬性定義在Object.prototype對象上。
從某種程度上來講,__proto__若是存在,那麼它老是等於該對象的內置屬性。並且在上一篇文章中咱們也點出了一點,改變__proto__的指向也能改變內置屬性的指向。因此,若是你執拗的把__proto__認爲就是內置對象,那也無可厚非。
可是請記住兩點:
1. 內置對象不可見,可是內置對象老是存在的。
2.__proto__若是存在,那麼它的值就是內置對象,可是這個__proto__並不老是存在的。
若是你必定認爲__proto__就是內置對象,也能夠,可是請保證兩點:不要在程序的任何地方用__proto__屬性。或者,若是你必定要用__proto__這個屬性的話,請保證永遠不要修改Object.prototype中的__proto__!!
若是你不能保證這兩點,請遠離__proto__.由於,一旦有人不遵照約定的話,這個bug的危害代價太大。好比,下面這樣...
var A = function () { } A.prototype.say = function () { return 'hello'; } var B = function () { } //子類繼承父類 function extend(subClass, superClass) { var object = { }; object.__proto__ = superClass.prototype; subClass.prototype = object; subClass.prototype.constructor = subClass; } extend(B, A); //B繼承A
var b = new B(); b.say();
上面是一段,毫無問題的代碼...可是若是有一個小白用戶,在某一天執行了下面一句代碼,
var A = function () { } A.prototype.say = function () { return 'hello'; } var B = function () { } function extend(subClass, superClass) { var object = { }; object.__proto__ = superClass.prototype; subClass.prototype = object; subClass.prototype.constructor = subClass; } delete Object.prototype.__proto__; //或則其餘的等等 extend(B, A); var b = new B(); b.say(); //TypeError: b.say is not a function 報錯...若是是這種錯誤,調試起來確定會讓你欲哭無淚的。因此,若是你想寫出好的程序,請遠離__proto__.
時無英雄,使豎子成名! JavaScript的今天的盛行,能夠說就是這句話的寫照。Object.create()也是這樣,在繼承時並非咱們非用它不可,只是在排除了使用__proto__以後,除了使用這個函數以外,咱們沒有其餘更好的選擇。
這個函數在W3C中這個函數是怎麼定義的呢?
這是這個函數在W3C中的定義,我來舉個例子來講明這個函數怎麼用吧!!!
var A = function (name) { this.name = name; }; A.prototype.getName = function () { return this.name } var returnObject = Object.create(A.prototype, { name: { value: 'zhangsan', configurable: true, enumerable:false, writable:true } });
上述代碼運行完畢以後,returnObject在內存中的結構如圖所示:
看看上面這張圖,在類比1.3中的最後一張圖,以下:
發現是否是,驚人的類似...因此,知道Objece.create()的強大了吧!! 咱們分析過,下面這張圖是實現繼承的完美狀態,而Object.create()就是爲了作到這些,專業爲繼承而設計出來的函數。
下面是一段用Object.create()函數實現子類繼承父類的代碼;
//子類繼承父類,這段代碼在執行delete Object.prototype.__proto__;這段代碼以後仍然能夠正常運行。
function extend(subClass, superClass) { var prototype=Object.create(superClass.prototype); subClass.prototype =prototype;
subClass.prototype.constructor = subClass; }
var A = function () {
}
A.prototype.say = function () {
return 'hello';
}
var B = function () {
}
extend(B, A);
var b = new B();
b.say(); //hello
Ok! 我知道,你能用N多種方法實現繼承,可是請記住,在繼承的時候請不要用__proto__這個屬性,由於它沒你想象中俺麼可靠。若是你想得到一個對象的原型,那麼這個方法能夠作到,Object.getPrototypeOf。與之對應的是Object.setPrototypeOf方法。
也許你也會說,Object.setPrototypeOf方法能夠在遠離__proto__的狀況下實現繼承啊啊...若是你在看到它的源代碼你還會這麼說嗎?
Object.setPrototypeOf = Object.setPrototypeOf || function(obj, proto) { obj.__proto_ _ = proto; //也是用到了__proto__. return obj; }
總結:
整篇文章,從prototype談起,分析了函數的prototype的類型與做用(這個你們都在談)。
在第二部分,咱們分析了__proto__,獲得的結果,內置屬性和__proto__根本不是一回事。__proto__這個屬性不可靠..撇開,這個屬性是非標準屬性不說,這個屬性隱藏的bug就能致人於死地,因此,在寫程序時,請謹記一點,珍愛生命,遠離__proto__.
在最後,咱們淺談了一下,用Object.create()實現繼承的好處。這個部分,很難講的清楚,須要慢慢去體會。
在下一篇中,咱們會分析,爲何會說JS中一切皆是對象!。。。