揭秘babel的魔法之class魔法处理

标签: javascript babel es6 ecmascript es5 | 发表时间:2017-01-13 20:48 | 作者:lucas_580e331d326b4
出处:https://segmentfault.com/blogs

2017年,很多人已经开始接触ES6环境,并且早已经用在了生产当中。我们知道ES6在大部分浏览器还是跑不通的,因此我们使用了伟大的Babel来进行编译。很多人可能没有关心过,经过Babel编译之后,我们华丽的ES6代码究竟变成了什么样子?

这篇文章,针对Babel对ES6里面“类class”的编译进行分析,你可以在线 测试编译结果,毕竟纸上得来终觉浅,自己动手,才能真正体会其中的奥秘。

另外,如果你还不明白JS中原型链等OOP相关知识,建议出门左转找到经典的《JS高级程序设计》来补课;如果你对JS中,通过原型链来实现继承一直云里雾里,安利一下我的同事,前端著名网红 颜海镜大大早在2014年的文章

为什么使用选择Babel

Babel:The compiler for writing next generation JavaScript;
我们知道,现在大部分浏览器或者类似NodeJS的javascript引擎还不能直接支持ES6语法。但这并不构成障碍,比如Babel的出现,使得我们在生产环境中书写ES6代码成为了现实,它工作原理是编译ES6的新特性为老版本的ES5,从而得到宿主环境的支持。

Class例子

在这篇文章中,我会讲解Babel如何处理ES6新特性:Class,这其实是一系列语法糖的实现。

Old school方式实现继承

在探究ES6之前,我们先来回顾一下ES5环境下,我们如何实现类的继承:

  // Person是一个构造器
function Person(name) {
    this.type = 'Person';
    this.name = name;
}

// 我们可以通过prototype的方式来加一条实例方法
Person.prototype.hello = function() {
    console.log('hello ' + this.name);
}

// 对于私有属性(Static method),我们当然不能放在原型链上了。我们可以直接放在构造函数上面
Person.fn = function() {
    console.log('static');
};

我们可以这么应用:

  var julien = new Person('julien');
var darul = new Person('darul');
julien.hello(); // 'hello julien'
darul.hello(); // 'hello darul'
Person.fn(); // 'static'

// 这样会报错,因为fn是一个私有属性
julien.fn(); //Uncaught TypeError: julien.fn is not a function

New school方式(ES6)实现继承

在ES6环境下,我们当然迫不及待地试一试Class:

  class Person {
    constructor(name) {
        this.name = name;
        this.type="person"
    }
    hello() {
        console.log('hello ' + this.name);
    }
    static fn() {
        console.log('static');
    };
}

这样写起来当然很cool,但是经过Babel编译,我们的代码是什么样呢?

Babel transformation

我们一步一步来看,

Step1: 定义
我们从最简单开始,试试不加任何方法和属性的情况下,

  Class Person{}

被编译为:

  function _classCallCheck(instance, Constructor) {
    // 检查是否成功创建了一个对象
    if (!(instance instanceof Constructor)) {  
        throw new TypeError("Cannot call a class as a function"); 
    } 
}

var Person = function Person() {
    _classCallCheck(this, Person);
};

你可能会一头雾水,_classCallCheck是什么?其实很简单,它是为了保证调用的安全性。
比如我们这么调用:

  // ok
new p = new Person();

是没有问题的,但是直接调用:

  
// Uncaught TypeError: Cannot call a class as a function
Person();

就会报错,这就是_classCallCheck所起的作用。具体原理自己看代码就好了,很好理解。

我们发现,Class关键字会被编译成构造函数,于是我们便可以通过new来实现实例的生成。

Step2:Constructor探秘
我们这次尝试加入constructor,再来看看编译结果:

  class Person() {
    constructor(name) {  
        this.name = name;
        this.type = 'person'
    }
}

编译结果:

  var Person = function Person(name) {
    _classCallCheck(this, Person);
    this.type = 'person';
    this.name = name;
};

看上去棒极了,我们继续探索。

Step3:增加方法
我们尝试给Person类添加一个方法:hello:

  class Person {
    constructor(name) {
        this.name = name;
        this.type = 'person'
    }

    hello() {
        console.log('hello ' + this.name);
    }
}

编译结果(已做适当省略):

  // 如上,已经解释过
function _classCallCheck.... 

// MAIN FUNCTION
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; 
    }; 
})();

var Person = (function () {
    function Person(name) {
        _classCallCheck(this, Person);

        this.name = name;
    }

    _createClass(Person, [{
        key: 'hello',
        value: function hello() {
            console.log('hello ' + this.name);
        }
    }]);

    return Person;
})();

Oh...no,看上去有很多需要消化!不要急,我尝试先把他精简一下,并加上注释,你就会明白核心思路:

  var _createClass = (function () {   
    function defineProperties(target, props) { 
        // 对于每一个定义的属性props,都要完全拷贝它的descriptor,并扩展到target上
    }  
    return defineProperties(Constructor.prototype, protoProps);    
})();

var Person = (function () {
    function Person(name) { // 同之前... }

    _createClass(Person, [{
        key: 'hello',
        value: function hello() {
            console.log('hello ' + this.name);
        }
    }]);

    return Person;
})();

如果你不明白defineProperty方法, 请参考这里

现在,我们知道我们添加的方法:

  hello() {
    console.log('hello ' + this.name);
}

被编译为:

  _createClass(
    Person, [{
    key: 'hello',
    value: function hello() {
        console.log('hello ' + this.name);
    }
}]);

而_createClass接受2个-3个参数,分别表示:

  参数1 => 我们要扩展属性的目标对象,这里其实就是我们的Person
参数2 => 需要在目标对象原型链上添加的属性,这是一个数组
参数3 => 需要在目标对象上添加的属性,这是一个数组

这样,Babel的魔法就一步一步被揭穿了。

总结

希望这篇文章能够让你了解到Babel是如何初步把我们ES6 Class语法编译成ES5的。下一篇文章我会继续介绍Babel如何处理子类的Super(), 并会通过一段函数桥梁,使得ES5环境下也能够继承ES6定义的Class。

相关 [揭秘 babel 魔法] 推荐:

揭秘babel的魔法之class魔法处理

- - SegmentFault 最新的文章
2017年,很多人已经开始接触ES6环境,并且早已经用在了生产当中. 我们知道ES6在大部分浏览器还是跑不通的,因此我们使用了伟大的Babel来进行编译. 很多人可能没有关心过,经过Babel编译之后,我们华丽的ES6代码究竟变成了什么样子. 这篇文章,针对Babel对ES6里面“类class”的编译进行分析,你可以在线 测试编译结果,毕竟纸上得来终觉浅,自己动手,才能真正体会其中的奥秘.

Flask-Babel 简介

- yinseny - python.cn(jobs, news)
本文有一个格式好看一点,并且有语法高亮的版本放在 readthedocs,欢迎浏览. 本文是原创,不是翻译,不过本文其实是谈翻译的. 话说用 wordpress 的 WYSIWYG 编辑器写这样的文章真痛苦啊,格式一不小心就乱了,本文是用 rst 写成,编译为 html,然后贴到这边来的. 最近用 Flask 给公司做了个小 web 应用,做的时候用英文了,现在要求翻译成中文.

给我一个 babel,还你一条完整前端工具链

- - 掘金 前端
提到 babel,你会想到什么. 可以把项目中的 es6、es7 等代码转成目标环境支持的代码. 可以自动 polyfill 目标环境不支持的 api. taro (小程序转译工具)是基于 babel 实现的. 我们公司现在用 babel 来编译 typescript,不用 tsc 了. 我基于 babel 做过自动埋点的功能,得到了领导的夸奖.

Electron架构揭秘

- - 掘金前端
本文已收录在前端食堂同名仓库Github. github.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个 Star 对食堂老板来说是莫大的鼓励. 昨晚搬砖回家看到 Peter 发了条朋友圈,腾讯云游戏平台 START 公测发布,他在用 MAC 打 LOL. 我紧随其后体验了一波,毕竟 LOL 是我们这代人的青春,工作后很少有时间玩,用上 MAC 后,之前的游戏本也放在箱底很久了.

魔法之吻

- 狐狸糊涂 - 科学松鼠会
原作:Sheharzad Arshad.

雷军:小米揭秘

- Leo - 《商业价值》杂志
关于小米这家神秘公司要做什么和怎么去做的大起底. 作为创业公司,它竟然有7个联合创始人,来自微软、谷歌、摩托罗拉等5个不同的地方. 这些公司所做的事、做事的风格、流程,甚至气质都大为不同. 表面看来,光是“多文化”的融合,就够这家新生公司头疼了. 作为一家成立才1年多的创业公司,小米并不算知名企业,但它招聘的标准比微软都高.

牙膏成分大揭秘

- 见涛 - 果壳网 guokr.com - 果壳网
古医书《外台秘要》曾说,用杨枝将一头咬软,蘸了药物揩牙,可使牙“香而光洁”, 咀嚼嫩树枝用以洁牙的效果似乎也不错,李时珍也说,用嫩柳枝“削为牙枝,涤齿甚妙”. 在还没发明牙膏和牙刷的古代,中国人用树枝、盐、药物等工具来清洁牙齿,希望能达到清洁、消炎抑菌,并带来些微清香的效果. 它是碳酸钙和肥皂粉的混合物,其功能是保持牙齿清洁,除却污渍.

Google索引诀窍揭秘

- - Google China Blog
发表者: Kristen Dwan, Victoria Shan, Javier Tordable,网站管理员工具团队. 原文: Behold Google index secrets, revealed. 转载自: 谷歌中文站长管理员博客. 发布时间:2012年8月8日 下午 03:09:00. 自从Googlebot问世以来,世界各地的网站站长们一直在问这样一个问题:Google,我的网页被索引了吗.

MySQL DBA面试全揭秘

- - OurMySQL
本文起源于有同学留言回复说想了解下MySQL DBA面试时可能涉及到的知识要点,那我们今天就来大概谈谈吧. MySQL DBA职位最近几年特别热门,不少朋友让我帮忙推荐什么的,也有很多公司找不到合适的DBA. 原因很简单,优秀的人才要么被大公司圈起来了,要么被创业公司高薪挖走,如果你既不是大公司,又不能出得起高价钱的土豪公司,想要找到优秀人才的几率堪比买彩票中奖的概率,哈哈.

Facebook新数据中心揭秘

- T.C - cnBeta全文版
Facebook刚刚在俄勒冈州Prineville新建了一座数据中心,号称是全球能效最高的数据中心. 硅谷知名博客作者Scoble有幸受到参观邀请,并拍摄了一些照片. 下面我们就一起去看看Facebook数据中心到底长什么样. 下面这张照片的建筑物体积很大,前面有很多太阳能电池板,连在一起有三个沃尔玛那么大.