詳解js中的幾種常用設(shè)計模式
工廠模式
function createPerson(name, age){ var o = new Object(); // 創(chuàng)建一個對象 o.name = name; o.age = age; o.sayName = function(){ console.log(this.name) } return o; // 返回這個對象 } var person1 = createPerson('ccc', 18) var person2 = createPerson('www', 18)
工廠函數(shù)的問題:
工廠模式雖然解決了創(chuàng)建多個相似對象的問題,但是沒有解決對象識別問題(即怎樣知道一個對象的類型)。如下
person1 instanceof createPerson // --> false person1 instanceof Object // --> true
構(gòu)造函數(shù)模式
function Person(name , age){ this.name = name; this.age = age; this.sayName = function(){ console.log(this.name) } } var person1 = new Person('ccc', 18) var person2 = new Person('www', 18) person1.sayName() // --> 'ccc'
person1 和person2 分別保存著Person的一個不同的實例。這兩個對象都有一個constructor(構(gòu)造函數(shù))屬性指向Person。這正是構(gòu)造函數(shù)模式勝過工廠模式的地方。如下:
console.log(person1 instanceof Person) // --> true console.log(person1 instanceof Object) // --> true console.log(person2 instanceof Person) // --> true console.log(person2 instanceof Object) // --> true
構(gòu)造函數(shù)模式與工廠模式的區(qū)別:
- 沒有顯式的創(chuàng)建對象
- 直接將屬性和方法賦給了this對象
- 沒有return 語句
要創(chuàng)建Person的新實例,必須使用new操作符。以這種方式調(diào)用構(gòu)造函數(shù)實際上會經(jīng)歷一下4個步驟:
- 創(chuàng)建一個新對象
- 將構(gòu)造函數(shù)的作用域賦給新對象(因此this就指向了這個新對象)
- 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性)
- 返回新對象
構(gòu)造函數(shù)的問題:
使用構(gòu)造函數(shù)的重要問題,就是每個方法都要在每個實例上重新創(chuàng)建一遍。person1和person2中都有一個名為sayName()的方法,但那兩個方法不是同一個Function實例。因為在ECMAscript中函數(shù)就是對象,因此每定義一個函數(shù),也就是實例化了一個對象。從邏輯角度上講,此時的構(gòu)造函數(shù)也可以你這樣定義:
function Person(name , age){ this.name = name; this.age = age; this.sayName = new Function('console.log(this.name)') // eslint: The Function constructor is eval. (no-new-func) }
這會導(dǎo)致,創(chuàng)建的不同的實例上的同名函數(shù)是不相等的,比如:console.log(person1.sayName() === person2.sayName()) // -->false,然而創(chuàng)建兩個完全相同的任務(wù)的Function實例是沒有必要的??梢酝ㄟ^把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外部來解決這個問題。
function Person(name , age){ this.name = name; this.age = age; this.sayName = sayName } function sayName(){ console.log(this.name) } var person1 = new Person('ccc', 18) var person2 = new Person('www', 18)
這樣,由于sayName包含的是一個指向函數(shù)的指針,因此person1和person2對象就共享了在全局作用域中定義的同一個sayName()函數(shù)。這樣做確實解決了兩個函數(shù)做同一件事的問題,可是新問題又來了:在全局作用域中定義的函數(shù)實際上只能被某個對象調(diào)用,這讓全局作用域有點名不副實。
帶來的新問題:
如果對象需要定義很多方法,那么就要定義很多個全局函數(shù),于是我們這個自定義的引用類型就絲毫沒有封裝性可言。
原型模式
關(guān)于原型,原型鏈內(nèi)容不在此描述,只討論原型設(shè)計模式
我們創(chuàng)建的每一個函數(shù)都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。即不必在構(gòu)造函數(shù)中定義對象實例的信息,而是將這些信息直接添加到原型對象中。
function Person(){ } Person.prototype.name = 'ccc' Person.prototype.age = 18 Person.prototype.sayName = function(){ console.log(this.name) } var person1 = new Person() person1.sayName() // --> ccc var person2 = new Person() person2.sayName() // --> ccc console.log(person1.sayName === person2.sayName) // --> true
原型模式的問題:
它省略了為構(gòu)造函數(shù)傳遞參數(shù)初始化參數(shù)的環(huán)節(jié),結(jié)果所有的實例在默認(rèn)情況下都將取得相同的屬性值。另外,原型模式的最大問題是由其共享的本性所導(dǎo)致的??慈缦聠栴}:
function Person(){ } Person.prototype = { constructor: Person, name: 'ccc', age: 18, friends:['www', 'aaa'], sayName: function () { console.log(this.name) } } var person1 = new Person() var person2 = new Person() person1.friends.push('bbb') console.log(person1.friends) // --> ["www", "aaa", "bbb"] console.log(person2.friends) // --> ["www", "aaa", "bbb"] console.log(person1.friends === person2.friends) // --> true
帶來的新問題:
如果我們的初衷就是這樣,所有的實例共用一個數(shù)組,那么這個結(jié)果就是想要的。可是,實例一般都是要有屬于自己的全部屬性的,這個問題正是我們很少看到有人單獨使用原型模式的原因所在。
組合使用構(gòu)造函數(shù)模式和原型模式
創(chuàng)建自定義類型的最常見方式,就是組合使用構(gòu)造函數(shù)模式與原型模式。構(gòu)造函數(shù)模式用于定義實例屬性,而原型模式用于定義方法和共享的屬性。這種方式還支持向構(gòu)造函數(shù)傳遞參數(shù)。
function Person(name, age){ this.name = name; this.age = age; this.friends = ['aaa', 'bbb'] } Person.prototype = { constructor: Person, sayName: function(){ console.log(this.name) } } var person1 = new Person('ccc', 18) var person2 = new Person('www', 18) person1.friends.push('ddd') console.log(person1.friends) // --> ["aaa", "bbb", "ddd"] console.log(person2.friends) // --> ["aaa", "bbb"] console.log(person1.friends === person2.friends) // --> false console.log(person1.sayName === person2.sayName) // --> true
這種構(gòu)造函數(shù)與原型混成的模式,是目前ECMAscript中使用最廣泛、認(rèn)同度最高的一種創(chuàng)建自定義類型的方法??梢哉f,這是用來定義引用類型的一種默認(rèn)方式。
動態(tài)原型模式
動態(tài)原型模式就是可以通過檢查某個應(yīng)該存在的方法是否有效,來決定是否需要初始化原型。
function Person(name, age){ // 屬性 this.name = name this.age = age // 方法 if(typeof this.sayName !== 'function'){ Person.prototype.sayName = function(){ console.log(this.name) } } } var person1 = new Person('ccc', 18) person1.sayName() // --> ccc
這里只有在sayName()方法不存在的情況下,才會將它添加到原型中。這段代碼只會在初次調(diào)用構(gòu)造函數(shù)時才會執(zhí)行。
注意:
- 在這里對原型所做的修改,能夠立即在所有實例中得到反映。
- 使用動態(tài)原型模式時,不能使用對象字面量重寫原型。如果在已經(jīng)創(chuàng)建了實例的情況下重寫原型,那么就會切斷現(xiàn)有實例與新原型之間的聯(lián)系。(參考原型與原型鏈中的內(nèi)容)
其它模式
還有寄生構(gòu)造函數(shù)模式和穩(wěn)妥構(gòu)造函數(shù)模式,可自行了解。以上所以知識內(nèi)容來自《JavaScript高級程序設(shè)計》(第三版)。
以上就是詳解js中的幾種常用設(shè)計模式的詳細(xì)內(nèi)容,更多關(guān)于JS 設(shè)計模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
typescript+react實現(xiàn)移動端和PC端簡單拖拽效果
這篇文章主要為大家詳細(xì)介紹了typescript+react實現(xiàn)移動端和PC端簡單拖拽效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09JS實現(xiàn)把一個頁面層數(shù)據(jù)傳遞到另一個頁面的兩種方式
這篇文章主要介紹了JS實現(xiàn)把一個頁面層數(shù)據(jù)傳遞到另一個頁面的方式,本文給大家提供了兩種方式,需要的朋友可以參考下2018-08-08TypeScript調(diào)整數(shù)組元素順序算法
數(shù)組類型在TS中可以使用多種方式,比較靈活,下面這篇文章主要給大家介紹了關(guān)于TypeScript調(diào)整數(shù)組元素順序算法的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04