在 ES6 規範中,引入了 class
的概念。使得 JS 開發者終於告別了,直接使用原型對象模仿面向對象中的類和類繼承時代。javascript
可是JS 中並無一個真正的 class
原始類型, class
僅僅只是對原型對象運用語法糖。因此,只有理解如何使用原型對象實現類和類繼承,才能真正地用好 class
。java
class
經過類來建立對象,使得開發者沒必要寫重複的代碼,以達到代碼複用的目的。它基於的邏輯是,兩個或多個對象的結構功能相似,能夠抽象出一個模板,依照模板複製出多個類似的對象。就像自行車製造商一遍一遍地複用相同的藍圖來製造大量的自行車。git
使用 ES6 中的 class
聲明一個類,是很是簡單的事。它的語法以下:github
class Person { constructor(name){ this.name = name } hello(){ console.log('Hello, my name is ' + this.name + '.'); } } var xiaoMing = new Person('xiaoMing'); xiaoMing.hello() // Hello, my name is xiaoMing.
xiaoMing
是經過類 Person
實例化出來的對象。對象 xiaoMing
是按照類 Person
這個模板,實例化出來的對象。實例化出來的對象擁有類預先訂製好的結構和功能。segmentfault
ES6 的語法很簡單,可是在實例化的背後,到底是什麼在起做用呢?ide
class
實例化的背後原理使用 class
的語法,讓開發者告別了使用 prototype
模仿面向對象的時代。可是,class
並非 ES6 引入的全新概念,它的原理依舊是原型繼承。函數
class
== "function"經過類型判斷,咱們能夠得知,class
的並非什麼全新的數據類型,它實際只是 function
(或者說 object
)。this
class Person { // ... } typeof Person // function
爲了更加直觀地瞭解 Person
的實質,能夠將它在控制檯打印出來,以下。spa
Person
的屬性並很少,除去用 [[...]]
包起來的內置屬性外,大部分屬性根據名字就能明白它的做用。須要咱們重點關注的是 prototype
和 __proto__
兩個屬性。prototype
(關於 __proto__
能夠在本文的姊妹篇 找到答案)
prototype
先來說講 prototype
屬性,它指向一個特殊性對象:原型對象。
原型對象因此特殊,是由於它擁有一個普通對象沒有的能力:將它的屬性共享給其餘對象。
在 ES6 規範 中,對 原型對象 是以下定義的:
object that provides shared properties for other objects
原型對象是如何將它的屬性分享給其餘對象的呢?
這裏使用 ES5 建立一個類,並將它實例化,來看看它的實質。
function Person() { this.name = name } // 1. 首先給 Person.prototype 原型對象添加了 describe 方法 。 Person.prototype.describe = function(){ console.log('Hello, my name is ' + this.name + '.'); } // 2. 實例化對象的 __proto__ 指向 Person.prototype var jane = new Person('jane'); jane.__proto__ === Person.prototype; // 3. 讀取 describe 方法時,實際會沿着原型鏈查找到 Person.prototype 原型對象上。 jane.describe() // Hello, my name is jane.
上述使用 JS 模仿面向對象實例化的背後,實際有三個步驟:
首先給 Person.prototype
屬性所指的原型對象上添加了一個方法 describe
。
在使用 new
關鍵字建立對象時,會默認給該對象添加一個原型屬性 __proto__
,該屬性指向 Person.prototype
原型對象。
在讀取 describe
方法時,首先會在 jane
對象查找該方法,可是 jane
對象並不直接擁有 describe
方法。因此會沿着原型鏈查找到 Person.prototype 原型對象上,最後返回該原型對象的 describe
方法。
JS 中面向對象實例化的背後原理,實際上就是 原型對象。
爲了方便你們理解,從網上扒了一張的圖片,放到這來便於你們理解。
prototype
與 __proto__
區別理解上述原理後,還須要注意 prototype
與 __proto__
屬性的區別。
__proto__
所指的對象,真正將它的屬性分享給它所屬的對象。全部的對象都有 __proto__
屬性,它是一個內置屬性,被用於繼承。
prototype
是一個只屬於 function
的屬性。當使用 new
方法調用該構造函數的時候,它被用於構建新對象的 __proto__
。另外它不可寫,不可枚舉,不可配置。
( new Foo() ).__proto__ === Foo.prototype ( new Foo() ).prototype === undefined
class
定義屬性當咱們使用 class
定義屬性(方法)的時候,實際上等因而在 class
的原型對象上定義屬性。
class Foo { constructor(){ /* constructor */ } describe(){ /* describe */ } } // 等價於 function Foo (){ /* constructor */ } Foo.prototype.describe = function(){ /* describe */ }
constructor
是一個比較特殊的屬性,它指向構造函數(類)自己。能夠經過如下代碼驗證。
Foo.prototype.constructor === Foo // true
在傳統面向對象中,類是能夠繼承類的。這樣子類就能夠複製父類的方法,達到代碼複用的目的。
ES6 也提供了類繼承的語法 extends
,以下:
class Foo { constructor(who){ this.me = who; } identify(){ return "I am " + this.me; } } class Bar extends Foo { constructor(who){ // super() 指的是調用父類 // 調用的同時,會綁定 this 。 // 如:Foo.call(this, who) super(who); } speak(){ alert( "Hello, " + this.identify() + "." ); } } var b1 = new Bar( "b1" ); b1.speak();
當實例 b1
調用 speak
方法時,b1
自己沒有 speak
,因此會到 Bar.prototype
原型對象上查找,而且調用原型對象上的 speak
方法。調用 identify
方式時,因爲 this
指向的是 b1
對象。因此也會先在 b1
自己查找,而後沿着原型鏈,查找 Bar.prototype
,最後在 Foo.prototype
原型對象上找到 identify
方法,而後調用。
實際上,在 JavaScript 中,類繼承的本質依舊是原型對象。
他們的關係以下圖所示: