JavaScript 建立物件的七種方式

Xuthus Blog發表於2017-06-13

JavaScript建立物件的方式有很多,通過Object建構函式或物件字面量的方式也可以建立單個物件,顯然這兩種方式會產生大量的重複程式碼,並不適合量產。接下來介紹七種非常經典的建立物件的方式,他們也各有優缺點。

工廠模式

function createPerson(name, job) {
  var o = new Object()
  o.name = name
  o.job = job
  o.sayName = function() {
    console.log(this.name)
  }
  return o
}
var person1 = createPerson('Jiang', 'student')
var person2 = createPerson('X', 'Doctor')

可以無數次呼叫這個工廠函式,每次都會返回一個包含兩個屬性和一個方法的物件

工廠模式雖然解決了建立多個相似物件的問題,但是沒有解決物件識別問題,即不能知道一個物件的型別

建構函式模式

function Person(name, job) {
  this.name = name
  this.job = job
  this.sayName = function() {
    console.log(this.name)
  }
}
var person1 = new Person('Jiang', 'student')
var person2 = new Person('X', 'Doctor')

沒有顯示的建立物件,使用new來呼叫這個建構函式,使用new後會自動執行如下操作

  • 建立一個新物件
  • 這個新物件會被執行[[prototype]]連結
  • 這個新物件會繫結到函式呼叫的this
  • 返回這個物件

使用這個方式建立物件可以檢測物件型別

person1 instanceof Object // true
person1 instanceof Person //true

但是使用建構函式建立物件,每個方法都要在每個例項上重新建立一次

原型模式

function Person() {
}
Person.prototype.name = 'Jiang'
Person.prototype.job = 'student'
Person.prototype.sayName = function() {
  console.log(this.name)
}
var person1 = new Person()

將資訊直接新增到原型物件上。使用原型的好處是可以讓所有的例項物件共享它所包含的屬性和方法,不必在建構函式中定義物件例項資訊。

原型是一個非常重要的概念,在一篇文章看懂proto和prototype的關係及區別中講的非常詳細

更簡單的寫法

function Person() {
}
Person.prototype = {
  name: 'jiang',
  job: 'student',
  sayName: function() {
    console.log(this.name)
  }
}
var person1 = new Person()

將Person.prototype設定為等於一個以物件字面量形式建立的物件,但是會導致.constructor不在指向Person了。

使用這種方式,完全重寫了預設的Person.prototype物件,因此 .constructor也不會存在這裡

Person.prototype.constructor === Person  // false

如果需要這個屬性的話,可以手動新增

function Person() {
}
Person.prototype = {
  constructor:Person
  name: 'jiang',
  job: 'student',
  sayName: function() {
    console.log(this.name)
  }
}

不過這種方式還是不夠好,應為constructor屬性預設是不可列舉的,這樣直接設定,它將是可列舉的。所以可以時候,Object.defineProperty方法

Object.defineProperty(Person.prototype, 'constructor', {
  enumerable: false,
  value: Person
})

缺點

使用原型,所有的屬性都將被共享,這是個很大的優點,同樣會帶來一些缺點

原型中所有屬性例項是被很多例項共享的,這種共享對於函式非常合適。對於那些包含基本值的屬性也勉強可以,畢竟例項屬性可以遮蔽原型屬性。但是引用型別值,就會出現問題了

function Person() {
}
Person.prototype = {
  name: 'jiang',
  friends: ['Shelby', 'Court']
}
var person1 = new Person()
var person2 = new Person()
person1.friends.push('Van')
console.log(person1.friends) //["Shelby", "Court", "Van"]
console.log(person2.friends) //["Shelby", "Court", "Van"]
console.log(person1.friends === person2.friends) // true

friends存在與原型中,例項person1和person2指向同一個原型,person1修改了引用的陣列,也會反應到例項person2中

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

這是使用最為廣泛、認同度最高的一種建立自定義型別的方法。它可以解決上面那些模式的缺點

使用此模式可以讓每個例項都會有自己的一份例項屬性副本,但同時又共享著對方法的引用

這樣的話,即使例項屬性修改引用型別的值,也不會影響其他例項的屬性值了

function Person(name) {
  this.name = name
  this.friends = ['Shelby', 'Court']
}
Person.prototype.sayName = function() {
  console.log(this.name)
}
var person1 = new Person()
var person2 = new Person()
person1.friends.push('Van')
console.log(person1.friends)  //["Shelby", "Court", "Van"]
console.log(person2.friends) // ["Shelby", "Court"]
console.log(person1.friends === person2.friends) //false

動態原型模式

動態原型模式將所有資訊都封裝在了建構函式中,初始化的時候,通過檢測某個應該存在的方法時候有效,來決定是否需要初始化原型

function Person(name, job) {
  // 屬性
  this.name = name
  this.job = job

  // 方法
  if(typeof this.sayName !== 'function') {
    Person.prototype.sayName = function() {
       console.log(this.name)
    }
  }

}
var person1 = new Person('Jiang', 'Student')
person1.sayName()

只有在sayName方法不存在的時候,才會將它新增到原型中。這段程式碼只會初次呼叫建構函式的時候才會執行。

此後原型已經完成初始化,不需要在做什麼修改了

這裡對原型所做的修改,能夠立即在所有例項中得到反映

其次,if語句檢查的可以是初始化之後應該存在的任何屬性或方法,所以不必用一大堆的if語句檢查每一個屬性和方法,只要檢查一個就行

寄生建構函式模式

這種模式的基本思想就是建立一個函式,該函式的作用僅僅是封裝建立物件的程式碼,然後再返回新建的物件

function Person(name, job) {
  var o = new Object()
  o.name = name
  o.job = job
  o.sayName = function() {
    console.log(this.name)
  }
  return o
}
var person1 = new Person('Jiang', 'student')
person1.sayName()

這個模式,除了使用new操作符並把使用的包裝函式叫做建構函式之外,和工廠模式幾乎一樣

建構函式如果不返回物件,預設也會返回一個新的物件,通過在建構函式的末尾新增一個return語句,可以重寫呼叫建構函式時返回的值

穩妥建構函式模式

首先明白穩妥物件指的是沒有公共屬性,而且其方法也不引用this。

穩妥物件最適合在一些安全環境中(這些環境會禁止使用this和new),或防止資料被其他應用程式改動時使用

穩妥建構函式模式和寄生模式類似,有兩點不同:一是建立物件的例項方法不引用this,而是不使用new操作符呼叫建構函式

function Person(name, job) {
  var o = new Object()
  o.name = name
  o.job = job
  o.sayName = function() {
    console.log(name)
  }
  return o
}
var person1 = Person('Jiang', 'student')
person1.sayName()

和寄生建構函式模式一樣,這樣建立出來的物件與建構函式之間沒有什麼關係,instanceof操作符對他們沒有意義

相關文章