瞭解如何建立對象有助於咱們理解如何繼承,和對象的
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')
複製代碼
顯而易見的是能夠屢次調用該函數獲得多個具備類似的對象,可是其存在一些缺點:
數組
- 咱們沒法知道上述的
person1
究竟是不是createPerson
的實例,由於person1
的prototype
是Object.prototype
- 咱們每建立一個對象都要新建一個
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
操做符來實例化一個對象時發生瞭如下幾件事:
函數
- 新建一個對象
- 將構造函數的
this
指向新對象- 執行構造函數中的代碼
- 返回新對象
同時
person1
的prototype
被賦值爲Person.prototype
所以使用person1 instanceof Person
時其實在判斷Person.prototype
是否出如今了person1
的原型鏈之中,便可判斷person1
爲Person
的實例。
可是構造函數模式依然沒有解決工廠模式的第二個問題。ui
原型模式的存在即解決了上述所說的 第二個問題 它使得一個類的全部實例共享一些屬性。而在那以前咱們須要瞭解什麼是原型對象,即上文提到的
prototype
。this
這裏有幾個點本身以爲比較重要的,對於我本身理解何爲原型比較有幫助
spa
- prototype是一個對象
- 實例將包含一個屬性,這個屬性是一個指向了構造函數的原型對象的指針
- 實例中能夠獲取到的屬性分兩種,一種 實例屬性 一種是 原型屬性
接下來再討論原型對象:
prototype
- 只要建立了一個函數,就會爲該函數建立一個
prototype
屬性,這個屬性指向一個對象即該函數的原型對象- 默認狀況下全部的原型對象都會自動得到一個
constructor
屬性,這個屬性是一個 指向prototype
所在函數的指針。即Person.prototype.constructor
指向了Person
- 當調用構造函數建立一個實例後,該實例的內部將包含一個指針,指向了構造函數的原型對象。
代碼示例以下:指針
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()
複製代碼
以上代碼表現成原型狀態以下,沒有上圖只有文字描述,多讀幾遍其實文字也來得精妙啦:
Person.prototype
指向了一個對象(如下簡稱原型對象,這裏時刻牢記是一個對象),即Person
的原型對象Person
的原型對象包含一系列屬性:第一個即默認狀況下被賦予的constructor
屬性,該屬性是一個指向構造函數Person
的指針,因此Person.prototype.constructor
值爲Person
;剩下的屬性即代碼中定義的各個屬性(name
、age
、job
、sayName
),因此這個原型對象的值爲:{ constructor: Person, name: 'Nichalos', age: 29, job: 'Software Engineer', sayName: Function } 複製代碼
- 實例出
person
實例後,person
被賦予了一個內部屬性,這個屬性指向了第2步中的原型對象,一般這個對象是__proto__
。值得注意的是,雖然咱們只是實例化person
,並無未其添加屬性和方法,可是卻能夠經過搜索原型鏈 訪問__proto__
中的屬性和方法。我本身將其稱做 原型屬性
以上描述了原型對象的由來、理清楚了 實例 和 構造函數 和 原型對象 的關係:實例和構造函數沒什麼關係,可是實例有一個屬性指向了原型對象,而原型對象是屬於構造函數的,因此三者存在了聯繫。而接下來咱們要討論一些關於原型對象的方法:
hasOwnProperty(propertyName: String) // 返回實例是否具備該實例屬性(即實例自己的屬性)
in // 操做符,'name' in person,這個操做符只要對象可以訪問到該屬性就返回true,不管是實例屬性仍是原型屬性
Object.keys(obj: Object) // 該方法會返回一個包含全部可枚舉屬性的字符串數組
Object.getOwnPropertyNames(obj: Object) // 該方法會的到全部實例的屬性不管是實例的屬性仍是原型屬性,也不論是否可枚舉
複製代碼
接着是原型的特性:
- 爲實例添加屬性會屏蔽原型中的同名屬性而不是改寫
- 原型具備動態性,當咱們對原型對象作出修改時,可以馬上從實例上反應出來,就算是先建立了實例,再修改了原型,以前建立的實例的
[[prototype]]
依然會反應出來- 若是先建立了實例,就不能切斷原本原型和構造函數之間的聯繫,即不能重寫構造函數的
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
複製代碼
其實還剩下幾種建立對象的方法:
- 動態原型模型:將對構造函數
prototype
屬性書寫的位置換到構造函數中來- 寄生構造函數模式和穩妥構造函數模式:這兩種方法建立的對象和在構造函數外部直接建立對象沒有區別,因此沒法斷定實例和構造函數之間的關係,這裏就不作記載了,有興趣能夠翻看書本進行了解。
持續更新在github