《JS高級程序設計》讀書筆記----JS建立對象的七種模式

【上一篇】:JavaScript對象內部屬性及其特性總結segmentfault

JS建立對象的七種模式

工廠模式(★★)

先在內部 顯示地建立一個臨時對象,根據接收的參數來構建(賦值屬性和方法)該對象,並返回該對象。
缺點:沒有解決對象識別的問題(即沒法確認一個對象的類型)。
function PersonFactory (name, age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function(){
        console.log(this.name)
    }
    return o;
}

var p1 = PersonFactory();
var p2 = PersonFactory();
p1 instanceof Object; // true
p2 instanceof Object; // true
p1 instanceof PersonFactory; // false  沒法識別具體對象類型
p2 instanceof PersonFactory; // false

構造函數模式(★★★)

  1. 函數名首字母通常大寫(如:Person,借鑑OO語言命名習慣)區別於其餘函數;
  2. 構造函數自己也是函數,只不過能夠用來參加對象而已;
  3. 每一個新建的實例對象都有一個constructor屬性,該屬性指向構造函數自身Person
  4. 構造函數也能夠被當作普通函數來調用;瀏覽器

    • 在全局做用域中調用一個函數時,this對象老是指向Global對象(瀏覽器中即window對象)
  5. 【優勢】自定義構造函數能夠將它的實例標識爲一種特定的類型;
  6. 【缺點】構造函數的主要問題是,每一個方法都要在每一個實例上從新建立一遍;

調用構造函數實際經歷4個步驟

  1. 建立(隱式地)一個新對象;
  2. 將構造函數的新對象賦值給新對象(此時this指向這個新對象);
  3. 執行構造函數中的代碼(爲這個新對象添加屬性);
  4. 返回新對象(該構造函數的實例);

與工廠模式比較

  1. 沒有顯示地建立對象;
  2. 直接將屬性和方法賦值給this對象;
  3. 沒有return語句;
  4. 優勢:能夠確認具體對象類型;
function Person (name, age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    }
}
// 當作普通函數
var p1 = new Person('Tom', 20);
var p2 = new Person('Greg', 30);
p1 instanceof Object;         // true
p2 instanceof Object;         // true
p1 instanceof PersonFactory;  // true 能夠確認具體對象類型
p2 instanceof PersonFactory;  // true

// 做爲普通函數調用
Person('Green', 30);  // 屬性和方法都被添加到window對象了
window.sayName();     // 'Green'

// 在另外一個對象的做用域中調用
var obj = new Object();
Person.call(obj , 'Jim', 23);
obj .sayName(); // 'Jim'

原型模式(★★★)

  1. 構造函數變爲空函數;
  2. 將全部屬性和方法都添加到了構造函數的prototype屬性中;

與構造函數模式比較

  1. 不一樣:新對象的屬性和方法由全部實例共享(p1和p2訪問的都是同一組屬性和同一個函數);
function Person(){}

Person.prototype.name = 'Tom';
Person.prototype.age = 24;
Person.prototype.sayName = function(){
    console.log(this.name);
}

var p1 = new Person(), p2 = new Person();
p1.sayName();// 'Tom'
p2.sayName();// 'Tom'
console.log(p1.sayName === p2.sayName);// true

利用更簡單的原型語法

  1. 每建立一個函數,就會同時建立它的prototype對象,該prototype對象也會自動得到constructor屬性。
  2. 用對象字面量形式建立的對象,直接賦值給函數的原型對象(Person.prototype),本質上徹底重寫了其prototype對象,
  3. 所以constructor屬性也就變成了新對象的constructor屬性(指向Object構造函數),再也不指向Person函數。
  4. 此時instanceof操做符能夠返回正確結果,但經過constructor已經沒法肯定對象類型了。
function Person(){}
Person.prototype = {
    // construtor : Person, // 須要從新指設constructor屬性
    name : 'Tom',
    age : 24,
    sayName : function(){
        console.log(this.name);
    }
}

var p = new Person(); 
console.log(p instanceof Object); // ture
console.log(p instanceof Person); // ture
console.log(p.construtor == Object); // ture
console.log(p.construtor == Person); // false

安全地重設constructor

以上述方式重設 constructor屬性會致使它的 [[Enumerable]]特性被設置爲 true;
默認狀況下,原生的 constructor屬性是不可枚舉的;
所以可利用 Object.defineProperty()重設;
Object.defineProperty(Person.prototype, 'constructor', {
    enumerable : false,
    value : Person
});

組合模式(構造函數模式和原型模式)(★★★★)

  • 定義安全

    1. 組合使用構造函數模式和原型模式;
    2. 構造函數模式用於定義實例屬性,原型模式用於定義方法和共享的屬性;
  • 優勢:函數

    1. 每一個實例都會有本身的一份實例屬性的副本,同時又共享着對方方法的引用,最大限度節省了內存;
    2. 這種混成模式支持向構造函數傳遞參數;
  • 目前使用最普遍、認同度最高的一種自定義類型的方法,能夠說是用來定義引用類型的一種默認模式
// 實例屬性均在構造函數中定義
function Person(name, age){
    this.name = name;
    this.age = age;
    this.friends = ['Nicholas', 'Jim'];
}
// 由全部實例共享屬性constructor和方法則是在原型中定義的
Person.prototype = {
    construtor: Person,
    sayName: function(){
        console.log(this.name);
    }
}

var p1 = new Person('Tom', 25);
var p2 = new Person('Greg', 26);
p1.friends.push('Tony');
console.log(p1.friends); // ["Nicholas", "Jim", "Tony"]
console.log(p2.friends); // ["Nicholas", "Jim"]
console.log(p1.friends === p2.friends); // false
console.log(p1.sayName === p2.sayName); // true

動態原型模式(★★★★★)

  • 定義this

    1. 動態原型模式把全部信息都封裝在了構造函數中;
    2. 經過在構造函數中初始化原型(僅在必要的狀況下,好比第一次新建實例時);
    3. 經過檢查某個應該存在(原型中)的方法是否有效,來決定是否須要初始化原型;
  • 優勢:保持了同時使用構造函數和原型的特色;

注意點:

  1. 下段代碼中只會在初次調用構造函數時纔會執行,此後原型已經完成初始化,無需再作修改;
  2. 這裏對原型所作對修改,可以當即在全部實例中獲得反映;
  3. if語句檢查的能夠是初始化以後應該存在的任何屬性或方法,檢查其中一個便可;
function Person(name, age){
    // 屬性
    this.name = name;
    this.age = age;
    // 方法
    if(typeof this.sayName != 'function'){
        Person.prototype.sayName = function(){
            console.log(this.name);
        }
    }
}

寄生構造函數模式(★★)

  • 【定義】: 基本思想是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象
  • 【特色】spa

    1. 返回的對象與構造函數或者與構造函數的原型屬性之間沒有關係;
    2. 寄生構造函數返回的對象與在寄生構造函數外部建立的對象沒有什麼不一樣;
    3. 不能依賴instanceof操做符來肯定對象類型;

與工廠模式的比較

  1. 除了使用new操做符並把使用的包裝函數叫作構造函數以外,該模式和工廠模式實際上是如出一轍的;

與構造函數模式的比較

  1. 從表面上看,很像典型的構造函數;
  2. 構造函數在不返回值的狀況下,默認會返回新對象實例
  3. 寄生構造函數經過在構造函數的末尾加一個return語句,重寫了調用構造函數時返回的值prototype

    • 此時返回的對象的__proto__屬性指向Object;
function Person(name, age){
    // 建立臨時新對象
    var o = new Object();
    // 初始化該對象
    o.name = name;
    o.age = age;
    o.sayName = function(){
        console.log(this.name);
    }
    // 返回該臨時新對象
    return o;
}

var p1 = new Person('Tom', 25);
var p2 = new Person('Greg',30);
console.log(p1 instanceof Object); // true
console.log(p1 instanceof Person); // false
console.log(p1.sayName == p2.sayName); // false
console.log(p1.constructor == Object); //true

穩妥構造函數模式(★★)

所謂 穩妥對象,就是指沒有公共屬性,並且其方法也不引用 this的對象。
穩妥對象最適合在一些安全的環境中(這些環境會禁用 thisnew),或者防止數據被其餘應用程序改動時調用。

與寄生構造函數模式的比較

與寄生構造函數相似,但又不一樣:code

  1. 一是新建立的對象實例方法不引用this;
  2. 二是不使用new操做符調用構造函數;
function Person (name, age){
    var o = new Object();
    // 可定義私有變量和函數
    // ...
    
    // 新建立的對象實例方法未引用this;
    o.sayName = function(){
        console.log(name)
    }
    return o;
}

var p = Person('Tom', 23);
p.sayName(); // 'Tom'
console.log(p instanceof Person); // false
  • 除了sayName()方法外,沒有別的方法能夠訪問其數據成員;
  • 即便有其餘代碼給這個對象添加方法或數據成員,也不可能有別的辦法訪問傳入到構造函數中的原始數據
  • 與寄生構造函數模式相似,使用穩妥構造函數模式建立的對象與構造函數之間也沒有關係(故instanceof操做符對這種對象也無心義);
相關文章
相關標籤/搜索