site infoHacknerd | Tech Blog
blog cover

🧳 [ECMAScript6]24.Module

ES6JavaScript

概述

历史上Javascript一直没有模块体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。这对开发大型的、复杂的项目形成了巨大障碍。

在ES6 之前社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

javascriptCopy
// CommonJS模块
let { stat, exists, readfile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

好处:

  • 不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
  • 将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
  • 不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
  • 严格模式

    ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

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

    javascriptCopy
    export default function() {...} // 默认导出
    
    function add(x, y) {
      return x * y;
    }
    export {add as default}; // 等同于 export default

    Import

    javascriptCopy
    import { lastName as surname } from './profile.js'; // 为输入的变量重新取一个名字
    
    import * from './profile.js' // 加载整体模块
    
    import _, { each, forEach } from 'lodash'; // 同时输入默认方法和其他接口

    注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。

    javascriptCopy
    foo();
    
    import { foo } from 'my_module';

    import命令是编译阶段执行的,在代码运行之前。 由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

    javascriptCopy
    // 报错
    import { 'f' + 'oo' } from 'my_module';
    
    // 报错
    let module = 'my_module';
    import { foo } from module;
    
    // 报错
    if (x === 1) {
      import { foo } from 'module1';
    } else {
      import { foo } from 'module2';
    }

    Export import 复合写法

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

    javascriptCopy
    export { foo, bar } from 'my_module';
    
    // 可以简单理解为
    import { foo, bar } from 'my_module';
    export { foo, bar };
    
    
    /*---- 导出默认接口 ---- */
    export { default } from 'foo';
    
    
    /*---- 具名接口改为默认接口的写法如下。 ---- */
    export { es6 as default } from './someModule';
    // 等同于
    import { es6 } from './someModule';
    export default es6;

    import()

    import 是编译时加载,这样固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。 ES2020提案 引入import()函数,支持动态加载模块。

    javascriptCopy
    const main = document.querySelector('main');
    
    // 返回的是一个promise对象
    import(`./section-modules/${someVariable}.js`)
      .then(module => {
        module.loadPageInto(main);
      })
      .catch(err => {
        main.textContent = err.message;
      });

    import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。import()类似于 Node.js 的require()方法,区别主要是前者是异步加载,后者是同步加载。

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

    适用场景

  • 1.按需加载。import()可以在需要的时候,再加载某个模块。
  • javascriptCopy
    button.addEventListener('click', event => {
      // 只有用户点击了按钮,才会加载这个模块。
      import('./dialogBox.js')
      .then(dialogBox => {
        dialogBox.open();
      })
      .catch(error => {
        /* Error handling */
      })
    });

  • 1.条件加载import()可以放在if代码块,根据不同的情况,加载不同的模块。
  • javascriptCopy
    if (condition) {
      import('moduleA').then(...);
    } else {
      import('moduleB').then(...);
    }

  • 1.动态的模块路径import()允许模块路径动态生成。
  • javascriptCopy
    import(f())
    .then(...);

    import.meta

    开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。ES2020 为 import 命令添加了一个元属性import.meta,返回当前模块的元信息。

    import.meta只能在模块内部使用,如果在模块外部使用会报错。

  • 1.import.meta.url
  • import.meta.url返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是https://foo.com/main.js,import.meta.url就返回这个路径。如果模块里面还有一个数据文件data.txt,那么就可以用下面的代码,获取这个数据文件的路径。 2. import.meta.scriptElement

    import.meta.scriptElement是浏览器特有的元属性,返回加载模块的那个<script>元素,相当于document.currentScript属性。

    javascriptCopy
    // HTML 代码为
    // <script type="module" src="my-module.js" data-foo="abc"></script>
    
    // my-module.js 内部执行下面的代码
    import.meta.scriptElement.dataset.foo
    // "abc"

    Contents

    • 概述
    • 严格模式
    • Export、
    • Import
    • Export import 复合写法
    • import()
    • import.meta

    2024/03/24 02:15