JavaScript中建立對象的幾種方式

前言

通常狀況下,JS中建立對象的方式能夠用構造函數Object或者對象字面量的方式,但須要建立幾個具備相同屬性或方法的對象時,就得寫大量的冗餘代碼。故而出現了下述幾種建立對象的方法。設計模式

1、工廠模式

工廠模式是一種常見的設計模式。這種模式把對象的建立過程抽象出來並封裝成一個函數。須要使用同類型的對象時,能夠不斷地調用此函數。例子以下:瀏覽器

function createPerson (name, age) {
    var o = new Object();
    o.name = name;
    o.sayName = function(){
        console.log(this.name)
    }
    return o;
}
複製代碼

工廠模式解決了建立多個相同類型對象的問題,即減小了代碼的冗餘,但沒有解決對象識別的問題。bash

2、構造函數模式

JavaScript中的構造函數其實就是普通的函數,但構造函數的目的通常用來建立對象。按照約定俗成的慣例,構造函數應該以一個大寫字母開頭。例子以下:函數

function Person (name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    }
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小紅', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小紅
複製代碼

此例子與工廠模式的區別有:學習

  • 沒有顯式地建立一個對象;
  • 直接把方法和屬性值賦給this;
  • 沒有return語句;
  • 建立對象時須要使用new關鍵字。

此外,構造函數模式還解決了對象識別的問題:ui

xiaoming instanceof Person // true
xiaohong instanceof Person // true
複製代碼

特別地,由於構造函數也是函數,在不用new操做符調用時,函數內部的this對象實際上是指向了全局對象(瀏覽器環境是window對象),具體緣由請學習this的原理。this

構造函數並非沒有缺點,如上面例子的sayName方法,在每一個實例上都從新建立了一遍:spa

xiaoming.sayName === xiaohong.sayBane // false
複製代碼

像sayName這種函數都是爲了完成相同的任務(打印實例的名字),大可抽象出來做爲一個共享的函數prototype

function Person (name, age) {
    this.name = name;
    this.age = age;
    this.sayName = sayName;
}
function sayName(){
    console.log(this.name);
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小紅', 16);
xiaoming.sayName();  //小明
xiaohong.sayName();  //小紅
複製代碼

但這種方式使得sayName函數變爲了全局函數,而且只合適被Person new出來的實例調用。故而帶來下面幾個問題:設計

  • 此全局函數會讓其餘的開發人員感到疑惑;
  • 使得全局做用域變得名副其實(全局做用域的屬性和函數應該適合在任何地方調用);
  • 若構造函數的實例須要不少方法時,會暴露太多函數到全局做用域,毫無封裝性可言。

3、原型模式

利用JS的原型對象和原型鏈,能夠解決構造函數模式中帶來的那幾個問題:

function Person(){};
Person.prototype = {
    constructor: Person,
    name: '小明',
    age: 18,
    sayName: function () {
        console.log(this.name)
    }
}

var xiaoming = new Person();
var xiaohong = new Person();
xiaoming.sayName(); //小明
xiaohong.sayName(); //小明
複製代碼

上面的例子能夠看到,雖然解決了構造函數模式存在的問題,但缺點也是大大的:

  • 因忽略了構造函數的傳參,每一個實例的屬性都是相同的,缺少靈活性;
  • 對於引用類型的屬性值,一個實例對其修改會反映到全部實例。
Person.prototype.friends = ['張三', '李四'];
xiaoming.friends === xiaohong.friends; //true
xiaoming.frinds.push('王五');
xiaohong.friends; // ['張三', '李四', '王五'];
複製代碼

4、組合使用構造函數模式和原型模式(推薦)

組合使用構造函數模式和原型模式是ES5中建立對象最經常使用的模式,此方式用構造函數定義實例的公共屬性,使用原型模式定義實例的公共方法,最大限度下降了內存:

function Person(name, age){
    this.name = name;
    this.age = age;
    this.friends = ['張三', '李四'];
};
Person.prototype.sayName = function () {
    console.log(this.name);
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小紅', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小紅
複製代碼

另外,對於引用類型的共享的屬性值,實例之間不會互相影響:

xiaoming.friends ==== xiaohong.friends; //false
xiaoming.frinds.push('王五');
xiaohong.friends; // ['張三', '李四'];
複製代碼

5、動態原型模式

動態原型模式就是把全部信息封裝在構造函數裏:

function Person(name, age){
    this.name = name;
    this.age = age;
    if(typeof this.sayName !== 'function'){
        Person.prototype.sayName = function () {
            console.log(this.name);
        }
    }
};
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小紅', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小紅
複製代碼

動態原型模式同事保持了構造函數模式和原型模式的優勢,且封裝性比構造函數和原型組合使用的方式更好。但要注意在構造函數執行後不能重寫原型對象,不然會切斷現有實例和新原型的關係。由於該模式中原型對象上的自定義方法是在構造函數中掛載的。

紅皮書還介紹了寄生構造函數模式和穩妥構造函數模式,但實踐中很是少用。有興趣的同窗能夠查閱。

相關文章
相關標籤/搜索