JavaScript物件與建立物件的方式

八叉樹發表於2019-04-28

引言

建立自定義物件最簡單的方式就是建立一個Object的例項,然後再為它新增屬性和方法,早期的開發人員經常使用這種模式來建立物件,後來物件字面量的方法成了建立物件的首選模式。雖然object建構函式或者物件字面量的方法都可以用來建立物件,但是這些方法使用同一個介面建立很多物件,會產生大量的重複程式碼。為了解決這個問題,人們開始使用各種模式來建立物件,在這些模式中,一般推薦使用四種方式,包括建構函式模式原型模式建構函式和原型組合模式動態原型模式,其他的方式,包括工廠模寄生建構函式模穩妥建構函式模式平時使用的較少。而這些方式中,用的最多最推薦的應是組合模式和動態原型模式。下面先介紹下各個模式的優缺點:

  • 工廠模式的優點就是確實是解決了建立多個相似物件的問題,但是卻沒有解決物件識別的問題,即不知道建立例項的是哪一個物件(建構函式),因為所有的例項都是object創造的;

  • 建構函式模式最大的優點就是可以通過contructor或者instanceof可以識別物件例項的類別(即它的建構函式),但是每次建立例項時,每個方法都要被建立一次;

  • 原型模式的優點是例項的方法是共享的,所有的例項的同一個方法都指向同一個,這樣使得每次建立例項時方法不會重新建立,但是這也是缺點,原型模式中所有的屬性和方法都共享,這樣造成兩個問題一是沒有辦法建立例項自己的屬性和方法,二是例項改變屬性裡的資料時其他例項也會跟著改變,另外它也不能像建立建構函式那樣可以傳遞引數;

  • 建構函式和原型組合模式優點就是該共享的共享,該私有的私有,即用建構函式定義物件的所有非函式屬性,用原型方式定義方法和共享的屬性,結果,每個例項都會有自己的一份例項屬性的副本,但同時又共享著對方法的引用,最大限度地節省了記憶體。另外,這種混成模式還支援向建構函式傳遞引數;這種建構函式和原型混成的模式,是目前在ECMAScript中使用最廣泛、認同度最高的一種建立定義型別的方法。

    注意組合模式解決了原型模式沒有辦法傳遞引數的缺點,也解決了建構函式模式不能共享方法的缺點。

  • 動態原型模式是目前極為推薦的一種形式,但是在使用時要注意不能用物件字面量重寫原型

  • 寄生建構函式模式和工廠模式基本一樣,除了多了個new操作符,因此它也不能識別物件,或者說不能識別例項的類別,該方法不推薦使用

  • 穩妥建構函式模式適合在一些安全的環境中,穩妥物件是指沒有公共屬性,而且其方法也不引用this的物件,它也無法識別物件的所屬型別,該方法不推薦使用

工廠模式

function Person(name) {
  var o = new Object();
  o.name = name;
  o.say = function() {
    alert(this.name);
  }
  return o;
}
var person1 = Person("yawei");
複製程式碼

缺點:

  1. 物件無法識別,所有例項都指向一個原型;無法通過constructor識別物件,因為都是來自Object。
  2. 每次通過Person建立物件的時候,所有的say方法都是一樣的,但是卻儲存了多次,浪費資源。

建構函式模式

function Person() {
  this.name = 'hanmeimei';
  this.say = function() {
    alert(this.name)
  }
}

var person1 = new Person();
複製程式碼

優點:

  1. 通過constructor或者instanceof可以識別物件例項的類別
  2. 可以通過new 關鍵字來建立物件例項,更像OO語言中建立物件例項

缺點:

  1. 多個例項的say方法都是實現一樣的效果,但是卻儲存了很多次(兩個物件例項的say方法是不同的,因為存放的地址不同)

注意:

  1. 建構函式模式隱試的在最後返回return this 所以在缺少new的情況下,會將屬性和方法新增給全域性物件,瀏覽器端就會新增給window物件。
  2. 也可以根據return this 的特性呼叫call或者apply指定this。這一點在後面的繼承有很大幫助。

原型模式

function Person() {}
Person.prototype.name = 'hanmeimei';
Person.prototype.say = function() {
  alert(this.name);
}
Person.prototype.friends = ['lilei'];

var person1 = new Person();
複製程式碼

優點:

  1. say方法是共享的了,所有的例項的say方法都指向同一個。

  2. 可以動態的新增原型物件的方法和屬性,並直接反映在物件例項上。

    var person1 = new Person()
    Person.prototype.showFriends = function() {
      console.log(this.friends)
    }
    person1.showFriends()  //['lilei']
    複製程式碼

缺點:

  1. 出現引用的情況下會出現問題具體見下面程式碼:

    var person1 = new Person();
    var person2 = new Person();
    person1.friends.push('xiaoming');
    console.log(person2.friends)  //['lilei', 'xiaoming']
    複製程式碼

    因為js對引用型別的賦值都是將地址儲存在變數中,所以person1和person2的friends屬性指向的是同一塊儲存區域。

  2. 第一次呼叫say方法或者name屬性的時候會搜尋兩次,第一次是在例項上尋找say方法,沒有找到就去原型物件(Person.prototype)上找say方法,找到後就會在實力上新增這些方法or屬性。

  3. 所有的方法都是共享的,沒有辦法建立例項自己的屬性和方法,也沒有辦法像建構函式那樣傳遞引數。

注意:

  1. 優點②中存在一個問題就是直接通過物件字面量給Person.prototype進行賦值的時候會導致constructor改變,所以需要手動設定,其次就是通過物件字面量給Person.prototype進行賦值,會無法作用在之前建立的物件例項上

    var person1 = new Person()
    Person.prototype = {
    	name: 'hanmeimei2',
      	setName: function(name){
          this.name = name
      	}
    }
    
    person1.setName()   //Uncaught TypeError: person1.set is not a function(…)
    複製程式碼

    這是因為物件例項和物件原型直接是通過一個指標連結的,這個指標是一個內部屬性[[Prototype]],可以通過__proto__訪問。我們通過物件字面量修改了Person.prototype指向的地址,然而物件例項的__proto__,並沒有跟著一起更新,所以這就導致,例項還訪問著原來的Person.prototype,所以建議不要通過這種方式去改變Person.prototype屬性

建構函式和原型組合模式

function Person(name) {
  this.name = name
  this.friends = ['lilei']
}
Person.prototype.say = function() {
  console.log(this.name)
}

var person1 = new Person('hanmeimei')
person1.say() //hanmeimei
複製程式碼

優點:

  1. 解決了原型模式對於引用物件的缺點
  2. 解決了原型模式沒有辦法傳遞引數的缺點
  3. 解決了建構函式模式不能共享方法的缺點

缺點:

  1. 和原型模式中注意①一樣

動態原型模式

function Person(name) {
  this.name = name
    // 檢測say 是不是一個函式
    // 實際上只在當前第一次時候沒有建立的時候在原型上新增sayName方法
    //因為建構函式執行時,裡面的程式碼都會執行一遍,而原型有一個就行,不用每次都重複,所以僅在第一執行時生成一個原型,後面執行就不必在生成,所以就不會執行if包裹的函式,
//其次為什麼不能再使用字面量的寫法,我們都知道,使用建構函式其實是把new出來的物件作用域繫結在建構函式上,而字面量的寫法,會重新生成一個新物件,就切斷了兩者的聯絡!
  if(typeof this.say != 'function') {
    Person.prototype.say = function(
    alert(this.name)
  }
}
複製程式碼

優點:

  1. 可以在初次呼叫建構函式的時候就完成原型物件的修改
  2. 修改能體現在所有的例項中

**缺點:**紅寶書都說這個方案完美了。。。。

注意:

  1. 只用檢查一個在執行後應該存在的方法或者屬性就行了 假設除了:say方法外,你還定義了很多其他方法,比如:sayBye、cry、smile等等。此時你只需要把它們都放到對sayName判斷的if塊裡面就可以了。

    if (typeof this.sayName != "function") {
        Person.prototype.sayName = function() {...};
        Person.prototype.sayBye = function() {...};
        Person.prototype.cry = function() {...};
        ...
    }
    複製程式碼
  2. 不能用物件字面量修改原型物件

寄生建構函式模式

function Person(name) {
  var o = new Object()
  o.name = name
  o.say = function() {
    alert(this.name)
  }
  return o
}

var peron1 = new Person('hanmeimei')
複製程式碼

優點:

  1. 和工廠模式基本一樣,除了多了個new操作符

缺點:

  1. 和工廠模式一樣,不能區分例項的類別

穩妥構造模式

function Person(name) {
  var o = new Object()
  o.say = function() {
    alert(name)
  }
}

var person1 = new Person('hanmeimei');
person1.name  // undefined
person1.say() //hanmeimei
複製程式碼

優點:

  1. 安全,那麼好像成為了私有變數,只能通過say方法去訪問。所謂穩妥物件,指的是沒有公共睡醒,而且其方法也不引用this的物件。

缺點:

  1. 跟工廠模式一樣,不能區分例項的類別

參考文章:

JavaScript中的物件,如何建立物件,建立物件的7種模式

一個人就需要物件之js中八種建立物件方式

讀書筆記之建立物件

相關文章