一、JavaScript對象的創建方式
在JavaScript中,創建對象的方式包括兩種:對象字面量和使用new表達式。
1.1 對象字面量是一種靈活方便的書寫方式,例如:
var o1 = { p:"hello world", alertP:function(){ alert(this.p); } }
這樣,就用對象字面量創建了一個對象o1,它具有一個成員變量p以及一個成員方法alertP。
這種寫法的缺點是:每創建一個新的對象都需要寫出完整的定義語句,不便於創建大量相同類型的對象,不利於使用繼承等高級特性。
1.2 new表達式是配合構造函數使用的,例如new String(“a string”),調用內置的String函數構造了一個字符串對象。
下面我們用構造函數的方式來重新創建一個實現同樣功能的對象,首先是定義構造函數,然后是調用new表達式:
function CO(){ this.p = “I’m in constructed object”; this.alertP = function(){ alert(this.p); } } var o2 = newCO();
那么,在使用new操作符來調用一個構造函數的時候,發生了什么呢?其實很簡單,就發生了四件事:
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。當構造函數里包含返回語句時情況比較特殊,這種情況會在下文中說到。
二、正確定義JavaScript構造函數
不同於其它的主流編程語言,JavaScript的構造函數並不是作為類的一個特定方法存在的;
當任意一個普通函數用於創建一類對象時,它就被稱作構造函數,或構造器。
一個函數要作為一個真正意義上的構造函數,需要滿足下列條件:
- 1、 在函數內部對新對象(this)的屬性進行設置,通常是添加屬性和方法。
- 2、 構造函數可以包含返回語句(不推薦),但返回值必須是this,或者其它非對象類型的值。
上文定義的構造函數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屬性的時候一定要小心,或者干脆不要用它。
文章轉自:http://www.2cto.com/kf/201402/281841.html