zoukankan      html  css  js  c++  java
  • ES6 |Module语法

    在这里插入图片描述

    前言

    背景:在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

    引入:下面代码实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”**

    // CommonJS模块
    let { stat, exists, readFile } = require('fs');
    
    // 等同于
    let _fs = require('fs');
    let stat = _fs.stat;
    let exists = _fs.exists;
    let readfile = _fs.readfile;
    

    ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

    下面代码实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载。

    import { stat, exists, readFile } from 'fs';
    

    由于 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。

    严格模式

    ES6 的模块自动采用严格模式:

    • 变量必须声明后再使用
    • 函数的参数不能有同名属性,否则报错
    • 不能使用with语句
    • 不能对只读属性赋值,否则报错
    • 不能使用前缀0表示八进制数,否则报错
    • 不能删除不可删除的属性,否则报错
    • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
    • eval不会在它的外层作用域引入变量
    • evalarguments不能被重新赋值
    • arguments不会自动反映函数参数的变化
    • 不能使用arguments.callee
    • 不能使用arguments.caller
    • 禁止this指向全局对象
    • 不能使用fn.callerfn.arguments获取函数调用的堆栈
    • 增加了保留字(比如protectedstaticinterface

    export 命令

    一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。export命令用于规定模块的对外接口,如果你希望外部能够读取模块内部的某个东西,就必须使用export关键字输出:

    • export可以输出变量、函数和类

      //输出变量
      export var firstName = 'Michael';
      //输出函数
      export function myfun() {};
      //输出类
      export class myclass {};
      
    • export的第二种写法

      var firstName = 'Michael';
      function myfun() {};
      class myclass {};
      export {firstName, myfun, myclass};
      
    • export输出的接口名称可以使用as关键字重命名

      function v1() { ... }
      function v2() { ... }
      
      export {
        v1 as streamV1,
        v2 as streamV2,
      };
      
    • export命令规定的是对外的接口,而不是直接输出的数据或立即执行的命令

      // 报错
      export 1;
      
      // 报错
      var m = 1;
      export m;
      
      // 报错
      function f() {}
      export f;
      
      // 正确
      export var m = 1;
      
      // 正确
      var m = 1;
      export {m};
      
      // 正确
      export function f() {};
      
      // 正确
      function f() {}
      export {f};
      
    • export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值

    • export命令可以出现在模块的任何位置,只要处于模块顶层就可以

      function foo() {
        export default 'bar' // 非顶层,报错
      }
      foo()
      

    import 命令

    import命令用于输入其他模块提供的功能。使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

    • import命令从其他文件(或模块)中引入变量(函数或类)

    • import命令接受一对大括号,里面指定要从其他文件(或模块)导入的变量名(函数名或类名),名称必须与模块对外接口的名称相同

      import {firstName, lastName, year} from './profile';
      
    • import命令可以使用as关键字,给输入的变量(函数或类)重命名

      import { lastName as surname } from './profile';
      
    • import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js路径可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

      import {myMethod} from 'util'; //util是个模块,引入前提是有配置文件
      import 'lodash'; ////lodash是个模块,引入前提是有配置文件
      
    • import命令具有提升效果,会提升到整个模块的头部,首先执行

      foo();
      import { foo } from 'my_module'; //会提升到foo前面,不会报错
      

    模块的整体加载

    除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面

    //circle.js:输出两个方法area和circumference
    
    export function area(radius) {
      return Math.PI * radius * radius;
    }
    export function circumference(radius) {
      return 2 * Math.PI * radius;
    }
    
    //main.js:加载circle.js模块
    
    //1. 逐一加载
    import { area, circumference } from './circle';
    console.log('圆面积:' + area(4));
    console.log('圆周长:' + circumference(14));
    
    //2. 整体加载
    import * as circle from './circle';
    console.log('圆面积:' + circle.area(4));
    console.log('圆周长:' + circle.circumference(14));
    

    export default 命令

    • export default命令为模块指定默认输出

      // export-default.js:默认输出一个匿名函数
      export default function () {}
      
    • import加载该模块时,可以为该匿名函数指定任意名字。这时import命令不使用大括号

      // import-default.js:加载模块
      import customName from './export-default';
      
    • export default命令也可以输出非匿名函数

      export default function foo() {}
      //or
      function foo() {}
      export default foo;
      

      虽然有名称,但在模块外部是无效的。加载的时候,视同匿名函数加载

    • 一个模块只能有一个默认输出,因此export default命令只能使用一次

    • 本质上,export default输出的是一个叫做default的变量或方法,所以它后面不能跟变量声明语句

      // 正确
      export var a = 1;
      
      // 正确
      var a = 1;
      export default a; //意为将变量a的值赋给变量default
      
      // 错误
      export default var a = 1;
      
      // 正确
      export default 42;
      
      // 报错
      export 42;
      
    • import可以同时输入默认方法和其他变量

      import _, { each } from 'lodash'; //前面的_表示默认输出的方法
      

      与上面对应的export语句如下

      export default function (obj) {}
      export function each(obj, iterator, context) {}
      //暴露出forEach接口,默认指向each接口,即forEach和each指向同一个方法
      export { each as forEach };
      
    • export default可以用来输出类

      export default class { ... }
      

    export 与 import 的复合写法

    如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起

    import { foo, bar } from 'my_module';
    export { foo, bar };
    //可以写成:
    export { foo, bar } from 'my_module';
    
    // 整体输出
    export * from 'my_module';
    

    跨模块常量

    const声明的常量只在当前代码块有效。如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法:

    // constants.js 模块
    export const A = 1;
    export const B = 3;
    export const C = 4;
    
    // test1.js 模块
    import * as constants from './constants';
    console.log(constants.A); // 1
    console.log(constants.B); // 3
    
    // test2.js 模块
    import {A, B} from './constants';
    console.log(A); // 1
    console.log(B); // 3
    

    如果要使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下

    // constants/db.js
    export const db = {
      url: 'http://my.couchdbserver.local:5984',
      admin_username: 'admin',
      admin_password: 'admin password'
    };
    
    // constants/user.js
    export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];
    

    然后,将这些文件输出的常量,合并在index.js里面

    // constants/index.js
    export {db} from './db';
    export {users} from './users';
    

    使用的时候,直接加载index.js就可以了

    // script.js
    import {db, users} from './constants';
    

    import()

    引入问题

    import命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行。所以下面代码会报错:

    if (x === 2) {
      import MyModual from './myModual';
    }
    //引擎处理import语句是在编译时,这时不会去分析或执行if语句。所以import语句放在if代码块之中毫无意义
    

    importexport命令只能在模块的顶层,不能在代码块之中。这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。从语法上,条件加载就不可能实现。

    解决

    import()函数,实现运行时加载模块,即动态加载

    import(specifier) //参数specifier,指定所要加载的模块的位置
    

    import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载

    import()返回一个 Promise 对象的例子

    const main = document.querySelector('main');
    
    import(`./section-modules/${someVariable}.js`)
      .then(module => {
        module.loadPageInto(main);
      })
      .catch(err => {
        main.textContent = err.message;
      });
    

    import()是运行时执行,也就是说,什么时候运行到这一句,也会加载指定的模块。import()类似于 Node 的require方法,区别主要是前者是异步加载,后者是同步加载。

    适用场合

    • 按需加载

      button.addEventListener('click', event => {
        import('./dialogBox.js')
        .then(dialogBox => {
          dialogBox.open();
        })
        .catch(error => {
          /* Error handling */
        })
      });
      //import()方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块
      
    • 条件加载

      if (condition) {
        import('moduleA').then(...);
      } else {
        import('moduleB').then(...);
      }
      
    • 动态的模块路径

      import(f())
      .then(...);
      //根据函数f的返回结果,加载不同的模块
      

    注意

    import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

    import('./myModule.js')
    .then(({export1, export2}) => {
      // ...·
    });
    //export1和export2都是myModule.js的输出接口,可以解构获得
    

    如果模块有default输出接口,可以用参数直接获得。

    import('./myModule.js')
    .then(myModule => {
      console.log(myModule.default);
    });
    

    如果想同时加载多个模块,可以采用下面的写法

    Promise.all([
      import('./module1.js'),
      import('./module2.js'),
      import('./module3.js'),
    ])
    .then(([module1, module2, module3]) => {
       ···
    });
    

    import()也可以用在 async 函数之中

    async function main() {
      const myModule = await import('./myModule.js');
      const {export1, export2} = await import('./myModule.js');
      const [module1, module2, module3] =
        await Promise.all([
          import('./module1.js'),
          import('./module2.js'),
          import('./module3.js'),
        ]);
    }
    main();
    
  • 相关阅读:
    寒假13
    寒假作业十二
    寒假12
    寒假作业十一
    寒假11
    寒假作业九
    寒假10
    寒假作业九
    寒假9
    寒假作业八
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/13595825.html
Copyright © 2011-2022 走看看