ES6系列之Babel是如何編譯Class的(上)
前言
在瞭解 Babel 是如何編譯 class 前,我們先看看 ES6 的 class 和 ES5 的建構函式是如何對應的。畢竟,ES6 的 class 可以看作一個語法糖,它的絕大部分功能,ES5 都可以做到,新的 class 寫法只是讓物件原型的寫法更加清晰、更像物件導向程式設計的語法而已。
constructor
ES6 中:
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
return `hello, I am ` + this.name;
}
}
var kevin = new Person(`Kevin`);
kevin.sayHello(); // hello, I am Kevin
對應到 ES5 中就是:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
return `hello, I am ` + this.name;
};
var kevin = new Person(`Kevin`);
kevin.sayHello(); // hello, I am Kevin
我們可以看到 ES5 的建構函式 Person,對應 ES6 的 Person 類的 constructor 方法。
值得注意的是:類的內部所有定義的方法,都是不可列舉的(non-enumerable)
以上面的例子為例,在 ES6 中:
Object.keys(Person.prototype); // []
Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]
然而在 ES5 中:
Object.keys(Person.prototype); // [`sayHello`]
Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]
例項屬性
以前,我們定義例項屬性,只能寫在類的 constructor 方法裡面。比如:
class Person {
constructor() {
this.state = {
count: 0
};
}
}
然而現在有一個提案,對例項屬性和靜態屬性都規定了新的寫法,而且 Babel 已經支援。現在我們可以寫成:
class Person {
state = {
count: 0
};
}
對應到 ES5 都是:
function Person() {
this.state = {
count: 0
};
}
靜態方法
所有在類中定義的方法,都會被例項繼承。如果在一個方法前,加上 static 關鍵字,就表示該方法不會被例項繼承,而是直接通過類來呼叫,這就稱為“靜態方法”。
ES6 中:
class Person {
static sayHello() {
return `hello`;
}
}
Person.sayHello() // `hello`
var kevin = new Person();
kevin.sayHello(); // TypeError: kevin.sayHello is not a function
對應 ES5:
function Person() {}
Person.sayHello = function() {
return `hello`;
};
Person.sayHello(); // `hello`
var kevin = new Person();
kevin.sayHello(); // TypeError: kevin.sayHello is not a function
靜態屬性
靜態屬性指的是 Class 本身的屬性,即 Class.propName,而不是定義在例項物件(this)上的屬性。以前,我們新增靜態屬性只可以這樣:
class Person {}
Person.name = `kevin`;
因為上面提到的提案,現在可以寫成:
class Person {
static name = `kevin`;
}
對應到 ES5 都是:
function Person() {};
Person.name = `kevin`;
new 呼叫
值得注意的是:類必須使用 new 呼叫,否則會報錯。這是它跟普通建構函式的一個主要區別,後者不用 new 也可以執行。
class Person {}
Person(); // TypeError: Class constructor Foo cannot be invoked without `new`
getter 和 setter
與 ES5 一樣,在“類”的內部可以使用 get 和 set 關鍵字,對某個屬性設定存值函式和取值函式,攔截該屬性的存取行為。
class Person {
get name() {
return `kevin`;
}
set name(newName) {
console.log(`new name 為:` + newName)
}
}
let person = new Person();
person.name = `daisy`;
// new name 為:daisy
console.log(person.name);
// kevin
對應到 ES5 中:
function Person(name) {}
Person.prototype = {
get name() {
return `kevin`;
},
set name(newName) {
console.log(`new name 為:` + newName)
}
}
let person = new Person();
person.name = `daisy`;
// new name 為:daisy
console.log(person.name);
// kevin
Babel 編譯
至此,我們已經知道了有關“類”的方法中,ES6 與 ES5 是如何對應的,實際上 Babel 在編譯時並不會直接就轉成這種形式,Babel 會自己生成一些輔助函式,幫助實現 ES6 的特性。
我們可以在 Babel 官網的 Try it out 頁面檢視 ES6 的程式碼編譯成什麼樣子。
編譯(一)
ES6 程式碼為:
class Person {
constructor(name) {
this.name = name;
}
}
Babel 編譯為:
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function Person(name) {
_classCallCheck(this, Person);
this.name = name;
};
_classCallCheck 的作用是檢查 Person 是否是通過 new 的方式呼叫,在上面,我們也說過,類必須使用 new 呼叫,否則會報錯。
當我們使用 var person = Person()
的形式呼叫的時候,this 指向 window,所以 instance instanceof Constructor
就會為 false,與 ES6 的要求一致。
編譯(二)
ES6 程式碼為:
class Person {
// 例項屬性
foo = `foo`;
// 靜態屬性
static bar = `bar`;
constructor(name) {
this.name = name;
}
}
Babel 編譯為:
`use strict`;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function Person(name) {
_classCallCheck(this, Person);
this.foo = `foo`;
this.name = name;
};
Person.bar = `bar`;
編譯(三)
ES6 程式碼為:
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
return `hello, I am ` + this.name;
}
static onlySayHello() {
return `hello`
}
get name() {
return `kevin`;
}
set name(newName) {
console.log(`new name 為:` + newName)
}
}
對應到 ES5 的程式碼應該是:
function Person(name) {
this.name = name;
}
Person.prototype = {
sayHello: function () {
return `hello, I am ` + this.name;
},
get name() {
return `kevin`;
},
set name(newName) {
console.log(`new name 為:` + newName)
}
}
Person.onlySayHello = function () {
return `hello`
};
Babel 編譯後為:
`use strict`;
var _createClass = function() {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function(Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function() {
function Person(name) {
_classCallCheck(this, Person);
this.name = name;
}
_createClass(Person, [{
key: `sayHello`,
value: function sayHello() {
return `hello, I am ` + this.name;
}
}, {
key: `name`,
get: function get() {
return `kevin`;
},
set: function set(newName) {
console.log(`new name 為:` + newName);
}
}], [{
key: `onlySayHello`,
value: function onlySayHello() {
return `hello`;
}
}]);
return Person;
}();
我們可以看到 Babel 生成了一個 _createClass 輔助函式,該函式傳入三個引數,第一個是建構函式,在這個例子中也就是 Person,第二個是要新增到原型上的函式陣列,第三個是要新增到建構函式本身的函式陣列,也就是所有新增 static 關鍵字的函式。該函式的作用就是將函式陣列中的方法新增到建構函式或者建構函式的原型中,最後返回這個建構函式。
在其中,又生成了一個 defineProperties 輔助函式,使用 Object.defineProperty 方法新增屬性。
預設 enumerable 為 false,configurable 為 true,這個在上面也有強調過,是為了防止 Object.keys() 之類的方法遍歷到。然後通過判斷 value 是否存在,來判斷是否是 getter 和 setter。如果存在 value,就為 descriptor 新增 value 和 writable 屬性,如果不存在,就直接使用 get 和 set 屬性。
寫在後面
至此,我們已經瞭解了 Babel 是如何編譯一個 Class 的,然而,Class 還有一個重要的特性就是繼承,Class 如何繼承,Babel 又該如何編譯,歡迎期待下一篇《 ES6 系列之 Babel 是如何編譯 Class 的(下)》
ES6 系列
ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標籤模板、箭頭函式、Symbol、Set、Map 以及 Promise 的模擬實現、模組載入方案、非同步處理等內容。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
相關文章
- ES6 系列之 Babel 是如何編譯 Class 的(上)Babel編譯
- ES6 系列之 Babel 是如何編譯 Class 的(下)Babel編譯
- babel是如何編譯es6 class和extends的Babel編譯
- ES6 系列之 Babel 將 Async 編譯成了什麼樣子Babel編譯
- ES6 系列之 Babel 將 Generator 編譯成了什麼樣子Babel編譯
- ES6系列之Babel將Generator編譯成了什麼樣子Babel編譯
- Es6 Class是如何實現的?
- TypeScript 編譯 classTypeScript編譯
- 反編譯系列教程(上)編譯
- [譯] React 是如何區分 Class 和 Function 的 ?ReactFunction
- webpack4 系列教程(二): 編譯 ES6Web編譯
- 入門babel--實現一個es6的class轉換器Babel
- (譯)React是如何區分Class和Function?ReactFunction
- 如何將一個Java檔案編譯成classJava編譯
- 如何配置 jad,讓 Eclipse 可以自動顯示反編譯之後的 .class 原始碼Eclipse編譯原始碼
- 如何理解es6中的class,以及class中的constructor函式Struct函式
- Python是如何編譯執行的Python編譯
- Android Apk反編譯系列教程(一)如何反編譯APKAndroidAPK編譯
- React Native babel編譯異常問題React NativeBabel編譯
- TypeScript 之 Class(上)TypeScript
- ES6和BabelBabel
- es6新特性之 class 基本用法
- 手把手帶你走進Babel的編譯世界Babel編譯
- 如何編寫一個前端框架之五-基於 ES6 代理的資料繫結(譯)前端框架
- TiDB 原始碼系列之沉浸式編譯 TiDBTiDB原始碼編譯
- 手寫 Vue2 系列 之 編譯器Vue編譯
- 為什麼說ES6的class是語法糖?
- ecplise配置jad反編譯.class檔案編譯
- 從AST編譯解析談到寫babel外掛AST編譯Babel
- 程式碼線上編譯器(上)- 編輯及編譯編譯
- ES6 class類的用法
- ES6 系列之 WeakMap
- Android編譯時註解框架系列1-什麼是編譯時註解Android編譯框架
- eclipse中怎麼找到編譯後的class路徑Eclipse編譯
- es6 class解析
- ES6 -- Babel 轉碼器Babel
- 深入 JavaScript 原型繼承原理——babel 編譯碼解讀JavaScript原型繼承Babel編譯
- Babel:下一代Javascript語法編譯器BabelJavaScript編譯