JavaScript 建立對象

建立對象

瞭解如何建立對象有助於咱們理解如何繼承,和對象的 prototype 屬性
建立對象的方法有許多種,後來的方法解決了前者的缺點,瞭解這些遞進的關係有助於幫助咱們更好地瞭解對象的工做模式git

工廠模式

最開始的模式,即經過一個函數,用戶能夠選擇傳入一些參數,而後函數返回一個新的對象完成對象的建立github

function createPerson (name, age, job) {
  let o = new Object()
  o.name = name
  o.age = age
  o.job = job
  o.sayName = function () {
    console.log('`Hello my name is ${this.name}`')
  }
  return o
}
let person1 = createPerson('Nicholas', 29, 'Software Engineer')
let person2 = createPerson('Greg', 27, 'Doctor')
複製代碼

顯而易見的是能夠屢次調用該函數獲得多個具備類似的對象,可是其存在一些缺點:
數組

  1. 咱們沒法知道上述的 person1 究竟是不是 createPerson 的實例,由於 person1prototypeObject.prototype
  2. 咱們每建立一個對象都要新建一個 sayName 方法屬性,即 person1.sayName === person2.sayName // false

構造函數模式

構造函數模式解決了工廠模式的第一個問題,即 能夠確認一個實例是否爲一個 的對象bash

function Person (name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function () {
    console.log('`Hello my name is ${this.name}`')
  }
}
let person1 = new Person('Nicholas', 29, 'Software Engineer')
let person2 = new Person('Greg', 27, 'Doctor')
複製代碼

這裏咱們使用 new 操做符來實例化一個對象時發生瞭如下幾件事:
函數

  1. 新建一個對象
  2. 將構造函數的 this 指向新對象
  3. 執行構造函數中的代碼
  4. 返回新對象

同時 person1prototype 被賦值爲 Person.prototype 所以使用 person1 instanceof Person 時其實在判斷 Person.prototype 是否出如今了 person1 的原型鏈之中,便可判斷 person1Person 的實例。
可是構造函數模式依然沒有解決工廠模式的第二個問題。ui

原型模式

原型模式的存在即解決了上述所說的 第二個問題 它使得一個類的全部實例共享一些屬性。而在那以前咱們須要瞭解什麼是原型對象,即上文提到的 prototypethis

prototype

這裏有幾個點本身以爲比較重要的,對於我本身理解何爲原型比較有幫助
spa

  1. prototype是一個對象
  2. 實例將包含一個屬性,這個屬性是一個指向了構造函數的原型對象的指針
  3. 實例中能夠獲取到的屬性分兩種,一種 實例屬性 一種是 原型屬性

接下來再討論原型對象:
prototype

  1. 只要建立了一個函數,就會爲該函數建立一個 prototype 屬性,這個屬性指向一個對象即該函數的原型對象
  2. 默認狀況下全部的原型對象都會自動得到一個 constructor 屬性,這個屬性是一個 指向 prototype 所在函數的指針。即 Person.prototype.constructor 指向了 Person
  3. 當調用構造函數建立一個實例後,該實例的內部將包含一個指針,指向了構造函數的原型對象。

代碼示例以下:指針

function Person () {}

Person.prototype.name = 'Nichalos'
Person.prototype.age = 29
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function () {
  console.log('Hello my name is ' + this.name)
}

let person = new Person()
複製代碼

以上代碼表現成原型狀態以下,沒有上圖只有文字描述,多讀幾遍其實文字也來得精妙啦:

  1. Person.prototype 指向了一個對象(如下簡稱原型對象,這裏時刻牢記是一個對象),即 Person 的原型對象
  2. Person 的原型對象包含一系列屬性:第一個即默認狀況下被賦予的 constructor 屬性,該屬性是一個指向構造函數 Person 的指針,因此 Person.prototype.constructor 值爲 Person;剩下的屬性即代碼中定義的各個屬性(nameagejobsayName),因此這個原型對象的值爲:
{
 constructor: Person,
 name: 'Nichalos',
 age: 29,
 job: 'Software Engineer',
 sayName: Function
}
複製代碼
  1. 實例出 person 實例後,person 被賦予了一個內部屬性,這個屬性指向了第2步中的原型對象,一般這個對象是 __proto__。值得注意的是,雖然咱們只是實例化 person,並無未其添加屬性和方法,可是卻能夠經過搜索原型鏈 訪問 __proto__ 中的屬性和方法。我本身將其稱做 原型屬性

以上描述了原型對象的由來、理清楚了 實例構造函數原型對象 的關係:實例和構造函數沒什麼關係,可是實例有一個屬性指向了原型對象,而原型對象是屬於構造函數的,因此三者存在了聯繫。而接下來咱們要討論一些關於原型對象的方法:

hasOwnProperty(propertyName: String) // 返回實例是否具備該實例屬性(即實例自己的屬性)
in // 操做符,'name' in person,這個操做符只要對象可以訪問到該屬性就返回true,不管是實例屬性仍是原型屬性
Object.keys(obj: Object) // 該方法會返回一個包含全部可枚舉屬性的字符串數組
Object.getOwnPropertyNames(obj: Object) // 該方法會的到全部實例的屬性不管是實例的屬性仍是原型屬性,也不論是否可枚舉
複製代碼

接着是原型的特性:

  1. 爲實例添加屬性會屏蔽原型中的同名屬性而不是改寫
  2. 原型具備動態性,當咱們對原型對象作出修改時,可以馬上從實例上反應出來,就算是先建立了實例,再修改了原型,以前建立的實例的 [[prototype]] 依然會反應出來
  3. 若是先建立了實例,就不能切斷原本原型和構造函數之間的聯繫,即不能重寫構造函數的 prototype 屬性例以下:
function Person () {}

Person.prototype.name = 'Nicholas'
Person.prototype.age = 22
Person.prototype.sayName = function () {
  console.log(`Hello, my name is ${this.name}`)
}

let person = new Person()
person.sayName() // Hello, my name is Nicholas

person.name = 'Bob'

person.sayName() // Hello, my name is Bob

Person.prototype = {
  constructor: Person,
  name: 'Peter',
  job: 'Software Engineer',
  tellJob: function () {
    console.log(`Hello, my job is ${this.job}`)
  }
}

let anotherPerson = new Person()

anotherPerson.tellJob() // Hello, my job is Software Engineer
anotherPerson.sayName() // type error, no method called sayName

person.sayName() // Hello, my name is Bob
person.tellJob() // type error, no method called tellJob
複製代碼

以上代碼解釋了實例和構造函數的原型對象之間的關係,同時闡明,當咱們訪問一個對象的實例的時候,會先從屬於實例自身的實例的屬性搜索,找到了直接返回,若找不到則取原型對象中去找。同時,因爲全部的實例都共享一個原型,咱們能夠將公共的屬性和方法都寫在原型對象裏,讓實例共享。解決了文章一開始說的第二個問題。可是這種方法引入了新的一個問題:

function Person () {}

Person.prototype = {
  constructor: Person,
  name: 'Nicholas',
  age: 22,
  job: 'Software Engineer',
  friends: ['Shelby', 'Court'],
  sayName: function () {
    console.log(`Hello, my name is ${this.name}`)
  }
}

let person1 = new Person()
let person2 = new Person()

person1.friends // ['Shelby', 'Court']
person2.friends // ['Shelby', 'Court']

person1.friends.push('Bob')

person1.friends // ['Shelby', 'Court', 'Bob']
person2.friends // ['Shelby', 'Court', 'Bob']

person1.friends === person2.friends // true

複製代碼

上述代碼說明,原型對象會讓實例共享屬性,而實際上,咱們但願實例擁有本身的屬性。

組合使用構造函數模式和原型模式

這個模式時爲了解決上述提出的三個問題的,最重要時解決讓實例擁有本身的屬性,同時全部實例又共享原型上的方法。簡而言之爲將屬性和方法分開設置。

function Person (name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.friends = ['Shelby', 'Court']
}

Person.prototype = {
  constructor: Person,
  sayName: function () {
    console.log(`Hello, my name is ${this.name}`)
  }
}

let person1 = new Person('Nicholas', 22, 'Software Engineer')
let person2 = new Person('Greg', 27, 'Doctor')

person1.friends.push('Bob')

person1.friends // ['Shelby', 'Court', 'Bob']
person2.friends // ['Shelby', 'Court']
person1.friends === person2.friends // false
person1.sayName === person2.sayName true
複製代碼

其餘

其實還剩下幾種建立對象的方法:

  1. 動態原型模型:將對構造函數 prototype 屬性書寫的位置換到構造函數中來
  2. 寄生構造函數模式和穩妥構造函數模式:這兩種方法建立的對象和在構造函數外部直接建立對象沒有區別,因此沒法斷定實例和構造函數之間的關係,這裏就不作記載了,有興趣能夠翻看書本進行了解。

持續更新在github

相關文章
相關標籤/搜索