JS函數的工廠模式、構造函數模式、原型模式的區別

建立對象

JS有六種數據數據類型,其中五種屬於基本數據類型:Null、Boolean、undefined、String、Number。
而其它值都是對象。數組是對象,函數是對象,正則表達式是對象。對象也是對象。正則表達式

來看一下對象的定義:數組

無序屬性的集合,其屬性能夠包含基本值、對象、或者函數。

咱們經過對象字面量的方式 建立對象。建立的對象用於咱們想要作的事。可是,若是隻有對象,那麼能夠想象咱們寫的代碼將全是光禿禿的對象,會產生大量的重複代碼,和命名衝突等等問題。這是很是很是糟糕的。
爲了解決這個問題,人們開始使用 工廠模式的一種變體。函數

工廠模式

工廠模式抽象了具體對象的過程。也就是說,發明了一種函數,把對象放到函數裏,用函數封裝建立對象的細節。this

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 用於檢測數據類型
var aa = []
console.log(aa instanceof Array)  //true

工廠模式解決了代碼複用的問題,可是卻沒有解決對象識別的問題。即建立的全部實例都是Object類型。
爲了解決這一問題,就有了構造函數模式prototype

構造函數模式

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. 構造函數 Person 有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象即:Person.prototype(原型對象);
  2. 實例person1 person2也有一個[[prototype]]屬性或者叫_proto_,這個屬性 也指向Person.prototype;
  3. 構造函數、和實例 都共享Person.prototype裏的 屬性和方法;
  4. Person.prototype裏有一個 constructor屬性,這個屬性也是一個指針,指向構造函數Person。這樣以來,實例 也指向了Person,那麼實例 也共享了構造函數的屬性和方法。
  5. 構造函數、實例、原型對象裏全部的屬性和方法 都是共享的。

構造函數解決了對象識別問題,咱們在這個例子中建立的對全部對象既是Object的實例,同時,也是Person的實例。這一點經過instanceof操做符能夠獲得驗證。指針

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

建立自定義的構造函數意味着,未來能夠將它的實例 標識爲一種特定類型;這正是構造函數賽過工廠模式的地方。Array就是這種方式(我認爲的)。用構造函數的方式,實例化一個新對象,這個對象能夠是其它類型例如Array類型。code

function Array () {

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

構造函數與普通函數的區別

構造函數和普通函數的惟一區別,在於調用它們的方式不一樣。

看成構造函數使用

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

須要注意的是,this 指向 構造函數Person對象

看成普通函數使用

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

this指向widow.內存

構造函數的問題

構造函數雖然好用,但也有缺點。既每一個new出來的實例 裏的方法都要從新建立一遍。在前面的例子中person1 person2 都有一個sayName方法,但這兩個方法不是同一個Function實例!每一個實例的方法 都是不一樣的,不相等的。這是不合理的!原型

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

因而可知,完成一樣任務的函數確實不必 每一個實例,就實例一次。
因而,有需求,就有解決方案。原型模式。

原型模式

咱們建立的每一個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。
使用原型對象的好處是可讓全部對象實例共享它所包含的屬性和方法。也就是說,沒必要在構造函數中定義對象實例的信息,而是將這些信息添加到原型對象中。

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

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

再來看下這個:

  1. 構造函數 Person 有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象即:Person.prototype(原型對象);
  2. 實例person1 person2也有一個[[prototype]]屬性或者叫_proto_,這個屬性 也指向Person.prototype;
  3. 構造函數、和實例 都共享Person.prototype裏的 屬性和方法;
  4. Person.prototype裏有一個 constructor屬性,這個屬性也是一個指針,指向構造函數Person。這樣以來,實例 也指向了Person,那麼實例 也共享了構造函數的屬性和方法。
  5. 構造函數、實例、原型對象裏全部的屬性和方法 都是共享的。

與構造函數相比,
原型模式,把公共方法提出來放到prototype對象裏。
每一個實例 的[[prototype]]指針 指向這個對象,因此全部實例的公共方法 是同一個。這樣也避免了內存浪費。

原型模式的其它特性

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

原型模式的問題

咱們寫代碼的時候,不多隻用到原型模式,說明它仍是有坑的。
原型模式很大的優勢 是全部實例都共享屬性和方法。這個很好的優勢 對於函數來講很是合適,可對於屬性來講,有點不適應這種開放的"熱情"。
看代碼:

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

以上已經很明瞭了,給一個實例添加屬性值,結果,全部實例的屬性值也改變了。實例應該具備本身的獨立性。本身莫名其妙的被改變,確定是有問題的。
有問題,就會有解決方案,再多坑,也抵不過咱們前輩的不懈努力。
因而就有了 構造函數和原型模式混合模式

組合使用構造函數模式和原型模式

建立自定義類型最多見的方式,就是組合模式。
構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。
結果,每一個實例都有本身的一份實例屬性的副本。注意是是 副本。同時,又共享着對方法的引用,最大限度地節省了內存。
另外,這種混成模式還支持向構造函數傳遞參數。
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

在例子中,實例屬性是由構造函數定義的,且每一個實例的屬性 是獨立的。改變 實例的屬性,並不影響其它實例的屬性,這樣就避免了 原型模式的"狂熱的共享熱情"。
全部實例的共享屬性和方法 則是在原型中定義的。修改任何實例(person1或person2)中哪個,其它實例 都會受到影響。由於因此實例的[[prototype]]指針 指向原型對象(Person.prototye),它們擁有共同的引用。構造函數中的實例屬性 則只是 副本。

以上就是 工廠模式、構造函數、原型模式以及組合模式 各自的特定和區別。
核心都在《JS高程3》。我作了些簡單的梳理,和本身的理解。有不明白或者質疑的地方,以書爲準。

總結

什麼是工廠模式 ?

工廠模式就是抽象了具體對象細節過程的方法。這個方法以函數的形式封裝實現的細節。實現了重複調用的功能。

什麼是構造函數模式 ?

構造函數模式就是建立一個對象,new 這個對象就是對象的實例。實現重複調用的同時,它的實例 也有了QQVIP的尊貴特權 ,即
實例能夠標識爲特定的類型。有了這個標識 能夠更好的識別出,誰是數組類型,誰是函數類型,而後你 typeof arr 或 typeof fun
一看,真的是Array類型,functiong類型。你也能夠自定義本身想要的類型,這樣大大的增長了JS的拓展性。

什麼是原型模式 ?

首先咱們要知道,咱們建立的每個函數都有一個隱藏屬性,也就是 原型屬性。這個原型屬性指向一個原型對象。且全部實例和構造函數 都指向這個原型對象,共享 原型對象的全部方法和屬性。 咱們經過操做原型對象達到 實例共享屬性方法的目的,就是原型模式。 同時,由於實例都是引用 原型對象的屬性和方法,也避免了構造函數模式下全部實例都有各自的方法的弊端。
相關文章
相關標籤/搜索