深入理解js建構函式

2022-09-06 04:39:10 字數 3899 閱讀 6279

在j**ascript中,建立物件的方式包括兩種:物件字面量和使用new表示式。

1.1物件字面量是一種靈活方便的書寫方式,例如:

var o1 =

}

這樣,就用物件字面量建立了乙個物件o1,它具有乙個成員變數p以及乙個成員方法alertp。

這種寫法的缺點是:每建立乙個新的物件都需要寫出完整的定義語句,不便於建立大量相同型別的物件,不利於使用繼承等高階特性。

1.2new表示式是配合建構函式使用的,例如new string(「a string」),呼叫內建的string函式構造了乙個字串物件。

下面我們用建構函式的方式來重新建立乙個實現同樣功能的物件,首先是定義建構函式,然後是呼叫new表示式:

function

co()

}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。當建構函式裡包含返回語句時情況比較特殊,這種情況會在下文中說到。

不同於其它的主流程式語言,j**ascript的建構函式並不是作為類的乙個特定方法存在的;

當任意乙個普通函式用於建立一類物件時,它就被稱作建構函式,或構造器。

乙個函式要作為乙個真正意義上的建構函式,需要滿足下列條件:

上文定義的建構函式co就是乙個標準的、簡單的建構函式。

下面例子定義的函式c1返回了乙個物件,我們可以使用new表示式來呼叫它,該表示式可以正確返回乙個物件:

function

c1()

returno;}

var o1 = new

c1();

alert(o1.p);

// hello world

但這種方式並不是值得推薦的方式,因為物件o1的原型是函式c1內部定義的物件o的原型,也就是object.prototype。

這種方式相當於執行了 正常new表示式的前三步,而在第四步的時候返回了c1函式的返回值。

該方式同樣不便於建立大量相同型別的物件,不利於使用繼承等高階特性,並且容易造成混亂,應該摒棄。

乙個建構函式在某些情況下完全可以作為普通的功能函式來使用,這是j**ascript靈活性的乙個體現。

下例定義的c2就是乙個「多用途」函式:

function

c2(a, b)

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屬性,使得通過建構函式建立的所有物件可以共享相同的原型。

這意味著同乙個建構函式建立的所有物件都繼承自乙個相同的物件,因此它們都是同乙個類的物件。

在j**ascript標準中,並沒有__prop__這個屬性,不過它現在已經是一些主流的j**ascript執行環境預設的乙個標準屬性,用於指向建構函式的原型。

該屬性是預設不可見的,而且在各執行環境中實現的細節不盡相同,例如ie瀏覽器中不存在該屬性。我們只要知道j**ascript物件內部存在指向建構函式原型的指標就可以了,這個指標是在呼叫new表示式的時候自動賦值的,並且我們不應該去修改它。

在構造物件的四個步驟中,我們可以看到,除第二步以外,別的步驟我們無須借助new表示式去實現,因此new表示式不僅僅是對這四個步驟的簡化,也是要實現繼承的必經之路。

關於j**ascript的

建構函式,有乙個容易混淆的地方,那就是原型的constructor屬性。

在j**ascript中,每乙個函式都有預設的原型物件屬性

prototype,該物件預設包含了兩個成員屬性:constructor和__proto__。

按照物件導向的習慣性思維,我們說建構函式相當於「類」的定義,從而可能會認為constructor屬性就是該類實際意義上的建構函式,在new表示式

建立乙個物件的時候,會直接呼叫constructor來初始化物件,那就大錯特錯了。

new表示式執行的實際過程已經在上文中介紹過了(四個步驟),其中用於初始化物件的是第三步,呼叫的初始化函式正是「類函式」本身,而不是constructor。

如果沒有考慮過這個問題,這一點可能不太好理解,那就讓我們舉個例子來說明一下吧:

function

c3(a, b)}//

我們定義乙個函式來覆蓋c3原型中的constructor,試圖改變屬性p的值

function

fake()

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() 

function s()

s.prototype = new f(); //

s繼承自f

var son = new s(); //

用建構函式s建立乙個子類物件

(son.constructor == s); //

false

(son.constructor == f); //

true

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

深入理解各種建構函式

include includeusing namespace std class test else test const test t else test operator const test t pdata new char strlen t.pdata 1 strcpy pdata t.pd...

C 建構函式深入理解

01 初始化引數列表.cpp include include include using namespace std struct student 拷貝建構函式定義 拷貝建構函式的有效引數,必須是該類的物件的引用 if 1 student student s,int class no 1 else ...

js箭頭函式深入理解

let f a let f a let f a return alet f a a f 2 2let f 如果沒括號,花括號內會被視作待執行語句let callback callback callback function ok,普通函式 callback callback syntaxerror ...