爲何說ES6的class是語法糖?

0. 前言

咱們帶着問題去閱讀本文:javascript

  • 爲何說ES6的class是語法糖?
  • class是原型的語法糖嗎?
  • 那又是如何使用原型來實現class這一語法糖的呢?

1. 基於Prototype的OOP

先來看一個prototype的例子:html

function Person (name, sex) {
	this.name = name
	this.sex = sex
}

function Man (name) {
	this.name = name
}

Man.prototype = new Person('', 'male')

let Jy = new Man('Jy')

console.log(Jy.name, Jy.sex) // Jy, male
複製代碼

這是咱們使用原型的一個很簡單的例子,Person具備名字和性別,Man是一個性別爲男的Person,Jy是一個Man。咱們先記住這一個例子,下面將使用class重寫這個例子。前端

Tips: new, this等是Brendan Eich使之更像Java的OOP而加上的,有興趣的讀者能夠自行查閱相關信息。java

2. ES6 Class的OOP

class Person {
	constructor (name, sex) {
		this.name = name
		this.sex = sex
	}
}

class Man extends Person {
	constructor (name) {
		super('', 'male')
		this.name = name
	}
}

let Jy = new Man('Jy')

console.log(Jy.name, Jy.sex) // Jy, 'male'
複製代碼

咱們經過重寫這個例子,採用了class、constructor、extends、super 這些單詞,接下來就具體來講說ES6規範中對它們作了什麼。express

3. 使用Prototype實現的Class OOP(ES6規範)

在ES6以前,JS對象其實就是屬性的集合,而屬性則是一組鍵值對(key, value),key能夠是String or Symbol, value包括數據屬性特徵值和訪問器特徵值。babel

file

你說普通的屬性還好,不還有對象下面的方法嗎?怎麼就變成了屬性的集合呢?app

其實在ES5規範中出現的method的定義是「function that is the value of a property」,是對象的函數屬性而已,不能稱之爲方法,直到ES6出現,規範中才有Method Definitions。函數

咱們能想到的在ES3有關OOP的東西: prototype、new、 this、 constructor、 instanceof, 甚至不是規範的 __proto__ 屬性。oop

所幸的是在ES5中咱們增長了不少方法來補全它,使之完備:ui

  • Object.defineProperty
  • Object.freeze
  • Object.create
  • Object.getPrototypeOf
  • Object.setPrototypeOf
  • isPrototypeOf
  • ......

再來看一段代碼:

let obj = {
	name: 'Jy',
	speak () { // Note: it's not speak: function () {}
		console.log(this.name, super.name)
	}
}

obj.speak() // Jy, undefined

Object.setPrototypeOf(obj,  { name: 'super' })

obj.speak() // Jy, super

let speak = obj.speak
speak() // undefined, super
複製代碼

obj.speak在ES6中定義已是Method了,它具備屬性[[homeObject]],homeObject指向方法被調用的對象(代碼中指的是obj), 它是綁定在對象中的Internal Slots,也就是你不能去修改,就至關於寫死了。

那麼homeObject有什麼用呢?它跟super密切相關,當解析到super這一關鍵字的時候就會找homeObject的prototype。

簡單來講,總結爲下面兩條公式:

  • let homeObj = Method[[HomeObject]] = obj
  • super = Object.getPrototypeOf(homeObj)

Note: homeObject是靜態綁定在internal slots中的,而super是動態查找的。

講完super,咱們來說講extends和constructor

class A extends B { }

class A extends B {
	constructor (...args) {
		super(args)
	}
}

class C extends null { }
複製代碼

extends主要作了如下兩件事:

  • Object.setPrototypeOf(A, B)
  • Object.setPrototypeOf(A.prototype, B.prototype)

若是父類是null, 則執行Object.setPrototypeOf(C.prototype, null)

上述代碼的第一和第二部分區別在於有沒有顯示聲明constructor, 那麼這兩段代碼是否等價呢?答案是等價的。

規範中就是這麼定義的:

file

代碼的第三部分是繼承了null, 它不會報語法錯誤,可是咱們沒法new一個C出來,緣由是new的時候會調用null的constructor,而null沒有constructor。

看到這裏,ES6的class oop, 規範聲明都是使用原型來操做,因此咱們是否是能夠說class是原型的語法糖了?

4. babel編譯後的class

咱們實際項目中多采用babel來編譯ES六、7的代碼,因此這節咱們就來分析如下babel編譯後的代碼,其中會省略一些報錯、類型檢測的一些相關代碼來更好地呈現使用原型來實現OOP的主題。

編譯前:

class A extends B {}

console.log(new A)
複製代碼

編譯後:

"use strict";

function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
    return o.__proto__ || Object.getPrototypeOf(o);
  };
  return _getPrototypeOf(o);
}

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      writable: true,
      configurable: true
    }
  });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
    o.__proto__ = p;
    return o;
  };
  return _setPrototypeOf(o, p);
}

var A =
  /*#__PURE__*/
  function (_B) {
    _inherits(A, _B);

    function A() {

      return _getPrototypeOf(A).apply(this, arguments);
    }

    return A;
  }(B);

console.log(new A());
複製代碼

咱們重點看_inherits 方法,跟咱們上述說的extends作的兩件事是同樣的:

  • Object.setPrototypeOf(subClass, superClass)
  • Object.setPrototypeOf(subClass.prototype, superClass.prototype)

只不過它採用的是Object.create方法,這兩個方法的區別能夠去MDN上查看。

再看function A內部,其實就是執行了B的構造器函數來達到super(arguments)的效果, 這個與規範:若是沒有顯示聲明constructor會自動加上constructor是一致的。

5. 總結

至此,咱們終於理解了爲何class是原型的語法糖以及如何使用原型來實現class這一語法糖。

但切記咱們使用原型的目的並非來模擬class oop的,prototype based的oop應該用prototype去理解而不是class。

ES6的class oop 是不完備的 ,例如abstract class 、interface、private等都尚未,不過有些功能已經在提案中了,你們能夠擁抱它,或者TypeScript是個不錯的選擇,若是你的項目中使用到了TS, 歡迎你到評論區分享你的感覺。

6. 參考連接

相關文章
相關標籤/搜索