建構式模式
建構式可以用來創建特定型別的物件,像 Object 和 Array 這樣的原生建構式,在運行時會自動出現在執行環境中,此外,也可以創建自定義的建構式,從而定義自定義的物件型別的屬性或者方法,例如,可以使用建構式模式將前面的工廠模式的例子重寫:
function preson(name, age, sex, sleep){
this.name = name
this.age = age
this.sex = sex
this.sleep = function(){
alert(this.name + '睡覺了')
}
}
var preson1 = new preson('小明', 22, '男')
var preson2 = new preson('小紅', 22, '女')
在這個例子中,preson() 函式取代了 createpreson() 函式,它們的不同之處:
- 沒有顯式地創建物件
- 直接將屬性和方法賦值給了 this 物件
- 沒有 return陳述句
此外,建構式的函式名 preson 的第一個字母是大寫字母,建構式的開頭字母都需要一個大寫的字母,非建構式則應該是小寫字母開頭,
建構式本身也是函式,只不過可以用來創建物件而已,
要創建 preson 的新實體,必須使用 new 運算子,以這種方式呼叫建構式實際上會經歷以下4個步驟:
- 創建一個新物件
- 將建構式的作用域賦給上一步創建的新物件(因此 this 指向了這個新物件)
- 執行建構式中的代碼(為這個新物件添加屬性)
- 回傳新物件
在前面例子最后,preson1 和 preson2 分別保存者 preson 的一個不同的實體,這兩個物件都有一個 constructor (構造屬性),該屬性指向 preson,如下所示:
console.log(preson1.constructor === preson) // true
console.log(preson2.constructor === preson) // true
物件的 constructor 屬性最初是用來標識物件型別的,但是,提到檢測物件型別,還是 instanceof 運算子更可靠一些,在例子中創建的所有物件既是 Object 的實體,同使也是 preson 的實體,這一點通過 instanceof 運算子可以得到驗證,
console.log(preson1 instanceof Object) // true
console.log(preson1 instanceof preson) // true
console.log(preson2 instanceof Object) // true
console.log(preson2 instanceof preson) // true
創建自定義的建構式意味著將來可以將它的實體標識為一種特定的型別,而這正是建構式模式勝過工廠模式的地方
-
將建構式當作函式
建構式于其他函式的唯一區別,就在于呼叫它們的方式不同,不過,建構式畢竟也是函式,不存在定義建構式的特殊語法,然和函式,只要通過 new 運算子來呼叫,那么它就可以作為建構式;而任何函式,如果不通過 new 運算子來呼叫,那么它就和普通函式一樣,例如前面的例子中定義的 preson() 函式可以通過以下任意一種方式來呼叫,// 當作建構式使用 var preson = new preson('小明', 22, '男') console.log(preson) // preson {name: "小明", age: 22, sex: "男", sleep: ?} preson.sleep() // 小明睡覺了 // 作為普通函式呼叫,由于沒有 new 出一個新的物件,他的this指向的是所在環境的物件,即window preson('小紅', 22, '女') console.log(name, age, sex) sleep() // 小紅睡覺了 // 在另一個物件的作用域中呼叫 const o = new Object() preson.call(o, '小美', 22, '男') console.log(o) // {name: "小美", age: 22, sex: "男", sleep: ?} o.sleep() // 小美睡覺了 -
建構式的問題
建構式模式雖然好用,但也并非沒有缺點,使用建構式的主要問題,就是每個方法都要在每個實體上重新創建一遍,在前面的例子中,preson1 和 preson2 都有一個名為 sleep() 的方法,但是那兩個方法不是同一個 Function 的實體,ECMAScript 中的函式是物件,因此沒定義一個函式,也就是實體化了一個物件,從邏輯角度講,此時的建構式也可以這樣定義:function preson(name, age, sex){ this.name = name this.age = age this.sex = sex this.sleep = new Function('alert(this.name)') // 與宣告函式在邏輯上是等價的 }從這個角度上來看建構式,更容易明白每個 preson 實體都包含了一個不同的 Function 實體的本質,說明白些,以這種方式創建函式,會導致不同的作用域鏈和識別符號決議,但是創建 Function 新實體的機制仍然是相同的,因此,在不同實體上的同名函式是不相等的,以下代碼可以證明這一點:
console (preson1.sleep === preson.sleeo) // false然而,創建兩個完成同樣任務的 Function 實體的確沒必要,況且有 this 物件在,根本不用執行代碼前就把這個函式系結到特定的物件上面,因此,大可以像下面這樣,通過把函式定義轉移到建構式外部來解決這個問題
function preson(name, age, sex){ this.name = name this.age = age this.sex = sex this.sleep = sleep } function sleep(){ alert(this.name + '睡覺了') } var preson1 = new preson('小明', 22, '男') var preson2 = new preson('小紅', 22, '女') console.log(preson1.sleep === preson2.sleep) // true在這個例子中,把 sleep() 函式的定義轉移到了建構式外部,而在建構式內部,我們將 sleep 屬性設定成全域的 sleep 函式,這樣一來,由于 sleep 包含的是一個指向函式的指標,因此 preson1 和 preson2 物件就共享了在全域作用域中定義的同一個 sleep 函式,
這樣做確實解決了兩個函式做同一件事的問題,可是新的問題又來了:
在全域作用域中定義的函式實際上只能被某個物件呼叫,這然全域作用域優點名不副實,而更讓人無法接受的是:如果物件需要定義很多方法,那么就要定義很多個全域函式,于是我們這個自定義的參考型別就絲毫沒有封裝性可言了,好在,這些問題可以通過原型模式來解決,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/253891.html
標籤:JavaScript
上一篇:原始模式和工廠模式
下一篇:溫習資料演算法—js滑塊驗證碼
