在JavaScript中,建立對象的方式包括兩種:對象字面量和使用new表達式。html
1.1 對象字面量是一種靈活方便的書寫方式,例如:編程
var o1 = { p:"hello world", alertP:function(){ alert(this.p); } }
這樣,就用對象字面量建立了一個對象o1,它具備一個成員變量p以及一個成員方法alertP。瀏覽器
這種寫法的缺點是:每建立一個新的對象都須要寫出完整的定義語句,不便於建立大量相同類型的對象,不利於使用繼承等高級特性。函數
1.2 new表達式是配合構造函數使用的,例如new String(「a string」),調用內置的String函數構造了一個字符串對象。測試
下面咱們用構造函數的方式來從新建立一個實現一樣功能的對象,首先是定義構造函數,而後是調用new表達式:this
function CO(){ this.p = 「I’m in constructed object」; this.alertP = function(){ alert(this.p); } } var o2 = newCO();
那麼,在使用new操做符來調用一個構造函數的時候,發生了什麼呢?其實很簡單,就發生了四件事:spa
var obj ={}; obj.__proto__ = CO.prototype; CO.call(obj); return obj;
第一行,建立一個空對象obj。prototype
第二行,將這個空對象的__proto__成員指向了構造函數對象的prototype成員對象,這是最關鍵的一步,具體細節將在下文描述。指針
第三行,將構造函數的做用域賦給新對象,所以CA函數中的this指向新對象obj,而後再調用CO函數。因而咱們就給obj對象賦值了一個成員變量p,這個成員變量的值是」 I’min constructed object」。code
第四行,返回新對象obj。當構造函數裏包含返回語句時狀況比較特殊,這種狀況會在下文中說到。
不一樣於其它的主流編程語言,JavaScript的構造函數並非做爲類的一個特定方法存在的;
當任意一個普通函數用於建立一類對象時,它就被稱做構造函數,或構造器。
一個函數要做爲一個真正意義上的構造函數,須要知足下列條件:
上文定義的構造函數CO就是一個標準的、簡單的構造函數。
下面例子定義的函數C1返回了一個對象,咱們可使用new表達式來調用它,該表達式能夠正確返回一個對象:
function C1(){ var o = { p:'hello world' } return o; } var o1 = new C1(); alert(o1.p); // hello world
但這種方式並非值得推薦的方式,由於對象o1的原型是函數C1內部定義的對象o的原型,也就是Object.prototype。
這種方式至關於執行了 正常new表達式的前三步,而在第四步的時候返回了C1函數的返回值。
該方式一樣不便於建立大量相同類型的對象,不利於使用繼承等高級特性,而且容易形成混亂,應該摒棄。
一個構造函數在某些狀況下徹底能夠做爲普通的功能函數來使用,這是JavaScript靈活性的一個體現。
下例定義的C2就是一個「多用途」函數:
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();
建立對象的時候,發生了四件事情:
var obj ={}; obj.__proto__ = CO.prototype; CO.call(obj); return obj;
咱們說最重要的是第二步,將新生成的對象的__prop__屬性賦值爲構造函數的prototype屬性,使得經過構造函數建立的全部對象能夠共享相同的原型。
這意味着同一個構造函數建立的全部對象都繼承自一個相同的對象,所以它們都是同一個類的對象。
在JavaScript標準中,並無__prop__這個屬性,不過它如今已是一些主流的JavaScript執行環境默認的一個標準屬性,用於指向構造函數的原型。
該屬性是默認不可見的,並且在各執行環境中實現的細節不盡相同,例如IE瀏覽器中不存在該屬性。咱們只要知道JavaScript對象內部存在指向構造函數原型的指針就能夠了,這個指針是在調用new表達式的時候自動賦值的,而且咱們不該該去修改它。
在構造對象的四個步驟中,咱們能夠看到,除第二步之外,別的步驟咱們無須藉助new表達式去實現,所以new表達式不只僅是對這四個步驟的簡化,也是要實現繼承的必經之路。
關於JavaScript的 構造函數,有一個容易混淆的地方,那就是原型的constructor屬性。
在JavaScript中,每個函數都有默認的原型對象屬性 prototype,該對象默認包含了兩個成員屬性:constructor和__proto__。
按照面向對象的習慣性思惟,咱們說構造函數至關於「類」的定義,從而可能會認爲constructor屬性就是該類實際意義上的構造函數,在new表達式 建立一個對象的時候,會直接調用constructor來初始化對象,那就大錯特錯了。
new表達式執行的實際過程已經在上文中介紹過了(四個步驟),其中用於初始化對象的是第三步,調用的初始化函數正是「類函數」自己,而不是constructor。
若是沒有考慮過這個問題,這一點可能不太好理解,那就讓咱們舉個例子來講明一下吧:
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屬性來測試對象的類型:
var myArray = [1,2,3]; (myArray.constructor == Array); // true
這招對於簡單的對象是管用的,涉及到繼承或者跨窗口等複雜狀況時,可能就沒那麼靈光了:
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屬性的時候必定要當心,或者乾脆不要用它。