zoukankan      html  css  js  c++  java
  • ES6 |Module 的加载实现

    在这里插入图片描述

    浏览器加载

    传统方法

    <!-- 页面内嵌的脚本 -->
    <script type="application/javascript">
      // module code
    </script>
    
    <!-- 外部脚本 -->
    <script type="application/javascript" src="path/to/myModule.js">
    </script>
    
    • 由于浏览器脚本的默认语言是 JavaScript,因此type="application/javascript"可以省略
    • 默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。
    • 如果脚本体积很大,下载和执行的时间就会很长,因此成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。

    异步加载

    <script src="path/to/myModule.js" defer></script>
    <script src="path/to/myModule.js" async></script>
    
    • <script>标签打开defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。
    • deferasync的区别是:前者要等到整个页面正常渲染结束,才会执行;后者一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的

    ES6模块加载规则

    <script type="module" src="foo.js"></script>
    
    • 浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。

    • 异步加载,即等到整个页面渲染完,再执行模块脚本,等同于默认打开了<script>标签的defer属性

    • <script>标签的async属性也可以打开

    • ES6 模块也允许内嵌在网页中,语法行为与加载外部脚本完全一致

        <script type="module">
          import utils from "./utils.js";
        </script>
        ```
      
      

    注意事项

    import utils from 'https://example.com/js/utils.js';
    const x = 1;
    console.log(x === window.x); //false
    console.log(this === undefined); // true
    delete x; // 句法错误,严格模式禁止删除变量
    
    • 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见
    • 模块之中,顶层的this关键字返回undefined,而不是指向window。也就是说,在模块顶层使用this关键字,是无意义的

    ES6 模块与 CommonJS 模块的差异

    两大差异

    • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
    • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

    第一个差异

    • CommonJS 模块输出的是值的拷贝,即一旦输出一个值,模块内部的变化就影响不到这个值。

      // 模块文件——lib.js
      var counter = 3;
      function incCounter() {counter++}
      module.exports = {
        counter: counter,
        incCounter: incCounter,
      };
      
      // 加载模块——main.js
      var mod = require('./lib');
      console.log(mod.counter);  // 3
      mod.incCounter();	
      //执行了函数counter也不会改变
      //这是因为counter是一个原始类型的值,会被缓存。
      console.log(mod.counter); // 3
      
      //一种办法就是把counter写成函数
      get counter() {
        return counter
      },
      
    • ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

      // lib.js
      export let counter = 3;
      export function incCounter() {
        counter++;
      }
      
      // main.js
      import { counter, incCounter } from './lib';
      console.log(counter); // 3
      incCounter();
      console.log(counter); // 4
      

      由于 ES6 输入的模块变量,只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错

      // lib.js
      export let obj = {};
      
      // main.js
      import { obj } from './lib';	
      obj.prop = 123; // //变量obj指向的地址是只读的,不能重新赋值
      obj = {}; // TypeError
      

    第二个差异

    CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成

    Node 加载

    Node 对 ES6 模块的处理比较麻烦,因为它有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。解决方案是:将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。

    在静态分析阶段,一个模块脚本只要有一行importexport语句,Node 就会认为该脚本为 ES6 模块,否则就为 CommonJS 模块。

    如果不输出任何接口,但是希望被 Node 认为是 ES6 模块,可以写成如下代码。这不是输出一个空对象,而是不输出任何接口的 ES6 标准写法

    export {};
    

    如何不指定绝对路径,Node 加载 ES6 模块会依次寻找以下脚本,与require()的规则一致

    import 'baz';
    // 依次寻找
    //   ./node_modules/baz.js
    //   ./node_modules/baz/package.json
    //   ./node_modules/baz/index.js
    // 寻找上一级目录
    //   ../node_modules/baz.js
    //   ../node_modules/baz/package.json
    //   ../node_modules/baz/index.js
    // 再上一级目录
    

    import 命令加载 CommonJS 模块

    Node 采用 CommonJS 模块格式,模块的输出都定义在module.exports这个属性上面。

    在 Node 环境中,Node 会自动将module.exports属性,当作模块的默认输出,即等同于export default,使用import命令加载该 CommonJS 模块时,module.exports会被视为默认输出

    // CommonJS 模块
    // a.js
    module.exports = {
      foo: 'hello',
      bar: 'world'
    };
    
    // 在node环境中,自动当作模块的默认输出,等同于
    export default {
      foo: 'hello',
      bar: 'world'
    };
    

    如果采用整体输入的写法(import * as xxx from someModule),default会取代module.exports,作为输入的接口

    // c.js
    module.exports = function two() {
      return 2;
    };
    
    // es.js
    import foo from './c';
    foo(); // 2
    
    import * as bar from './c';
    bar.default(); // 2
    bar(); // throws, bar is not a function
    //bar本身是一个对象,不能当作函数调用,只能通过bar.default调用
    

    CommonJS 模块的输出缓存机制,在 ES6 加载方式下依然有效

    // foo.js
    module.exports = 123;
    setTimeout(_ => module.exports = null); //一直都会是123
    

    由于 ES6 模块是编译时确定输出接口,CommonJS 模块是运行时确定输出接口,所以采用import命令加载 CommonJS 模块时,不允许采用下面的写法。

    import {readfile} from 'fs';
    //因为fs是 CommonJS 格式,只有在运行时才能确定readfile接口,而import命令要求编译时就确定这个接口
    

    解决方法就是改为整体输入

    import * as express from 'express';
    const app = express.default();
    
    import express from 'express';
    const app = express();
    

    require 命令加载 ES6 模块

    采用require命令加载 ES6 模块时,ES6 模块的所有输出接口,会成为输入对象的属性。

    // es.js
    let foo = {bar:'my-default'};
    export default foo;
    foo = null;
    
    // cjs.js
    const es_namespace = require('./es');
    console.log(es_namespace.default);
    // {bar:'my-default'}
    //default接口变成了es_namespace.default属性。另外,由于存在缓存机制,es.js对foo的重新赋值没有在模块外部反映出来。
    

    ES6模块的转码

    浏览器目前还不支持ES6模块,为了现在就能使用,可以将转为ES5的写法。除了Babel可以用来转码之外,还有以下两个方法,也可以用来转码。

    ES6 module transpiler

    ES6 module transpiler是 square 公司开源的一个转码器,可以将 ES6 模块转为 CommonJS 模块或 AMD 模块的写法,从而在浏览器中使用

    首先,安装这个转码器。

    $ npm install -g es6-module-transpiler
    

    然后,使用compile-modules convert命令,将 ES6 模块文件转码。

    $ compile-modules convert file1.js file2.js
    

    -o参数可以指定转码后的文件名

    $ compile-modules convert -o out.js file1.js
    

    SystemJS

    另一种解决方法是使用 SystemJS。它是一个垫片库(polyfill),可以在浏览器内加载 ES6 模块、AMD 模块和 CommonJS 模块,将其转为 ES5 格式。它在后台调用的是 Google 的 Traceur 转码器。

    使用时,先在网页内载入system.js文件。

    <script src="system.js"></script>
    

    然后,使用System.import方法加载模块文件。

    <script>
      System.import('./app.js');
    </script>
    

    上面代码中的./app,指的是当前目录下的app.js文件。它可以是ES6模块文件,System.import会自动将其转码。

    需要注意的是,System.import使用异步加载,返回一个 Promise 对象,可以针对这个对象编程。下面是一个模块文件。

    // app/es6-file.js:
    
    export class q {
      constructor() {
        this.es6 = 'hello';
      }
    }
    

    然后,在网页内加载这个模块文件。

    <script>
    System.import('app/es6-file').then(function(m) {
      console.log(new m.q().es6); // hello
    });
    </script>
    

    ESLint的使用

    ESLint是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。

    首先,安装ESLint。

    $ npm i -g eslint
    

    然后,安装Airbnb语法规则。

    $ npm i -g eslint-config-airbnb
    

    最后,在项目的根目录下新建一个.eslintrc文件,配置ESLint。

    {
      "extends": "eslint-config-airbnb"
    }
    

    现在就可以检查,当前项目的代码是否符合预设的规则。

    index.js文件的代码如下。

    var unusued = 'I have no purpose!';
    
    function greet() {
        var message = 'Hello, World!';
        alert(message);
    }
    
    greet();
    

    使用ESLint检查这个文件。

    $ eslint index.js
    index.js
      1:5  error  unusued is defined but never used                 no-unused-vars
      4:5  error  Expected indentation of 2 characters but found 4  indent
      5:5  error  Expected indentation of 2 characters but found 4  indent
    
    ✖ 3 problems (3 errors, 0 warnings)
    

    上面代码说明,原文件有三个错误,一个是定义了变量,却没有使用,另外两个是行首缩进为4个空格,而不是规定的2个空格。

  • 相关阅读:
    js字符串空格和换行
    python resources
    -eous
    英语资源网站
    -iatry 没病走两步
    book corpus
    epub converters
    brainstorm detain
    craftsman
    parachute
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/13595834.html
Copyright © 2011-2022 走看看