在了解 Babel 是如何编译 class 前,咱们先看看 ES6 的 class 和 ES5 的构造函数是如何对应的。毕竟,ES6 的 class 能够看做一个语法糖,它的绝大部分功能,ES5 均可以作到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。react
ES6 中:git
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 中就是:github
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 中:babel
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 已经支持。如今咱们能够写成:ui
class Person {
state = {
count: 0
};
}
复制代码
对应到 ES5 都是:this
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 也能够执行。
class Person {}
Person(); // TypeError: Class constructor Foo cannot be invoked without 'new'
复制代码
与 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
复制代码
至此,咱们已经知道了有关“类”的方法中,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 系列目录地址:github.com/mqyqingfeng…
ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级做用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。
若是有错误或者不严谨的地方,请务必给予指正,十分感谢。若是喜欢或者有所启发,欢迎 star,对做者也是一种鼓励。