深刻理解js構造函數

JavaScript對象的建立方式

在JavaScript中,建立對象的方式包括兩種:對象字面量和使用new表達式。對象字面量是一種靈活方便的書寫方式,例如:html

 

?
1
2
3
4
5
6
var o1 = {
     p:」I’m in Object literal」,
     alertP:function(){
         alert( this .p);
     }
}

 

這樣,就用對象字面量建立了一個對象o1,它具備一個成員變量p以及一個成員方法alertP。這種寫法不須要定義構造函數,所以不在本文的討論範圍以內。這種寫法的缺點是,每建立一個新的對象都須要寫出完整的定義語句,不便於建立大量相同類型的對象,不利於使用繼承等高級特性。java

new表達式是配合構造函數使用的,例如new String(「a string」),調用內置的String函數構造了一個字符串對象。下面咱們用構造函數的方式來從新建立一個實現一樣功能的對象,首先是定義構造函數,而後是調用new表達式:編程

 

?
1
2
3
4
5
6
7
function CO(){
     this .p = 「I’m in constructed object」;
     this .alertP = function(){
         alert( this .p);
     }
}
var o2 = newCO();

 

那麼,在使用new操做符來調用一個構造函數的時候,發生了什麼呢?其實很簡單,就發生了四件事:瀏覽器

?
1
2
3
4
var obj  ={};
obj.__proto__ = CO.prototype;
CO.call(obj);
return obj;

 

第一行,建立一個空對象obj。編程語言

第二行,將這個空對象的__proto__成員指向了構造函數對象的prototype成員對象,這是最關鍵的一步,具體細節將在下文描述。函數

第三行,將構造函數的做用域賦給新對象,所以CA函數中的this指向新對象obj,而後再調用CO函數。因而咱們就給obj對象賦值了一個成員變量p,這個成員變量的值是」 I’min constructed object」。測試

第四行,返回新對象obj。當構造函數裏包含返回語句時狀況比較特殊,這種狀況會在下文中說到。this

 

正肯定義JavaScript構造函數

不一樣於其它的主流編程語言,JavaScript的構造函數並非做爲類的一個特定方法存在的;當任意一個普通函數用於建立一類對象時,它就被稱做構造函數,或構造器。一個函數要做爲一個真正意義上的構造函數,須要知足下列條件:spa

一、 在函數內部對新對象(this)的屬性進行設置,一般是添加屬性和方法。prototype

二、 構造函數能夠包含返回語句(不推薦),但返回值必須是this,或者其它非對象類型的值。

上文定義的構造函數CO就是一個標準的、簡單的構造函數。下面例子定義的函數C1返回了一個對象,咱們可使用new表達式來調用它,該表達式能夠正確返回一個對象:

 

?
1
2
3
4
5
6
7
8
function C1(){
     var o = {
         p:」I’m p in C1」
     }
     return o;
}
var o1 = new C1();
alert(o1.p); //I’m p in C1

 

但這種方式並非值得推薦的方式,由於對象o1的原型是函數C1內部定義的對象o的原型,也就是Object.prototype。這種方式至關於執行了正常new表達式的前三步,而在第四步的時候返回了C1函數的返回值。該方式一樣不便於建立大量相同類型的對象,不利於使用繼承等高級特性,而且容易形成混亂,應該摒棄。

一個構造函數在某些狀況下徹底能夠做爲普通的功能函數來使用,這是JavaScript靈活性的一個體現。下例定義的C2就是一個「多用途」函數:

 

?
1
2
3
4
5
6
7
8
9
10
function C2(a, b){
     this .p = a + b;
     this .alertP = function(){
         alert( this .p);
     }
     return this .p; //此返回語句在C2做爲構造函數時沒有意義
}
var c2 = new C2( 2 , 3 );
c2.alertP(); //結果爲5
alert(C2( 2 , 3 )); //結果爲5

 

該函數既能夠用做構造函數來構造一個對象,也能夠做爲普通的函數來使用。用做普通函數時,它接收兩個參數,並返回二者的相加的結果。爲了代碼的可讀性和可維護性,建議做爲構造函數的函數不要摻雜除構造做用之外的代碼;一樣的,通常的功能函數也不要用做構造對象。

 

爲何要使用構造函數

根據上文的定義,在表面上看來,構造函數彷佛只是對一個新建立的對象進行初始化,增長一些成員變量和方法;然而構造函數的做用遠不止這些。爲了說明使用構造函數的意義,咱們先來回顧一下前文提到的例子。執行var o2 = new CO();建立對象的時候,發生了四件事情:

 

?
1
2
3
4
var obj  ={};
obj.__proto__ = CO.prototype;
CO.call(obj);
return obj;

 

咱們說最重要的是第二步,將新生成的對象的__prop__屬性賦值爲構造函數的prototype屬性,使得經過構造函數建立的全部對象能夠共享相同的原型。這意味着同一個構造函數建立的全部對象都繼承自一個相同的對象,所以它們都是同一個類的對象。關於原型(prototype)和繼承的細節,筆者會再另外一篇文章中深刻說明。

在JavaScript標準中,並無__prop__這個屬性,不過它如今已是一些主流的JavaScript執行環境默認的一個標準屬性,用於指向構造函數的原型。該屬性是默認不可見的,並且在各執行環境中實現的細節不盡相同,例如IE瀏覽器中不存在該屬性。咱們只要知道JavaScript對象內部存在指向構造函數原型的指針就能夠了,這個指針是在調用new表達式的時候自動賦值的,而且咱們不該該去修改它。

在構造對象的四個步驟中,咱們能夠看到,除第二步之外,別的步驟咱們無須藉助new表達式去實現,所以new表達式不只僅是對這四個步驟的簡化,也是要實現繼承的必經之路。

 

容易混淆的地方

關於JavaScript的構造函數,有一個容易混淆的地方,那就是原型的constructor屬性。在JavaScript中,每個函數都有默認的原型對象屬性prototype,該對象默認包含了兩個成員屬性:constructor和__proto__。關於原型的細節就不在本文贅述了,咱們如今關心的是這個constructor屬性。

按照面向對象的習慣性思惟,咱們說構造函數至關於「類」的定義,從而可能會認爲constructor屬性就是該類實際意義上的構造函數,在new表達式建立一個對象的時候,會直接調用constructor來初始化對象,那就大錯特錯了。new表達式執行的實際過程已經在上文中介紹過了(四個步驟),其中用於初始化對象的是第三步,調用的初始化函數正是「類函數」自己,而不是constructor。若是沒有考慮過這個問題,這一點可能不太好理解,那就讓咱們舉個例子來講明一下吧:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
function C3(a, b){
     this .p = a + b;
     this .alertP = function(){
         alert( this .p);
     }
}
//咱們定義一個函數來覆蓋C3原型中的constructor,試圖改變屬性p的值
function fake(){
     this .p = 100 ;
}
C3.prototype.constructor = fake; //覆蓋C3原型中的constructor
var c3 = new C3( 2 , 3 );
c3.alertP(); //結果仍然爲5

 

上述代碼手動改變了C3原型中的constructor函數,然而卻沒有對c3對象的建立產生實質的影響,可見在new表達式中,起初始化對象做用的只能是構造函數自己。那麼constructor屬性的做用是什麼呢?通常來講,咱們可使用constructor屬性來測試對象的類型:

 

?
1
2
var myArray = [ 1 , 2 , 3 ];
(myArray.constructor == Array); // true

 

這招對於簡單的對象是管用的,涉及到繼承或者跨窗口等複雜狀況時,可能就沒那麼靈光了:

 

?
1
2
3
4
5
6
7
function f() { this .foo = 1 ;}
function s() { this .bar = 2 ; }
s.prototype = new f(); // s繼承自f
  
var son = new s(); // 用構造函數s建立一個子類對象
(son.constructor == s); // false
(son.constructor == f); // true

 

這樣的結果可能跟你的預期不相一致,因此使用constructor屬性的時候必定要當心,或者乾脆不要用它。

 

引用地址:http://www.2cto.com/kf/201402/281841.html

相關文章
相關標籤/搜索