JavaScript原型與原型鏈
JavaScript原型與原型鏈
正如一些面嚮物件語言中所實現的那樣,在JavaScript中我們有時也需要建立一個擁有公共函式與屬性的類作為父類來減少程式碼重複、實現型別檢查與實現更加清晰地程式碼結構。在JavaScript中,繼承是通過原型鏈實現的。瞭解JavaScript的繼承與原型鏈之前首先需要了解JavaScript中物件建立的方式。
在JavaScript中建立物件
JavaScript中物件建立的方式有兩種:工廠方法(Factory Functions)、構造器方法(Constructor Functions) 。
工廠方法
工廠方法在程式設計領域是一個非類或構造器的返回物件的方法。在JavaScript中,任何返回不使用new
function person(firstName, lastName, age) {
const person = {};
person.firstName = firstName;
person.lastName = lastName;
person.age = age;
return person;
}
構造器方法
構造器方法和工廠方法的區別僅在用例和命名規範上。命名規範上一個構造器方法的名字開頭字母需要大寫,我們需要通過new
關鍵詞來呼叫構造器方法生成例項。這個例項之後便可以通過instanceof
關鍵詞來檢查。
function Person(firstName, lastName, age) { this.firstName = firstName; this.lastName = lastName; this.age = age; }
new
的行為
當同時在工廠方法和構造器方法上使用new
關鍵詞建立時,工廠方法創建出的物件的__proto__
屬性指向Object.prototype
,構造器方法創建出的物件的__proto__
屬性指向本身的Xxx.prototype
。
const mike = new person('mike', 'grand', 23);
mike.__proto__ // Object.prototype
const jack = new Person('jack', 'grand', 23); jack.__proto__ // Person.protytype這裡的prototype指向Person的Prototype Object jack.__proto__.__proto__ // Object.prototype
new
關鍵詞在後臺為構造器方法執行了以下幾步
- 在構造器方法內建立一個新物件並將其賦值到
this
上 - 設定物件的
[[Prototype]]
和__proto__
為原型的建構函式,這一步也讓新物件的建構函式在構造新物件時被新增到原型鏈上 - 如果這個方法內沒有返回
object
、function
或array
型別的結果,就返回this
- 如果這個方法內沒有返回任何值則返回
this
下面是一個展示new
關鍵詞在JavaScript引擎當中執行效果的虛擬碼,註釋當中的是用來示範new
關鍵詞新增語句的虛擬碼
function Person(firstName, lastName, age) {
// this = {};
// this.__proto__ = Person.prototype;
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
// return this;
}
在構造器方法上沒有返回值所以後臺建立的this
將被返回,而工廠方法內由於返回了物件所以後臺不再新增return this
自然返回的內容將不一致。
如果沒有在構造器方法前使用new
關鍵詞,而將構造器方法直接呼叫執行,其僅作為一個方法來被執行而非構造器。
const bob = Person('bob', 'grand', 23);
bob // undefined. 因為這裡Person當作方法直接呼叫了且沒有返回值
window.firstName // bob. 函式內的this將指向全域性作用域,導致意外操作
繼承與原型鏈
原型
原型(Prototype)可以認為是一個JavaScript方法的屬性,每次在JavaScript程式碼中建立方法時,JavaScript引擎會將一個名為prototype
的屬性新增上去,這個prototype
屬性是一個物件(原型物件),這個物件預設有一個constructor
屬性指向原方法物件。任何新增到prototype
的屬性和方法都在這個物件裡面,所有該類例項共享這個原型物件,例項物件的__proto__
屬性指向這個物件,方法的prototype
屬性指向這個物件。
在ECMAScript的標準裡object.[[Prototype]]
是訪問原型的方法,但在ECMAScript 2015中用Object.getPrototypeOf()
和Object.setPrototypeOf()
來替代。等價的__proto__
是多數瀏覽器使用的事實上的但是非標準的實現。
function Person(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
Person.prototype === Person.prototype.constructor.prototype // 指向Person的原型物件
Person.prototype.constructor === Person // 指向Person方法物件
let bob = new Person("Bob", "Ross", 21);
Person.prototype === bob.__proto__; // true
let alex = new Person("Alex", "Wang", 21);
Person.prototype === alex.__proto__; // true
alex.__proto__ === bob.__proto__; // true
原型鏈
首先我們需要了解物件查詢機制。當我們使用一個物件的屬性時,JavaScript引擎會首先查詢本物件裡是否有對應屬性,如果沒有則去物件的原型裡查詢屬性,如果沒有則去物件的原型物件的原型物件裡查詢屬性,直至查詢到物件的__proto__
為null
的時候停止。
const obj = {};
console.log(obj); // [object Object] obj的toString()方法從Object的原型中查詢到並使用
function Person(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
// 在Person.prototype上定義了toString覆寫了Object.prototype上的toString
Person.prototype.toString = function() {
return `${this.firstName} It Is`;
}
}
let bob = new Person("Bob", "Ross", 21);
let alex = new Person("Alex", "Wang", 21);
console.log(bob); // Bob It Is
console.log(alex); // Alex It Is