JavaScript 是動態的,而且自己不提供一個 class
實現。(在 ES2015/ES6 中引入了 class
關鍵字,但那只是語法糖,JavaScript 仍然是基於原型的)。javascript
不斷向上追溯的原型共同組成了原型鏈前端
null
。null
沒有原型,並做爲這個原型鏈中的最後一個環節。Object
的實例。上面的解釋可能有些繞,看完下面應該就會清晰一些。java
JavaScript 對象是動態的屬性「包」(指其本身的屬性)。JavaScript 對象有一個指向一個原型對象的鏈。當試圖訪問一個對象的屬性時,它不單單在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。函數
這裏演示當嘗試訪問屬性時會發生什麼:post
// 讓咱們從一個自身擁有屬性a和b的構造函數裏建立一個對象o:
let f = function () {
this.a = 1;
this.b = 2;
}
/* 這麼寫也同樣 function f() { this.a = 1; this.b = 2; } */
let o = new f(); // {a: 1, b: 2}
console.log(o) // f { a: 1, b: 2 }
// 指向構造函數
console.log(o.constructor)
// function () {
// this.a = 1;
// this.b = 2;
// }
// 實例沒有原型對象
console.log(o.prototype) // undefined
//構造函數的原型對象
console.log(o.constructor.prototype, f.prototype) f {} f {}
// 實例的__proto__指向構造函數的原型對象
console.log('o.proto', o.__proto__, typeof o.__proto__) // f {} object
複製代碼
綜上,整個原型鏈以下:ui
如今,下面四條就很清晰明確了this
null
。null
沒有原型,並做爲這個原型鏈中的最後一個環節。
- 對象的原型爲
__proto__
屬性- 函數有2個屬性,一個是是
__proto__
,還有一個是函數專有的prototype
屬性,由於函數有雙重身份,便可以是實例也能夠是構造器Object.prototype
爲{}
- 箭頭函數雖然屬於函數,由Function產生,可是沒有prototype屬性沒有構造器特性,因此也就沒有所謂的constructor,不能做爲構造器使用
下面對構造函數的原型作些變動spa
// 在f函數的原型上定義屬性
f.prototype.b = 3;
f.prototype.c = 4;
複製代碼
不要在 f 函數的原型上直接定義對象, f.prototype = {b:3,c:4}; 這樣會直接打破原型鏈prototype
// 實例的__proto__指向構造函數的原型對象
console.log('o.proto', o.__proto__) // f { b: 3, c: 4 }
複製代碼
變動以後的原型鏈以下:code
// 遵循ECMAScript標準,someObject.[[Prototype]] 符號是用於指向 someObject 的原型。
console.log(o.a); // 1
// a是o的自身屬性嗎?是的,該屬性的值爲 1
console.log(o.b); // 2
// b是o的自身屬性嗎?是的,該屬性的值爲 2
// 原型上也有一個'b'屬性,可是它不會被訪問到。
// 這種狀況被稱爲"屬性遮蔽 (property shadowing)"
console.log(o.c); // 4
// c是o的自身屬性嗎?不是,那看看它的原型上有沒有
// c是o.[[Prototype]]的屬性嗎?是的,該屬性的值爲 4
console.log(o.d); // undefined
// d 是 o 的自身屬性嗎?不是,那看看它的原型上有沒有
// d 是 o.[[Prototype]] 的屬性嗎?不是,那看看它的原型上有沒有
// o.[[Prototype]].[[Prototype]] 爲 null,中止搜索
// 找不到 d 屬性,返回 undefined
複製代碼
JavaScript 並無其餘基於類的語言所定義的「方法」。在 JavaScript 裏,任何函數均可以添加到對象上做爲對象的屬性。函數的繼承與其餘的屬性繼承沒有差異,包括上面的「屬性遮蔽」(這種狀況至關於其餘語言的方法重寫)。
當繼承的函數被調用時,this 指向的是當前繼承的對象,而不是繼承的函數所在的原型對象。
ECMAScript 5 中引入了一個新方法:
Object.create()
。能夠調用這個方法來建立一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數:
var o = {
a: 2,
m: function(){
return this.a + 1;
}
};
console.log(o.m()); // 3
// 當調用 o.m 時,'this' 指向了 o.
var p = Object.create(o);
// p是一個繼承自 o 的對象
p.a = 4; // 建立 p 的自身屬性 'a'
console.log(p.m()); // 5
// 調用 p.m 時,'this' 指向了 p
// 又由於 p 繼承了 o 的 m 函數
// 因此,此時的 'this.a' 即 p.a,就是 p 的自身屬性 'a'
複製代碼
相關係列: 從零開始的前端築基之旅(超級精細,持續更新~)
若是你收穫了新知識,請給做者點個贊吧~
參考文檔:
MDN:繼承與原型鏈