關於JS中的constructor與prototype的總結
定義和用法
prototype 屬性使您有能力向對象添加屬性和方法
constructor 屬性返回對創建此對象的數組函數的引用
語法
object.prototype.name=value
object.constructor
在JS中有一個function的東西。一般人們叫它函數
function Person(name){
console.log(name);
}
Person('js'); //js
在Javascript語言中,constructor屬性是專門為function而設計的,它存在於每一個function的prototype屬性中。這個constructor保存了指向function的一個引用
JavaScript內部會執行如下幾個動作:
為該函數添加一個原形屬性(即prototype對象).
為prototype對象額外添加一個constructor屬性,並且該屬性保存指向函數F的一個引用
這樣當我們把函數F作為自定義構造函數來創建對象的時候,對象實例內部會自動保存一個指向其構造函數(這裏就是我們的自定義構造函數F)的prototype對象的一個屬性__proto__,所以我們在每一個對象實例中就可以訪問構造函數的prototype所有擁有的全部屬性和方法,就好像它們是實例自己的一樣
當然該實例也有一個constructor屬性了(從prototype那裏獲得的),這時候constructor的作用就很明顯了,因為這時每一個對象實例都可以通過constrcutor對象訪問它的構造函數,請看下麵代碼:
var f = new F();
alert(f.constructor === F);// output true
alert(f.constructor === F.prototype.constructor);// output true
我們可以利用這個特性來完成下麵的事情: 對象類型判斷
if(f.constructor === F) {
// do sth with F
}
其實constructor的出現原本就是用來進行對象類型判斷的,但是constructor屬性易變,不可信賴。
我們有一種更加安全可靠的判定方法:instanceof 操作符。
下麵代碼仍然返回true
if(f instanceof F) {
// do sth with F
}
原型鏈繼承,由於constructor存在於prototype對象上,因此我們可以結合constructor沿著原型鏈找到最原始的構造函數,如下麵代碼:
function Person(name){
this.name=name;
this.showMe=function(){
alert(this.name);
}
};
var one=new Person('<a href="#" class='replace_word' title="JavaScript知識庫" target='_blank'>JavaScript</a>');
one.showMe(); //<a href="#" class='replace_word' title="JavaScript知識庫" target='_blank'>javascript</a>
按照javascript的說法,function定義的這個Person就是一個Object(對象),而且還是一個很特殊的對象,這個使用function定義的對象與使用new操作符生成的對象之間有一個重要的區別:function定義的對象有一個prototype屬性,使用new生成的對象就沒有這個prototype屬性
function a(c){
this.b = c;
this.d =function(){
alert(this.b);
}
}
var obj = new a('test');
console.log(typeof obj.prototype); //undefine
console.log(typeof a.prototype); //object
console.log(a.prototype);
console.log(obj.__proto__ === a.prototype) //true
上麵的例子可以看出函數的prototype 屬性又指向了一個對象,這個對象就是prototype對象
a.prototype 包含了2個屬性,一個是constructor ,另外一個是__proto__。這個constructor 就是我們的構造函數a,那麼__proto__ 是什麼呢?
這個就涉及到了原型鏈的概念:每個對象都會在其內部初始化一個屬性,就是__proto__,當我們訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那麼他就會去__proto__裏找這個屬性,這個__proto__又會有自己的__proto__,於是就這樣 一直找下去
在這裏我們來分析出new 運算符做了這些事情
var obj={}; 也就是說,初始化一個對象obj。
obj.__proto__=a.prototype;
a.call(obj); 也就是說構造obj,也可以稱之為初始化obj
我們將這個例子改造一些,變得複雜一點
function a(c){
this.b = c;
this.d =function(){
console.log(this.b);
}
}
a.prototype.test = function(){
console.log(this.b);
}
var obj = function (){}
obj.prototype = new a('test');
obj.prototype.test1 =function(){
console.log(22222);
}
var t = new obj('test');
t.test(); //console.log('test');
我們來分析下這個過程:
由 var t = new obj('test');
我們可以得到 t.__proto__ = obj.prototype
,但是上麵指定obj.prototype =new a('test');
可以這樣來看下
obj.prototype = p, p = new a('test'); p.__proto__ = a.prototype;
那麼obj.prototype.__proto__ = a.prototype
,由 t.__proto__ = obj.prototype
可以得出 t.__proto__.__proto__ = a.prototype
,所以對象t先去找本身的prototype 是否有test函數,發現沒有,結果再往上級找,即 t.__proto__
,亦即obj.prototype
尋找test函數 ,但是obj.prototype
也沒有這個函數,然後再往上找。即t.__proto__.__proto__
找,由於t.__proto__.__proto__ = a.prototype
在 a.prototype
中找到了這個方法,輸出了alert('test')
從這裏可以分析得出一個結論,js中原形鏈的本質在於 proto
function a(c){
this.b = c;
this.d =function(){
console.log(this.b);
}
}
var obj = new a('test');
console.log(obj.constructor);//function a(){}
console.log(a.prototype.constructor);//function a(){}
根據上麵講到的__proto__ 我們來分析下,首先obj是沒有constructor 這個屬性的,但是 obj.__proto__ = a.prototype;就從
a.prototype中尋找,而 a.prototype.constructor 是就a,所有兩者的結果是一一樣的
接著看繼承是如何實現的
function Person(name){
this.name=name;
this.showMe=function(){
console.log(this.name);
}
};
Person.prototype.from=function(){
console.log('I come from prototype.');
}
function SubPerson(){}
SubPerson.prototype=new Person();
var subOne=new SubPerson();
subOne.from();//I come from prototype.
console.log(subOne.constructor);//function Person(name) {...};
console.log(SubPerson.prototype.constructor);//function Person(name) {...};
更實際點的意義在於:一個子類對象可以獲得其父類的所有屬性和方法,稱之為繼承。繼承的實現很簡單,隻需要把子類的prototype設置為父類的一個對象即可。注意這裏說的可是對象哦
function Person(name){
this.name=name;
this.showMe=function(){
console.log(this.name);
}
};
Person.prototype.from=function(){
console.log('I come from prototype.');
}
var father=new Person('js');//為了下麵演示使用showMe方法,采用了js參數,實際多采用無參數
console.log(father.constructor);//查看構造函數,結果是:function Person(name) {...};
function SubPer(){}
SubPer.prototype=father;//注意這裏
SubPer.prototype.constructor=SubPer;
var son=new SubPer();
son.showMe();//js
son.from();//I come from prototype.
console.log(father.constructor);//function SubPer(){...}
console.log(son.constructor);//function SubPer(){...}
console.log(SubPer.prototype.constructor);//function SubPer(){...}
之前提到constructor易變,那是因為函數的prototype屬性容易被更改,我們用時下很流行的編碼方式來說明問題:
function F() {}
F.prototype = {
_name: 'Eric',
getName: function() {
return this._name;
}
};
初看這種方式並無問題,但是你會發現下麵的代碼失效了:
var f = new F();
console.log(f.constructor === F); // output false
怎麼回事?F不是實例對象f的構造函數了嗎?
當然是!隻不過構造函數F的原型被開發者重寫了,這種方式將原有的prototype對象用一個對象的字麵量{}來代替,而新建的對象{}隻是Object的一個實例,係統(或者說瀏覽器)在解析的時候並不會在{}上自動添加一個constructor屬性,因為這是function創建時的專屬操作,僅當你聲明函數的時候解析器才會做此動作。
然而你會發現constructor並不是不存在的,下麵代碼可以測試它的存在性:
console.log(typeof f.constructor == 'undefined');// output false
既然存在,那這個constructor是從哪兒冒出來的呢?
我們要回頭分析這個對象字麵量{}。
因為{}是創建對象的一種簡寫,所以{}相當於是new Object()。
那既然{}是Object的實例,自然而然他獲得一個指向構造函數Object()的prototype屬性的一個引用__proto__,又因為Object.prototype上有一個指向Object本身的constructor屬性。所以可以看出這個constructor其實就是Object.prototype的constructor,下麵代碼可以驗證其結論:
alert(f.constructor === Object.prototype.constructor);//output true
alert(f.constructor === Object);// also output true
一個解決辦法就是手動恢複他的constructor,下麵代碼非常好地解決了這個問題:
function F() {}
F.prototype = {
constructor: F, /* reset constructor */
_name: 'Eric',
getName: function() {
return this._name;
}
};
之後一切恢複正常,constructor重新獲得構造函數的引用,我們可以再一次測試上麵的代碼,這次返回true
var f = new F();
alert(f.constructor === F); // output true this time ^^
解惑:構造函數上怎麼還有一個constructor?它又是哪兒來的?
像JavaScript內建的構造函數,如Array, RegExp, String, Number, Object, Function等等自己也有一個constructor:
alert(typeof Array.constructor != 'undefined');// output true
經過測試發現,此物非彼物它和prototype上constructor不是同一個對象,他們是共存的:
alert(typeof Array.constructor != 'undefined');// output true
alert(typeof Array.prototype.constructor === Array); // output true
不過這件事情也是好理解的,因為構造函數也是函數。是函數說明它就是Function構造函數的實例對象,自然他內部也有一個指向Function.prototype的內部引用__proto__。因此我們很容易得出結論,這個constructor(構造函數上的constructor不是prototype上的)其實就是Function構造函數的引用:
alert(Array.constructor === Function);// output true
alert(Function.constructor === Function); // output true
最後更新:2017-06-20 13:01:43