ES6 系列之 Babel 是如何编译 Class 的(上)

摘要: ## 前言 在了解 Babel 是如何编译 class 前,我们先看看 ES6 的 class 和 ES5 的构造函数是如何对应的。毕竟,ES6 的 class 可以看作一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。 ## constructor ES6 中: ```js class Pe

前言

在了解 Babel 是如何编译 class 前,我们先看看 ES6 的 class 和 ES5 的构造函数是如何对应的。毕竟,ES6 的 class 可以看作一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

constructor

ES6 中:

1class Person { 2 constructor(name) { 3 this.name = name; 4 } 5 6 sayHello() { 7 return 'hello, I am ' + this.name; 8 } 9} 10 11var kevin = new Person('Kevin'); 12kevin.sayHello(); // hello, I am Kevin

对应到 ES5 中就是:

1function Person(name) { 2 this.name = name; 3} 4 5Person.prototype.sayHello = function () { 6 return 'hello, I am ' + this.name; 7}; 8 9var kevin = new Person('Kevin'); 10kevin.sayHello(); // hello, I am Kevin

我们可以看到 ES5 的构造函数 Person,对应 ES6 的 Person 类的 constructor 方法。

值得注意的是:类的内部所有定义的方法,都是不可枚举的(non-enumerable)

以上面的例子为例,在 ES6 中:

1Object.keys(Person.prototype); // [] 2Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]

然而在 ES5 中:

1Object.keys(Person.prototype); // ['sayHello'] 2Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]

实例属性

以前,我们定义实例属性,只能写在类的 constructor 方法里面。比如:

1class Person { 2 constructor() { 3 this.state = { 4 count: 0 5 }; 6 } 7}

然而现在有一个提案,对实例属性和静态属性都规定了新的写法,而且 Babel 已经支持。现在我们可以写成:

1class Person { 2 state = { 3 count: 0 4 }; 5}

对应到 ES5 都是:

1function Person() { 2 this.state = { 3 count: 0 4 }; 5}

静态方法

所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

ES6 中:

1class Person { 2 static sayHello() { 3 return 'hello'; 4 } 5} 6 7Person.sayHello() // 'hello' 8 9var kevin = new Person(); 10kevin.sayHello(); // TypeError: kevin.sayHello is not a function

对应 ES5:

1function Person() {} 2 3Person.sayHello = function() { 4 return 'hello'; 5}; 6 7Person.sayHello(); // 'hello' 8 9var kevin = new Person(); 10kevin.sayHello(); // TypeError: kevin.sayHello is not a function

静态属性

静态属性指的是 Class 本身的属性,即 Class.propName,而不是定义在实例对象(this)上的属性。以前,我们添加静态属性只可以这样:

1class Person {} 2 3Person.name = 'kevin';

因为上面提到的提案,现在可以写成:

1class Person { 2 static name = 'kevin'; 3}

对应到 ES5 都是:

1function Person() {}; 2 3Person.name = 'kevin';

new 调用

值得注意的是:类必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行。

1class Person {} 2 3Person(); // TypeError: Class constructor Foo cannot be invoked without 'new'

getter 和 setter

与 ES5 一样,在“类”的内部可以使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

1class Person { 2 get name() { 3 return 'kevin'; 4 } 5 set name(newName) { 6 console.log('new name 为:' + newName) 7 } 8} 9 10let person = new Person(); 11 12person.name = 'daisy'; 13// new name 为:daisy 14 15console.log(person.name); 16// kevin

对应到 ES5 中:

1function Person(name) {} 2 3Person.prototype = { 4 get name() { 5 return 'kevin'; 6 }, 7 set name(newName) { 8 console.log('new name 为:' + newName) 9 } 10} 11 12let person = new Person(); 13 14person.name = 'daisy'; 15// new name 为:daisy 16 17console.log(person.name); 18// kevin

Babel 编译

至此,我们已经知道了有关“类”的方法中,ES6 与 ES5 是如何对应的,实际上 Babel 在编译时并不会直接就转成这种形式,Babel 会自己生成一些辅助函数,帮助实现 ES6 的特性。

我们可以在 Babel 官网的 Try it out 页面查看 ES6 的代码编译成什么样子。

编译(一)

ES6 代码为:

1class Person { 2 constructor(name) { 3 this.name = name; 4 } 5}

Babel 编译为:

1"use strict"; 2 3function _classCallCheck(instance, Constructor) { 4 if (!(instance instanceof Constructor)) { 5 throw new TypeError("Cannot call a class as a function"); 6 } 7} 8 9var Person = function Person(name) { 10 _classCallCheck(this, Person); 11 12 this.name = name; 13};

_classCallCheck 的作用是检查 Person 是否是通过 new 的方式调用,在上面,我们也说过,类必须使用 new 调用,否则会报错。

当我们使用 var person = Person() 的形式调用的时候,this 指向 window,所以 instance instanceof Constructor 就会为 false,与 ES6 的要求一致。

编译(二)

ES6 代码为:

1class Person { 2 // 实例属性 3 foo = 'foo'; 4 // 静态属性 5 static bar = 'bar'; 6 7 constructor(name) { 8 this.name = name; 9 } 10}

Babel 编译为:

1'use strict'; 2 3function _classCallCheck(instance, Constructor) { 4 if (!(instance instanceof Constructor)) { 5 throw new TypeError("Cannot call a class as a function"); 6 } 7} 8 9var Person = function Person(name) { 10 _classCallCheck(this, Person); 11 12 this.foo = 'foo'; 13 14 this.name = name; 15}; 16 17Person.bar = 'bar';

编译(三)

ES6 代码为:

1class Person { 2 constructor(name) { 3 this.name = name; 4 } 5 6 sayHello() { 7 return 'hello, I am ' + this.name; 8 } 9 10 static onlySayHello() { 11 return 'hello' 12 } 13 14 get name() { 15 return 'kevin'; 16 } 17 18 set name(newName) { 19 console.log('new name 为:' + newName) 20 } 21}

对应到 ES5 的代码应该是:

1function Person(name) { 2 this.name = name; 3} 4 5Person.prototype = { 6 sayHello: function () { 7 return 'hello, I am ' + this.name; 8 }, 9 get name() { 10 return 'kevin'; 11 }, 12 set name(newName) { 13 console.log('new name 为:' + newName) 14 } 15} 16 17Person.onlySayHello = function () { 18 return 'hello' 19};

Babel 编译后为:

1'use strict'; 2 3var _createClass = function() { 4 function defineProperties(target, props) { 5 for (var i = 0; i < props.length; i++) { 6 var descriptor = props[i]; 7 descriptor.enumerable = descriptor.enumerable || false; 8 descriptor.configurable = true; 9 if ("value" in descriptor) descriptor.writable = true; 10 Object.defineProperty(target, descriptor.key, descriptor); 11 } 12 } 13 return function(Constructor, protoProps, staticProps) { 14 if (protoProps) defineProperties(Constructor.prototype, protoProps); 15 if (staticProps) defineProperties(Constructor, staticProps); 16 return Constructor; 17 }; 18}(); 19 20function _classCallCheck(instance, Constructor) { 21 if (!(instance instanceof Constructor)) { 22 throw new TypeError("Cannot call a class as a function"); 23 } 24} 25 26var Person = function() { 27 function Person(name) { 28 _classCallCheck(this, Person); 29 30 this.name = name; 31 } 32 33 _createClass(Person, [{ 34 key: 'sayHello', 35 value: function sayHello() { 36 return 'hello, I am ' + this.name; 37 } 38 }, { 39 key: 'name', 40 get: function get() { 41 return 'kevin'; 42 }, 43 set: function set(newName) { 44 console.log('new name 为:' + newName); 45 } 46 }], [{ 47 key: 'onlySayHello', 48 value: function onlySayHello() { 49 return 'hello'; 50 } 51 }]); 52 53 return Person; 54}();

我们可以看到 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 的模拟实现、模块加载方案、异步处理等内容。

原文链接

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

一分钟入门 Babel(下一代 JavaScript 语法的编译器)

简单来说把JavaScript中es2015/2016/2017/2046的新语法转化为es5,让低端运行环境(如浏览器和node)能够认识并执行。严格来说,babel也可以转化为更低的规范。但以目前情况来说,es5规范已经足以覆盖绝大部分浏览器,因此常规来说转到es5是一个安全且流行的做法。ES6转ES5(第一种)初始化项目npm 

你不可不知的JS面试题(第二期)

1、什么是继承?子类可以使用父类的所有功能,并且对功能进行扩展。新增方法改用方法(1)、ES6使用extends子类继承父类的方法。// 父类    class A        constructor(name)            this.name name;                getNa