JS函數(shù)的工廠模式、構(gòu)造函數(shù)模式、原型模式的區(qū)別

創(chuàng)建對(duì)象

JS有六種數(shù)據(jù)數(shù)據(jù)類型,其中五種屬于基本數(shù)據(jù)類型:Null、Boolean、undefined、String、Number。
而其它值都是對(duì)象。數(shù)組是對(duì)象,函數(shù)是對(duì)象,正則表達(dá)式是對(duì)象。對(duì)象也是對(duì)象。

來看一下對(duì)象的定義:

無序?qū)傩缘募希鋵傩钥梢园局?、?duì)象、或者函數(shù)。

我們通過對(duì)象字面量的方式 創(chuàng)建對(duì)象。創(chuàng)建的對(duì)象用于我們想要做的事。但是,如果只有對(duì)象,那么可以想象我們寫的代碼將全是光禿禿的對(duì)象,會(huì)產(chǎn)生大量的重復(fù)代碼,和命名沖突等等問題。這是非常非常糟糕的。
為了解決這個(gè)問題,人們開始使用 工廠模式的一種變體。

工廠模式

工廠模式抽象了具體對(duì)象的過程。也就是說,發(fā)明了一種函數(shù),把對(duì)象放到函數(shù)里,用函數(shù)封裝創(chuàng)建對(duì)象的細(xì)節(jié)。

function createPerson (name,age) {
    var o = {
        name : name,
        age : age,    
        sayName : function () {
            alert(this.name)
        }
    }
    return o;
}
var person1 = createPerson("Tom",14);
var person2 = createPerson("Jerry",18)

console.log(person1 instanceof Object)  //true
console.log(person1 instanceof createPerson)  //false

instanceof 用于檢測(cè)數(shù)據(jù)類型
var aa = []
console.log(aa instanceof Array)  //true

工廠模式解決了代碼復(fù)用的問題,但是卻沒有解決對(duì)象識(shí)別的問題。即創(chuàng)建的所有實(shí)例都是Object類型。
為了解決這一問題,就有了構(gòu)造函數(shù)模式

構(gòu)造函數(shù)模式

function Person (name,age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        alert(this.name)
    }
}
    var person1 = new Person('Tom',14);
    var Person2 = new Person('Jerry',18);
  1. 構(gòu)造函數(shù) Person 有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象即:Person.prototype(原型對(duì)象);
  2. 實(shí)例person1 person2也有一個(gè)[[prototype]]屬性或者叫proto,這個(gè)屬性 也指向Person.prototype;
  3. 構(gòu)造函數(shù)、和實(shí)例 都共享Person.prototype里的 屬性和方法;
  4. Person.prototype里有一個(gè) constructor屬性,這個(gè)屬性也是一個(gè)指針,指向構(gòu)造函數(shù)Person。這樣以來,實(shí)例 也指向了Person,那么實(shí)例 也共享了構(gòu)造函數(shù)的屬性和方法。
  5. 構(gòu)造函數(shù)、實(shí)例、原型對(duì)象里所有的屬性和方法 都是共享的。

構(gòu)造函數(shù)解決了對(duì)象識(shí)別問題,我們?cè)谶@個(gè)例子中創(chuàng)建的對(duì)所有對(duì)象既是Object的實(shí)例,同時(shí),也是Person的實(shí)例。這一點(diǎn)通過instanceof操作符可以得到驗(yàn)證。

console.log(person1 instanceof Object)  //true
console.log(person1 instanceof Person)  //true

創(chuàng)建自定義的構(gòu)造函數(shù)意味著,將來可以將它的實(shí)例 標(biāo)識(shí)為一種特定類型;這正是構(gòu)造函數(shù)勝過工廠模式的地方。Array就是這種方式(我認(rèn)為的)。用構(gòu)造函數(shù)的方式,實(shí)例化一個(gè)新對(duì)象,這個(gè)對(duì)象可以是其它類型例如Array類型。

 function Array () {

 }
 var arr = new Array()
 arr instanceof Array  // true

構(gòu)造函數(shù)與普通函數(shù)的區(qū)別

構(gòu)造函數(shù)和普通函數(shù)的唯一區(qū)別,在于調(diào)用它們的方式不同。

當(dāng)作構(gòu)造函數(shù)使用

    function Person (name,age) {
        console.log(this)   
    }
    var person = new Person()

需要注意的是,this 指向 構(gòu)造函數(shù)Person

當(dāng)作普通函數(shù)使用

 function Person (name,age) {
    console.log(this)
 }
 Person()

this指向widow.

構(gòu)造函數(shù)的問題

構(gòu)造函數(shù)雖然好用,但也有缺點(diǎn)。既每個(gè)new出來的實(shí)例 里的方法都要重新創(chuàng)建一遍。在前面的例子中person1 person2 都有一個(gè)sayName方法,但這兩個(gè)方法不是同一個(gè)Function實(shí)例!每個(gè)實(shí)例的方法 都是不同的,不相等的。這是不合理的!

function Person (name) {
    this.name = name;
    this.sayName = new Function ("alert(this.name)")
}
function Person (name) {
    this.name = name;
    this.sayName = function () {
        alert(this.name)
    }
}
alert( person1.sayName == person2.sayName )  //false

由此可見,完成同樣任務(wù)的函數(shù)確實(shí)沒必要 每個(gè)實(shí)例,就實(shí)例一次。
于是,有需求,就有解決方案。原型模式。

原型模式

我們創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象,而這個(gè)對(duì)象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法。
使用原型對(duì)象的好處是可以讓所有對(duì)象實(shí)例共享它所包含的屬性和方法。也就是說,不必在構(gòu)造函數(shù)中定義對(duì)象實(shí)例的信息,而是將這些信息添加到原型對(duì)象中。

廢話少說,那么到底 原型模式是如何解決 每個(gè)實(shí)例的方法 是同一個(gè)呢?
看代碼:

function Person (){
}
Person.prototype.name = "Tom";
Person.prototype.sayName = function () {
    alert(this.name)
};
或者  
Person.prototype = {
    constructor : Person,
    name : "Tom",
    sayName : function () {
        alert(this.name)
    }
}
var person1 = new Person();
person1.sayName();  //"Tom"

var person2 = new Person();
person2.sayName(); //"Tom"

alert( person1.sayName == persona2.sayName )   //true

再來看下這個(gè):

  1. 構(gòu)造函數(shù) Person 有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象即:Person.prototype(原型對(duì)象);
  2. 實(shí)例person1 person2也有一個(gè)[[prototype]]屬性或者叫proto,這個(gè)屬性 也指向Person.prototype;
  3. 構(gòu)造函數(shù)、和實(shí)例 都共享Person.prototype里的 屬性和方法;
  4. Person.prototype里有一個(gè) constructor屬性,這個(gè)屬性也是一個(gè)指針,指向構(gòu)造函數(shù)Person。這樣以來,實(shí)例 也指向了Person,那么實(shí)例 也共享了構(gòu)造函數(shù)的屬性和方法。
  5. 構(gòu)造函數(shù)、實(shí)例、原型對(duì)象里所有的屬性和方法 都是共享的。

與構(gòu)造函數(shù)相比,
原型模式,把公共方法提出來放到prototype對(duì)象里。
每個(gè)實(shí)例 的[[prototype]]指針 指向這個(gè)對(duì)象,所以所有實(shí)例的公共方法 是同一個(gè)。這樣也避免了內(nèi)存浪費(fèi)。

原型模式的其它特性

詳情參見 《JS高程3》 第六章

原型模式的問題###

我們寫代碼的時(shí)候,很少只用到原型模式,說明它還是有坑的。
原型模式很大的優(yōu)點(diǎn) 是所有實(shí)例都共享屬性和方法。這個(gè)很好的優(yōu)點(diǎn) 對(duì)于函數(shù)來說非常合適,可對(duì)于屬性來說,有點(diǎn)不適應(yīng)這種開放的"熱情"。
看代碼:

function Person () {
}
Person.prototype = {
    constructor : Person,
    name : "Tom",
    friends : ["Jerry","Sara"]
}
var person1 = new Person();
var person2 = new Person();

person1.friends.push("Vans");

alert( person1.friends ); //"Jerry","Sara","Vans"
alert( person2.friends ); //"Jerry","Sara","Vans"
alert( person1.friends === person2.friends ); //true

以上已經(jīng)很明了了,給一個(gè)實(shí)例添加屬性值,結(jié)果,所有實(shí)例的屬性值也改變了。實(shí)例應(yīng)該具有自己的獨(dú)立性。自己莫名其妙的被改變,肯定是有問題的。
有問題,就會(huì)有解決方案,再多坑,也抵不過我們前輩的不懈努力。
于是就有了 構(gòu)造函數(shù)和原型模式混合模式

組合使用構(gòu)造函數(shù)模式和原型模式

創(chuàng)建自定義類型最常見的方式,就是組合模式。
構(gòu)造函數(shù)模式用于定義實(shí)例屬性,而原型模式用于定義方法和共享的屬性。
結(jié)果,每個(gè)實(shí)例都有自己的一份實(shí)例屬性的副本。注意是是 副本。同時(shí),又共享著對(duì)方法的引用,最大限度地節(jié)省了內(nèi)存。
另外,這種混成模式還支持向構(gòu)造函數(shù)傳遞參數(shù)。

function Person (name,age) {
    this.name = name;
    this.age = age;
    this.friends = ["Tom","Jerry"]
}
Person.prototype = {
    consructor : Person,
    sayName : function () {
        alert(this.name)
    }
}

var person1 = new Person("Abert",18);
var person2 = new Person("Marry",17);

person1.friends.push("Vans");
alert( person1.friends ); //"Tom","Jerry","Vans"
alert( person2.friends ); //"Tom","Jerry"
alert( person1.friends === person2.friends ); //false

在例子中,實(shí)例屬性是由構(gòu)造函數(shù)定義的,且每個(gè)實(shí)例的屬性 是獨(dú)立的。改變 實(shí)例的屬性,并不影響其它實(shí)例的屬性,這樣就避免了 原型模式的"狂熱的共享熱情"。
所有實(shí)例的共享屬性和方法 則是在原型中定義的。修改任何實(shí)例(person1或person2)中哪一個(gè),其它實(shí)例 都會(huì)受到影響。因?yàn)樗詫?shí)例的[[prototype]]指針 指向原型對(duì)象(Person.prototye),它們擁有共同的引用。構(gòu)造函數(shù)中的實(shí)例屬性 則只是 副本。

以上就是 工廠模式、構(gòu)造函數(shù)、原型模式以及組合模式 各自的特定和區(qū)別。
核心都在《JS高程3》。我做了些簡(jiǎn)單的梳理,和自己的理解。有不明白或者質(zhì)疑的地方,以書為準(zhǔn)。

總結(jié)

什么是工廠模式 ?

工廠模式就是抽象了具體對(duì)象細(xì)節(jié)過程的方法。這個(gè)方法以函數(shù)的形式封裝實(shí)現(xiàn)的細(xì)節(jié)。實(shí)現(xiàn)了重復(fù)調(diào)用的功能。

什么是構(gòu)造函數(shù)模式

構(gòu)造函數(shù)模式就是創(chuàng)建一個(gè)對(duì)象,new 這個(gè)對(duì)象就是對(duì)象的實(shí)例。實(shí)現(xiàn)重復(fù)調(diào)用的同時(shí),它的實(shí)例 也有了QQVIP的尊貴特權(quán) ,即
實(shí)例可以標(biāo)識(shí)為特定的類型。有了這個(gè)標(biāo)識(shí) 可以更好的識(shí)別出,誰是數(shù)組類型,誰是函數(shù)類型,然后你 typeof arr 或 typeof fun
一看,真的是Array類型,functiong類型。你也可以自定義自己想要的類型,這樣大大的增加了JS的拓展性。

什么是原型模式 ?

首先我們要知道,我們創(chuàng)建的每一個(gè)函數(shù)都有一個(gè)隱藏屬性,也就是 原型屬性。這個(gè)原型屬性指向一個(gè)原型對(duì)象。且所有實(shí)例和構(gòu)造函數(shù) 都指向這個(gè)原型對(duì)象,共享 原型對(duì)象的所有方法和屬性。
我們通過操作原型對(duì)象達(dá)到 實(shí)例共享屬性方法的目的,就是原型模式。
同時(shí),因?yàn)閷?shí)例都是引用 原型對(duì)象的屬性和方法,也避免了構(gòu)造函數(shù)模式下所有實(shí)例都有各自的方法的弊端。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容