最近看了很多文章,想要更通透的搞懂JS中的prototype
、__proto__
與constructor
屬性,從各個博主的文章裡摘取了我認為可以有助於理解的一些內容,希望自己能夠掌握好這一重要知識點的同時也幫助到大家,具體內容請見下文。
(注意:文中__proto__
屬性的兩邊是各由兩個下劃線構成。 )
本文通過下面一個簡單的例子展開討論,並配以相關的圖幫助理解:
function Foo() {
this.name="小紅";
}; //建立一個函式Foo
var f1 = new Foo();
var f2 = new Foo(); //通過new關鍵字,例項化Foo這個建構函式得到一個例項化物件f1
console.log(f1 === f2); //false
// 列印起來,看看都是啥吧
console.log(fn.prototype); //undefined,可見由建構函式建立出的物件沒有prototype屬性,但是有__proto__屬性
console.log(Foo.prototype); //{constructor: ƒ Foo(...), __proto__: Object}
console.log(fn.__proto__); //{constructor: ƒ Foo(...), __proto__: Object} fn.__proto__指向建構函式的prototype
console.log(Foo.__proto__); //ƒ () { [native code] } 指向了Foo的原型物件Function,大家可以嘗試一下Function.prototype是什麼結果
console.log(fn.constructor); //ƒ Foo() {this.name="小紅"} 物件的constructor用於返回建立這個物件的函式(即建構函式)
console.log(Foo.constructor); //ƒ Function() { [native code] } Foo也是一個物件,它的建構函式是Function(),所以返回的也是Function(),而Function()較特殊,它的建構函式是他自己,下文會再次強調
console.log(Foo.prototype.constructor); //ƒ Foo() {this.name="小紅"}
console.log(Foo.prototype.__proto__); //{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ,…}
複製程式碼
雖然是簡簡單單的兩行程式碼,然而它們背後的關係卻是錯綜複雜的,如下圖所示:
別怕,讓我們一步步剖析,徹底搞懂它們。
圖的說明:右下角為圖例,紅色箭頭表示__proto__
屬性指向、綠色箭頭表示prototype
屬性的指向、棕色箭頭表示constructor
屬性的指向;藍色方塊表示物件,淺綠色方塊表示函式。圖的中間部分即為它們之間的聯絡,圖的最左邊即為例子程式碼
首先,我們需要牢記兩點:
① __proto__
和constructor
屬性是物件所獨有
的;
② prototype
屬性是函式所獨有
的。
但是由於JS中函式也是一種物件
,所以函式也擁有__proto__
和constructor
屬性,這點是致使我們產生困惑的很大原因之一。
(例如 獲取圖中Foo函式的__proto__和constructor的方式:Foo.prototype.__proto__; Foo.prototype.constructor;)
上圖有點複雜,我們把它按照屬性分別拆開,然後進行分析:
__proto__
屬性
__proto__
屬性,它是 物件所獨有
的,可以看到__proto__
屬性都是由 一個物件指向一個物件
,即指向它們的原型物件(也可以理解為父物件)。
那麼__proto__
屬性的作用是什麼呢?
它的作用
就是當訪問一個物件的屬性時,如果該物件內部不存在這個屬性,那麼就會去它的__proto__
屬性所指向的那個物件(可以理解為父物件)裡找,如果父物件也不存在這個屬性,則繼續往父物件的__proto__
屬性所指向的那個物件(可以理解為爺爺物件)裡找,如果還沒找到,則繼續往上找….直到原型鏈頂端 null
(可以理解為原始人。。。),此時若還沒找到,則返回undefined
(可以理解為,再往上就已經不是“人”的範疇了,找不到了,到此為止),由以上這種通過__proto__
屬性來連線物件直到null
的一條鏈即為我們所謂的 原型鏈
。
prototype
屬性
prototype
屬性,別忘了一點,就是我們前面提到要牢記的兩點中的第二點:
① 函式所獨有的
② 它是從一個函式指向一個物件
。
它的 含義 是函式的原型物件
,也就是這個函式(其實所有函式都可以作為建構函式)所建立的例項的原型物件,由此可知:f1.__proto__ === Foo.prototype
,它們兩個完全一樣。
那prototype
屬性的作用
又是什麼呢?
它的作用
就是 包含可以由特定型別的所有例項共享的屬性和方法,也就是讓該函式所例項化的物件們都可以找到公用的屬性和方法
。
constructor
屬性
constructor
屬性也是物件才擁有的,它是從一個物件指向一個函式
,含義 就是指向該物件的建構函式
,每個物件都有建構函式,從圖中可以看出 Function
這個物件比較特殊,它的建構函式就是它自己
(因為Function可以看成是一個函式,也可以是一個物件),所有函式最終都是由Function()建構函式得來,所以constructor
屬性的終點就是 Function()
。
——————————————————————
總結一下:
-
我們需要牢記兩點:
①
__proto__
和constructor
屬性是物件所獨有的;②
prototype
屬性是函式所獨有的,因為函式也是一種物件,所以函式也擁有__proto__
和constructor
屬性。 -
__proto__
屬性的作用就是當訪問一個物件的屬性時,如果該物件內部不存在這個屬性,那麼就會去它的__proto__
屬性所指向的那個物件(父物件)裡找,一直找,直到__proto__
屬性的終點null
,然後返回undefined,通過__proto__
屬性將物件連線起來的這條鏈路即我們所謂的原型鏈
。 -
prototype
屬性的作用就是讓該函式所例項化的物件們都可以找到公用的屬性和方法,即f1.__proto__ === Foo.prototype
。 -
constructor
屬性的含義就是指向該物件的建構函式
,所有函式(此時看成物件了)最終的建構函式都指向終點Function()
。
下面根據上述內容整理相關的表格如下:
__proto__ |
constructor |
prototype |
|
---|---|---|---|
屬性歸屬 | 物件獨有 | 物件獨有 | 函式獨有(由於函式也是物件,所以函式也擁有__proto__ 和constructor 屬性) |
含義 | 指向它們的原型物件(也可以理解為父物件),最終指向null,實現了原型鏈 | 指向該物件的建構函式,最終指向Function() | 函式的原型物件 |
指向 | 一個物件指向一個物件 | 一個物件指向一個函式 | 一個函式指向一個物件 |
作用 | 作為原型鏈的橋樑,幫助向上一層層找到被訪問物件的屬性,直到null | 返回對建立此物件的函式的引用 | 包含可以由特定型別的所有例項共享 的屬性和方法,也就是讓該函式所例項化的物件們都可以找到公用的屬性和方法。 |
幫助理解
幫助理解 __proto__ 和 prototype
//如果你能耐心看完下列列印內容和註釋,相信你會對 prototype、__proto__ 屬性理解的更通透
function Fun(){}; //創造了一個函式Fun,這個函式由Function生成(Function作為建構函式)
var fn=new Fun(); //建立了一個函式fn,這個函式由Fn生成(Fn作為建構函式)
console.log(fn.__proto__===Fun.prototype) //true
// fn的__proto__指向其建構函式Fun的prototype
console.log(Fun.__proto__===Function.prototype) //true
// Fun的__proto__指向其建構函式Function的prototype
console.log(Function.__proto__===Function.prototype) //true
// Function的__proto__指向其建構函式Function的prototype
// 建構函式自身是一個函式,他是被自身構造的
console.log(Function.prototype.__proto__===Object.prototype) //true
// Function.prototype的__proto__指向其建構函式Object的prototype
// Function.prototype是一個物件,同樣是一個方法,方法是函式,所以它必須有自己的建構函式也就是Object
console.log(Fun.prototype.__proto__===Object.prototype) //true
// 與上條相同
// 此處可以知道一點,所有建構函式的的prototype方法的__都指向__Object.prototype(除了....Object.prototype自身)
console.log(Object.__proto__===Function.prototype) //true
// Object作為一個建構函式(是一個函式物件!!函式物件!!),所以他的__proto__指向Function.prototype
console.log(Object.prototype.__proto__===null) //true
// Object.prototype作為一切的源頭,他的__proto__是null
// 下面是一個新的,額外的例子
var obj={}
// 建立了一個obj
console.log(obj.__proto__===Object.prototype) //true
// obj作為一個直接以字面量建立的物件,所以obj__proto__直接指向了Object.prototype,而不需要經過Function了!!
// 下面是根據原型鏈延伸的內容
// 還有一個上文並未提到的constructor, constructor在原型鏈中,是作為物件prototypr的一個屬性存在的,它指向建構函式(由於主要講原型鏈,這個就沒在意、);
console.log(obj.__proto__.__proto__ === null) //true
console.log(obj.__proto__.constructor === Object) //true
console.log(obj.__proto__.constructor.__proto__===Function.prototype) //true
console.log(obj.__proto__.constructor.__proto__.__proto__===Object.prototype) //true
console.log(obj.__proto__.constructor.__proto__.__proto__.__proto__===null) //true
console.log(obj.__proto__.constructor.__proto__.__proto__.constructor.__proto__===Function.prototype) //true
複製程式碼
幫助理解 constructor和prototype
剛剛有說,constructor
屬性是物件才擁有的,它是從一個物件指向一個函式
,含義 就是指向該物件的建構函式
。
prototype
屬性是函式所獨有
的,是函式的原型物件,是由一個函式指向一個物件。
按照javascript的說法,function定義的方法就是一個Object(物件),而且還是一個很特殊的物件,這個使用function定義的物件與使用new操作符生成的物件之間有一個重要的區別。就是function定義的物件有一個prototype屬性,使用new生成的物件就沒有這個prototype屬性
。
prototype屬性又指向了一個prototype物件,注意prototype屬性與prototype物件是兩個不同的東西,要注意區別。在prototype物件中又有一個constructor屬性,這個constructor屬性同樣指向一個constructor物件,而這個constructor物件恰恰就是這個function函式本身。
有點暈麼?請看下圖:
function Person(name){
this.name=name;
this.showMe=function(){
alert(this.name);
}
};
var one=new Person('js');
console.log(one.prototype); //undefined 證明了one這個物件沒有prototype屬性
console.log(typeof Person.prototype); //object
console.log(Person.prototype.constructor); //function Person(name) {...};
console.log(one.constructor); //function Person(name) {...};
複製程式碼
鞏固
練習:下面自己動手畫一畫這段程式碼的原型圖:
function People(name) {
this.name = name;
this.sayName = function() {
console.log('my name is:' + this.name);
}
}
People.prototype.walk = function() {
console.log(this.name + 'is walking');
}
var p1 = new People('小明');
var p2 = new People('小紅');
複製程式碼
練習:建立一個 Car 物件,擁有屬性name、color、status;擁有方法run,stop,getStatus
function Car(name, color, status) {
this.name = name;
this.color = color;
this.status = status;
}
Car.prototype = {
constructor : Car,
run: function() {
this.status = 'run';
},
stop: function() {
this.status = 'stop';
},
getStatus: function() {
console.log(this.status);
}
}
var car1 = new Car('BMW', 'red', 'stop');
複製程式碼
參考
本文內容均參考自以下文章(以便追根溯源):
developer.mozilla.org/zh-CN/docs/…
如若發現文中紕漏請留言,歡迎大家糾錯,我們一起成長。