• 从模块化到认识Babel


    转载自:https://www.cnblogs.com/qcloud1001/p/10167756.html

    https://blog.csdn.net/a250758092/article/details/78543440

    1.模块化

    模块化是指把一个复杂的系统分解到一个一个的模块。

    模块化开发的优点

    (1)代码复用,让我们更方便地进行代码管理、同时也便于后面代码的修改和维护。

    (2)一个单独的文件就是一个模块,是一个单独的作用域,只向外暴露特定的变量和函数。这样可以避免污染全局变量,减少变量命名冲突。

    js模块化规范有:CommonJS、AMD、CMD、ES6的模块系统。

    1.1 CommonJS 规范

    服务器端模块的规范,由nodejs推广使用。该规范的核心思想是:允许模块通过require方法来同步加载所要依赖的其他模块,然后通过 exports module.exports 来导出需要暴露的接口。

    // 导出
    module.exports = moduleA.someFunc;
    // 导入
    const moduleA = require('./moduleA');

    1.2 AMD

    AMD采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是require.js(还有个js库:curl.js)

    // 定义一个模块
    define('module', ['dep'], function (dep) {
      return exports;
    });
    
    // 导入和使用
    require(['module'], function (module) {
    });

    1.3 ES6模块化

    ES6在语言的层面上实现了模块化。浏览器厂商和 Node.js 都宣布要原生支持该规范。它将逐渐取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

    在 ES6 中,使用export关键字来导出模块,使用import关键字引用模块。但是浏览器还没有完全兼容,需要使用babel转换成浏览器支持的代码。正是由于Babel的存在,前端开发者才能能够不用考虑浏览器兼容性、畅快淋漓地使用最新的JavaScript语言特性。

    // 导出
    export function hello() { };
    export default {
      // ...
    };
    // 导入
    import { readFile } from 'fs';
    import React from 'react';

    使用import导入模块时,需要知道要加载的变量名或函数名。

    在ES6中还提供了export default,为模块指定默认输出。对应导入模块import时,不需要使用大括号。

    2. Babel

    在1.3中,我们提到了由于浏览器兼容问题,需要使用Babel将es6转成es5。官方给出的定义是,将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。从中,我们可以看出,Babel的主要功能是“翻译”。

    实际上babel转换后的代码是遵循commonJS规范的,而这个规范,浏览器(支持的是 ECMAScript)并不能识别。因此导入到浏览器中会报错,而nodeJS是commonJS的实现者,所以在babel转换后的代码是可以在node中运行的。

    为了将babel生成的遵循commonJS规范的es5写法能够在浏览器上直接运行,我们就借助webpack这个打包工具来完成。【概括一下:流程是 es6->es5(commonJS规范)->浏览器可执行代码】

    Babel中的Preset:即一组预先设定的插件(plugins)【嗯,所以它们之间是集合和元素的关系】可以使用官方提供的,也可以自己创建。通过babel.config.js文件中配置useBulidIns选项,可以只将我们需要的、目标浏览器中不支持的那些语法进行转义。

    2.1 Babel配置文件的选择

    之前版本的babel都是使用.baberc来做配置文件,babel7引入了babel.config.js。但是它并不是.baberc的替代品,二者根据使用的场景不同自行选择。

    .babelrc

    {
      "presets": ["@babel/preset-flow","@babel/preset-react", "@babel/preset-typescript"],
      "plugins": [...]
    }
    babel.config.js(新的)  env的参数配置https://babeljs.io/docs/en/babel-preset-env#options
    module.exports = function () {
      const presets = [ 
          ["env", {
                "targets": { //指定要转译到哪个环境
                    //浏览器环境
                    "browsers": ["last 2 versions", "safari >= 7"],
                    //node环境
                    "node": "6.10", //"current"  使用当前版本的node
                    
                },
                 //是否将ES6的模块化语法转译成其他类型
                 //参数:"amd" | "umd" | "systemjs" | "commonjs" | false,默认为'commonjs'
                "modules": 'commonjs',
                //是否进行debug操作,会在控制台打印出所有插件中的log,已经插件的版本
                "debug": false,
                //强制开启某些模块(包含在该Preset中的),默认为[]
                "include": ["transform-es2015-arrow-functions"],
                //禁用某些模块,默认为[]
                "exclude": ["transform-es2015-for-of"],
                //babel / preset-env处理polyfill的方式。
                //参数:usage | entry | false,默认为false.
                "useBuiltIns": false
         }]
     ];
    // 不包含在Preset中的Plugins需要单独引入 const plugins
    = [ "@babel/transform-arrow-functions" ]; return { presets, plugins }; }

     useBuiltIns的三个参数都是什么意思呢?

    • entry:在应用程序入口导入一次core-js,多次导入可能会有全局冲突或其他问题。

    • usage:自动为每个文件添加特定的该文件所用到的polyfill。

    • false:不要为每个文件自动添加polyfill,也不将“@ babel / polyfill”导入到单个polyfill。

    babel.config.js: 项目范围内的配置,放在根目录下。配置可用于node_modules文件夹。
    .babelrc:文件通常用于根目录下有多个package的项目,放在packages目录下;或者放在packages的子目录下,但需要在babel.config.js文件中进行配置
    babelrcRoots: [
      ".",
      "packages/*",
    ],

     2.2 需要安装的依赖

    • @babel/core:babel的核心包,核心的api都在这里。

    • @babel/cli :通过命令行运行babel.

    • @babel/polyfill:包含所有新的JS语法特征。相当于一个填充,因为babel本身只支持转换箭头函数、结构赋值这些语法糖类的语法,而Polyfill中包含了Promise函数等新的特征。【注意】babel/polyfill安装时是--save而不是--save-dev
    • @babel/preset-env:指定的一组babel plugins.

    当存在多个presets和多个plugins时的优先级:

     2.3 如果想从es6一键转浏览器可以直接运行的es5, 可以利用webpack(详见参考链接2)

    • 进入项目,并安装以下各个依赖
    - npm install --save webpack
    - npm install --save babel-loader
    - npm install --save babel-core
    - npm install --save babel-preset-es2015

    自从babel升级6.x版本后就分成了两个插件,一个是babel-core【终端运行】(如果是node请安装babel-cli ),一个是babel-preset-es2015

    安装完上述内容之后,需要设置一个.babelrc的文件放在根目录下,内容为

    {
    "presets": ["es2015"]
    }

    并且在webpack.config.js中配置babel-loader

    module.exports = {
        entry: "./js/main.js",
        output:{
            filename: 'bundle.js'
    
        },
        module: {
            loaders: [{
                test: /.js$/,
                loader: "babel-loader"
            }]
    
        }
    }

    配置完成后,就可以直接在JS文件中使用es6的语法,然后通过webpack命令打包生成即可。

    补充:为什么babel会使treeshaking失效?

    上文我们提到了babel的作用是将浏览器无法识别的较新的JS语法,编译从浏览器能够支持的JS语法。然而也是由于它的编译,一些我们原本看似没有副作用的代码,便转化为了(可能)有副作用的。比如我们用ES6语法定义了Person类

    export class Animal {
      constructor ({ breed, age, sex }) {
        this.breed = breed
        this.age = age
        this.sex = sex
      }
      getBreed () {
        return this.breed
      }
    }

    在经过babel编译后,得到:

    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    
    var _createClass = function() {
      function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
          var descriptor = props[i];
          descriptor.enumerable = descriptor.enumerable || !1, descriptor.configurable = !0,
          "value" in descriptor && (descriptor.writable = !0), Object.defineProperty(target, descriptor.key, descriptor);
        }
      }
      return function(Constructor, protoProps, staticProps) {
        return protoProps && defineProperties(Constructor.prototype, protoProps), staticProps && defineProperties(Constructor, staticProps),
        Constructor;
      };
    }()
    
    var Animal = function () {
      function Animal(_ref) {
        var breed = _ref.breed, age = _ref.age, sex = _ref.sex;
        _classCallCheck(this, Animal);
        this.breed = breed;
        this.age = age;
        this.sex = sex;
      }
    
      _createClass(Animal, [{
        key: 'getBreed',
        value: function getBreed() {
          return this.breed;
        }
      }]);
      return Animal;
    }();

    我们可以看到,在创建Animal的时候使用了_createClass函数,由此产生了副作用。

    按照我们常规的想法(我们之前写类的方式),我们希望的编译结果可能是这样的:

    var Animal = function () {
      function Animal() {
    
      }
      Animal.prototype.getBreed = function () { return this.breed };
      return Anaimal;
    }();

    那babel为什么要使用Object.defineProperty,而不是原型链的方式去编译呢?

    babel有一个loose模式的,直译的话叫做宽松模式。(不严格遵循ES6的语义,而采取更符合我们平常编写代码时的习惯去编译代码)。而在 .babelrc文件中,默认是这样的:

    // .babelrc
    {
      "presets": [["env", { "loose": false }]]
    }

    也就是说babel默认使用符合ES6真正的语义的语法进行编译。

    【这里解释一下,ES6语义需要注意的点:

    a. 类内部声明的方法,是不可枚举的,而通过原型链声明的方法是可以枚举的。

    b. for...of的循环是通过遍历器(Iterator)迭代的,并非i++.】

    所以,当我们开启loose模式后,即可消除babel编译带来的副作用。

     

  • 相关阅读:
    mouseOver与rollOver
    排序二叉树
    发展
    bitmapData
    回调与事件
    遍历舞台上所有对象
    面向对象原则
    面向对象的三大特征(个人理解)
    面向对象出现原因以及意义
    OC语言BLOCK和协议
  • 原文地址:https://www.cnblogs.com/ceceliahappycoding/p/11032103.html
Copyright © 2020-2023  润新知