Jamey's Jamey's
首页
导航站
  • 学习专栏

    • 《HTML》笔记
    • 《CSS》笔记
    • 《JavaScript》笔记
    • 《Vue》笔记
    • 《Git》笔记
    • 《规范》笔记
    • 《软技能》笔记
    • 《面试》笔记
    • 《持续集成&交付&部署》笔记
  • 踩坑专栏

    • 《Element-UI 实践系列》笔记
    • 《移动端 实践系列》笔记
    • 《综合》笔记
  • 配置专栏

    • 《环境系列》笔记
  • 极空间

    • Docker
  • 影视

    • movie
  • 编辑器笔记

    • 开发编辑器
  • 浏览器笔记

    • Chrome
  • Mac笔记

    • Mac
  • 跨界学习

    • 运营
  • 破解合集

    • 破解
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于

Jamey

首页
导航站
  • 学习专栏

    • 《HTML》笔记
    • 《CSS》笔记
    • 《JavaScript》笔记
    • 《Vue》笔记
    • 《Git》笔记
    • 《规范》笔记
    • 《软技能》笔记
    • 《面试》笔记
    • 《持续集成&交付&部署》笔记
  • 踩坑专栏

    • 《Element-UI 实践系列》笔记
    • 《移动端 实践系列》笔记
    • 《综合》笔记
  • 配置专栏

    • 《环境系列》笔记
  • 极空间

    • Docker
  • 影视

    • movie
  • 编辑器笔记

    • 开发编辑器
  • 浏览器笔记

    • Chrome
  • Mac笔记

    • Mac
  • 跨界学习

    • 运营
  • 破解合集

    • 破解
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于
  • 深入系列

  • 专题系列

  • underscore系列

  • ES6系列

    • ES6 系列之 let 和 const
    • ES6 系列之模板字符串
    • ES6 系列之箭头函数
    • ES6 系列之模拟实现 Symbol 类型
    • ES6 系列之迭代器与 for of
    • ES6 系列之模拟实现一个 Set 数据结构
    • ES6 系列之 WeakMap
    • ES6 系列之我们来聊聊 Promise
    • ES6 系列之 Generator 的自动执行
    • ES6 系列之我们来聊聊 Async
    • ES6 系列之异步处理实战
    • ES6 系列之 Babel 将 Generator 编译成了什么样子
    • ES6 系列之 Babel 将 Async 编译成了什么样子
    • ES6 系列之 Babel 是如何编译 Class 的(上)
      • 📖. 前言
      • 一. constructor
      • 二. 实例属性
      • 三. 静态方法
      • 四. 静态属性
      • 五. new 调用
      • 六. getter 和 setter
      • 七. Babel 编译
        • 1. 编译(一)
        • 2. 编译(二)
        • 3. 编译(三)
      • 八. 写在后面
    • ES6 系列之 Babel 是如何编译 Class 的(下)
    • ES6 系列之 defineProperty 与 proxy
    • ES6 系列之模块加载方案
    • ES6 系列之我们来聊聊装饰器
    • ES6 系列之私有变量的实现
    • ES6 完全使用手册
  • 模块化

  • 正则表达式

  • 单元测试

  • 微前端

  • 实用函数

  • Rollup

  • 解决方案

  • 《JavaScript》笔记
  • ES6系列
Jamey
2021-12-02
目录

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

# 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
1
2
3
4
5
6
7
8
9
10
11
12

对应到 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
1
2
3
4
5
6
7
8
9
10

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

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

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

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

然而在 ES5 中:

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

# 二. 实例属性

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

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

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

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

对应到 ES5 都是:

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

# 三. 静态方法

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

ES6 中:

class Person {
    static sayHello() {
        return 'hello';
    }
}

Person.sayHello() // 'hello'

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

对应 ES5:

function Person() {}

Person.sayHello = function() {
    return 'hello';
};

Person.sayHello(); // 'hello'

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

# 四. 静态属性

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

class Person {}

Person.name = 'kevin';
1
2
3

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

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

对应到 ES5 都是:

function Person() {};

Person.name = 'kevin';
1
2
3

# 五. new 调用

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

class Person {}

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

# 六. 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

对应到 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 七. Babel 编译

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

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

# 1. 编译(一)

ES6 代码为:

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

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;
};
1
2
3
4
5
6
7
8
9
10
11
12
13

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

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

# 2. 编译(二)

ES6 代码为:

class Person {
    // 实例属性
    foo = 'foo';
    // 静态属性
    static bar = 'bar';

    constructor(name) {
        this.name = name;
    }
}
1
2
3
4
5
6
7
8
9
10

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';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3. 编译(三)

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)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

对应到 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'
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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;
}();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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 的(下)》

#JavaScript ES6
上次更新: 2022/07/01, 17:34:19
ES6 系列之 Babel 将 Async 编译成了什么样子
ES6 系列之 Babel 是如何编译 Class 的(下)

← ES6 系列之 Babel 将 Async 编译成了什么样子 ES6 系列之 Babel 是如何编译 Class 的(下)→

Theme by Vdoing | Copyright © 2017-2023 Jamey | blog 闽ICP备19022664号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式