[查遗补漏] | JavaScript 模块

一、模块化发展历程

背景:早期的Javascript程序很小,它们大多被用来执行独立的脚本任务,在你的 web 页面需要的地方提供一定交互,所以一般不需要多大的脚本。过了几年,我们现在有了运行大量 Javascript 脚本的复杂程序,还有一些被用在其他环境(例如 Node.js)。

因此,近年来,有必要开始考虑提供一种将 JavaScript 程序拆分为可按需导入的单独模块的机制。Node.js 已经提供这个能力很长时间了,还有很多的 Javascript 库和框架 已经开始了模块的使用(例如, CommonJS 和基于 AMD 的其他模块系统 如 RequireJS, 以及最新的 Webpack 和 Babel)。

  1. IIFE
  • 使用自执行函数来编写模块化,特点:在一个单独的函数作用域中执行代码,避免变量冲突。
(function(){
  return {
	data:[]
  }
})()
复制代码
  1. AMD
  • 最古老的模块系统之一,最初由 require.js 库实现。
  • 特点:依赖必须提前声明好。
define('./index.js',function(code){
 // code 就是index.js 返回的内容
})
复制代码
  1. CMD
  • 使用seaJS 来编写模块化,特点:支持动态引入依赖文件。
define(function(require, exports, module) {  
  var indexCode = require('./index.js');
});
复制代码
  1. CommonJS
  • 为 Node.js 服务器创建的模块系统。
var fs = require('fs');
复制代码
  1. UMD
  • 兼容AMD,CommonJS 模块化语法。
  1. Webpack/Babel
  2. ES Moduls
  • ES6 引入的模块化,支持import 来引入另一个 js 。

二、模块的核心概念

这本书里写的很详细,具体参见:模块简介,下面做些总结:

  1. 一个模块就是一个文件。浏览器需要使用<script type="module">以使 import/export可以工作。模块相较于常规脚本有几点差别:
    • 默认是延迟解析的(deferred)
    <script type="module">
      alert(typeof button); // object:脚本可以“看见”下面的 button
      // 因为模块是被延迟的(deferred,所以模块脚本会在整个页面加载完成后才运行
    </script>
    
    <script>
      alert(typeof button); // button 为 undefined,脚本看不到下面的元素
      // 常规脚本会立即运行,常规脚本的运行是在在处理页面的其余部分之前进行的
    </script>
    
    <button id="button">Button</button>
    //请注意:上面的第二个脚本实际上要先于前一个脚本运行!所以我们会先看到 undefined,然后才是 object。
    复制代码
    • Async 可用于内联脚本。
    <!-- 所有依赖都获取完成(analytics.js)然后脚本开始运行 -->
    <!-- 不会等待 HTML 文档或者其他 <script> 标签 -->
    <script async type="module">
      import {counter} from './analytics.js';
    
      counter.count();
    </script>
    复制代码
    • 要从另一个源(域/协议/端口)加载外部脚本,需要 CORS header。
    <!-- another-site.com 必须提供 Access-Control-Allow-Origin -->
    <!-- 否则,脚本将无法执行 -->
    <script type="module" src="http://another-site.com/their.js"></script>
    复制代码
    • 重复的外部脚本会被忽略
  2. 模块具有自己的本地顶级作用域,并可以通过import/export交换功能。
  3. 模块始终使用 use strict
  4. 模块代码只执行一次。导出仅创建一次,然后会在导入之间共享。

三、ES6模块-导出和导入

这本书里写的很详细,具体参见:导出和导入,下面做些总结:

  • 命名的导出默认的导出

image.png
请记住,import 命名的导出时需要花括号,而 import 默认的导出时不需要花括号。建议团队尽可能的用命名导出。

  • 自查表

image.png

  • 在实际开发中,导入通常位于文件的开头,但是这只是为了更加方便。把 import/export 语句放在脚本的顶部或底部,都没关系。

  • 请注意在{...}中的import/export语句无效。

// 像这样的有条件的导入是无效的:
if (something) {
  import {sayHi} from "./say.js"; // Error: import must be at top level
}
复制代码

四、ES6模块-动态导入

这本书里写的很详细,具体参见:动态导入,下面做些总结:

  • import(module)表达式加载模块并返回一个promise
  • 如果在异步函数中,我们可以使用 let module = await import(modulePath)
  • 有默认的导出,可以使用模块对象的default属性:
// ? say.js
export default function() {
  alert("Module loaded (export default)!");
}

// 访问
let obj = await import('./say.js');
let say = obj.default;
// or, in one line: let {default: say} = await import('./say.js');

say();
复制代码

长文参见

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享