閱讀456 返回首頁    go 阿裏雲 go 技術社區[雲棲]


關於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.prototypea.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

  上一篇:go  超經典互聯網爆笑段子句句笑趴下
  下一篇:go  《科技之巔2》序——機器智能數據智能:工具之王