JS-建立物件

莫顯輝發表於2019-02-16

建立物件

使用new Object()或者物件字面量都可以建立物件,但是這樣建立的物件過於簡單,不易於物件的屬性與方法的擴充套件與繼承。
下面講的物件可以與JavaEE中的bean做類比。

工廠模式

對,首先可能想到的是使用設計模式中的工廠模式

function createPizza(type) {
  var o = new Object();
  o.type = type;
  o.bake = function() {
    alert(`Start~`);
    alert(this.type);
    alert(`End~`);
  };
  return o;
}

var cheesePizza = createPizza(`cheese`);
var veggiePizza = createPizza(`veggie`);
cheesePizza.bake();

優點

工廠模式解決了建立多個類似物件的問題

缺點

物件無法識別,即建立出來的物件無法通過instanceof等分析出屬於哪種型別

建構函式模式

用建構函式可用來建立特定型別的物件

// 建構函式首字母遵循OO語言慣例進行大寫
function Pizza(type) {
  this.type = type;
  this.bake = function() {
    alert(`Start~`);
    alert(this.type);
    alert(`End~`);
  };
}

var cheesePizza = new Pizza(`cheese`);
var veggiePizza = new Pizza(`veggie`);
cheesePizza.bake();

與工廠模式相比:

  1. 沒有在方法中顯示創造物件(o);
  2. 直接將屬性與方法賦值給this;
  3. 沒有return語句

在用new的時候,會經歷一下4步:

  1. 建立一個新物件
  2. 將建構函式的作用域賦值給新物件(此時this指向新物件)
  3. 執行建構函式程式碼(為物件新增屬性)
  4. 返回新物件

如果不使用new,將建構函式當做函式使用,則this指向Global物件(在瀏覽器中為window物件),當然,可以使用call方法來指定作用域,例如

var o = new Object();
Pizza.call(o, `salty`);
o.bake();

使用建構函式方法,每個例項物件都有一個constructor建構函式屬性,該屬性指向Pizza(使用物件字面量、工廠模式方法建立的物件該屬性指向Object)

cheesePizza.constructor == Pizza

檢查某個物件屬於哪種型別,一般使用instanceof,cheesePizza同時屬於PizzaObject(之所以屬於Object,是因為所有物件均繼承於Object)

cheesePizza instanceof Pizza;
cheesePizza instanceof Object;

優點

與工廠模式相比,建構函式模式能夠識別出物件型別
與下面的原型模式相比,能夠實現物件屬性的互相獨立,在引用型別屬性上很有用

缺點

每個例項物件的方法都是獨立的,導致方法不能夠共享

原型模式

每個函式(不是例項物件)都有一個prototype屬性,該屬性是一個指標,指向一個物件,物件的用途是包含所有例項共享的屬性和方法。prototype通過呼叫建構函式建立的那個物件例項的原型物件。使用原型物件的好處是可以讓所有例項物件共享屬性與方法。

function Pizza() {

}

Pizza.prototype.type = `original`
Pizza.prototype.bake = function() {
  alert(`Start~`);
  alert(this.type);
  alert(`End~`);
};

var cheesePizza = new Pizza();
cheesePizza.type = `cheese`;
var veggiePizza = new Pizza();
veggiePizza.type = `veggie`;

cheesePizza.bake();
veggiePizza.bake();

各個物件共享屬性與方法,同時每個物件都可以建立自己的屬性,並遮蔽掉原型物件的同名屬性,因為共享屬性與方法,所以以下等式成立

cheesePizza.bake == veggiePizza.bake

物件字面量重寫原型物件

也可以通過物件字面量來重寫整個原型物件:

Pizza.prototype = {
  type: `original`,
  bake: function() {
    alert(`Start~`);
    alert(this.type);
    alert(`End~`);
  }
}

這樣完全重寫,原型物件上的constructor屬性不再指向Pizza函式(全新的constructor指向Object),不過不影響通過instanceof來識別物件型別。如果constructor特別重要的話,可以顯式將它置為適當的值:

Pizza.prototype = {
  constructor: Pizza,
  type: `original`,
  bake: function() {
    alert(`Start~`);
    alert(this.type);
    alert(`End~`);
  }
}

不過這種方式會將constructor的屬性特徵變為可列舉,而預設情況下它是不可列舉的,如果想不可列舉,可以使用Object.defineProperty()方法。

原型的動態性

對原型物件的修改會體現在例項物件上,即使例項物件先被建立。但是通過物件字面量重寫的原型物件則沒有該動態性

優點

定義在原型物件上的屬性,能夠保證在各例項物件上的共享

缺點

對於引用型別的屬性,各例項的共享會導致額外的問題。

組合使用建構函式模式與原型模式

整合建構函式模式與原型模式,建構函式模式用於定義例項屬性,原型模式用於定義方法和共享屬性。

動態原型模式

寄生建構函式模式

穩妥建構函式模式

各建立模式在Chrome瀏覽器中的表現

可以通過Chrome瀏覽器觀察使用工廠模式建立的cheesePizza物件屬性為:

cheesePizza
{type: "cheese", bake: ƒ}
  bake: ƒ ()
  type: "cheese"
  __proto__:
    constructor: ƒ Object()
    hasOwnProperty: ƒ hasOwnProperty()
    isPrototypeOf: ƒ isPrototypeOf()
    propertyIsEnumerable: ƒ propertyIsEnumerable()
    toLocaleString: ƒ toLocaleString()
    toString: ƒ toString()
    valueOf: ƒ valueOf()
    __defineGetter__: ƒ __defineGetter__()
    __defineSetter__: ƒ __defineSetter__()
    __lookupGetter__: ƒ __lookupGetter__()
    __lookupSetter__: ƒ __lookupSetter__()
    get __proto__: ƒ __proto__()
    set __proto__: ƒ __proto__()

使用建構函式模式建立cheesePizza物件屬性為:

cheesePizza
Pizza {type: "cheese", bake: ƒ}
  bake: ƒ ()
  type: "cheese"
  __proto__:
    constructor: ƒ Pizza(type)
    __proto__:
      constructor: ƒ Object()
      hasOwnProperty: ƒ hasOwnProperty()
      isPrototypeOf: ƒ isPrototypeOf()
      propertyIsEnumerable: ƒ propertyIsEnumerable()
      toLocaleString: ƒ toLocaleString()
      toString: ƒ toString()
      valueOf: ƒ valueOf()
      __defineGetter__: ƒ __defineGetter__()
      __defineSetter__: ƒ __defineSetter__()
      __lookupGetter__: ƒ __lookupGetter__()
      __lookupSetter__: ƒ __lookupSetter__()
      get __proto__: ƒ __proto__()
      set __proto__: ƒ __proto__()

使用原型模式建立cheesePizza物件屬性為:

cheesePizza
Pizza {type: "cheese"}
  type: "cheese"
  __proto__:
    bake: ƒ ()
    type: "original"
    constructor: ƒ Pizza()
    __proto__:
      constructor: ƒ Object()
      hasOwnProperty: ƒ hasOwnProperty()
      isPrototypeOf: ƒ isPrototypeOf()
      propertyIsEnumerable: ƒ propertyIsEnumerable()
      toLocaleString: ƒ toLocaleString()
      toString: ƒ toString()
      valueOf: ƒ valueOf()
      __defineGetter__: ƒ __defineGetter__()
      __defineSetter__: ƒ __defineSetter__()
      __lookupGetter__: ƒ __lookupGetter__()
      __lookupSetter__: ƒ __lookupSetter__()
      get __proto__: ƒ __proto__()
      set __proto__: ƒ __proto__()

參考文章

ESLint 需要約束 for-in (guard-for-in)

個人不定期更新主頁

相關文章