前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Javascript模块化详解

Javascript模块化详解

作者头像
Clearlove
发布2021-03-11 11:10:36
5480
发布2021-03-11 11:10:36
举报
文章被收录于专栏:前端客栈前端客栈
  1. 首页
  2. 专栏
  3. javascript
  4. 文章详情

5

Javascript模块化详解

Clearlove发布于 3 月 9 日

为什么需要Javascipt模块化?

前端的发展日新月异,前端工程的复杂度也不可同日而语。原始的开发方式,随着项目复杂度提高,代码量越来越多,所需加载的文件也越来越多,这个时候就需要考虑如下几个问题:

  1. 命名问题:所有文件的方法都挂载到window/global上,会污染全局环境,并且需要考虑命名冲突问题
  2. 依赖问题:script是顺序加载的,如果各个文件文件有依赖,就得考虑js文件的加载顺序
  3. 网络问题:如果js文件过多,所需请求次数就会增多,增加加载时间

Javascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。

本文主要介绍Javascript模块化的4种规范: CommonJSAMDUMDESM

CommonJS

CommonJS是一个更偏向于服务器端的规范。NodeJS采用了这个规范。CommonJS的一个模块就是一个脚本文件。require命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成一个对象

代码语言:javascript
复制
{
  id: '...',
  exports: { ... },
  loaded: true,
  ...
}

id是模块名,exports是该模块导出的接口,loaded表示模块是否加载完毕。

以后需要用到这个模块时,就会到exports属性上取值。即使再次执行**require**命令,也不会再次执行该模块,而是到缓存中取值

代码语言:javascript
复制
// utile.js
const util = {
  name:'Clearlove'
  sayHello:function () {
      return 'Hello I am Clearlove';
  }
}
// exports 是指向module.exports的一个快捷方式
module.exports = util
// 或者
exports.name = util.name;
exports.sayHello = util.sayHello;

const selfUtil = require('./util');
selfUtil.name;            
selfUtil.sayHello(); 
  • CommonJS是同步导入模块
  • CommonJS导入时,它会给你一个导入对象的副本
  • CommonJS模块不能直接在浏览器中运行,需要进行转换、打包

由于CommonJS是同步加载模块,这对于服务器端不是一个问题,因为所有的模块都放在本地硬盘。等待模块时间就是硬盘读取文件时间,很小。但是,对于浏览器而言,它需要从服务器加载模块,涉及到网速,代理等原因,一旦等待时间过长,浏览器处于”假死”状态。

所以在浏览器端,不适合于CommonJS规范。所以在浏览器端又出现了一个规范—-AMD

AMD

AMD(Asynchronous Module Definition - 异步加载模块定义)规范,一个单独的文件就是一个模块。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。

这里异步指的是不堵塞浏览器其他任务(**dom**构建,**css**渲染等),而加载内部是同步的(加载完模块后立即执行回调)

AMD也采用require命令加载模块,但是不同于CommonJS,它要求两个参数:

代码语言:javascript
复制
require([module], callback);

第一个参数module,是一个数组,里面的成员是要加载的模块,callback是加载完成后的回调函数,回调函数中参数对应数组中的成员(模块)。

AMD的标准中,引入模块需要用到方法require,由于window对象上没定义require方法, 这里就不得不提到一个库,那就是RequireJS

官网介绍RequireJS是一个js文件和模块的加载器,提供了加载和定义模块的api,当在页面中引入了RequireJS之后,我们便能够在全局调用definerequire

代码语言:javascript
复制
define(id?, dependencies?, factory);
  • id:模块的名字,如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字
  • dependencies:模块的依赖,已被模块定义的模块标识的数组字面量。依赖参数是可选的,如果忽略此参数,它应该默认为 ["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。
  • factory:模块的工厂函数,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
代码语言:javascript
复制
// 定义一个moduleA.js
define(function(){
  const name = "module A";
  return {
    getName(){
      return name
    }
  }
});

// 定义一个moduleB.js
define(["moduleA"], function(moduleA){
  return {
    showFirstModuleName(){
      console.log(moduleA.getName());
    }
  }
});

// 实现main.js
require(["moduleB"], function(moduleB){
  moduleB.showFirstModuleName();
});
代码语言:javascript
复制
<html>
<!-- 此处省略head -->
<body>
    <!--引入requirejs并且在这里指定入口文件的地址-->
    <script data-main="js/main.js" src="js/require.js"></script>
</body>
</html>

要通过script引入requirejs,然后需要为标签加一个属性data-main来指定入口文件。

前面介绍用define来定义一个模块的时候,直接传“模块名”似乎就能找到对应的文件,这一块是在哪实现的呢?其实在使用RequireJS之前还需要为它做一个配置:

代码语言:javascript
复制
// main.js
require.config({
  paths: {
    // key为模块名称, value为模块的路径
    "moduleA": "./moduleA",
    "moduleB": "./moduleB"
  }
});

require(["moduleB"], function(moduleB){
    moduleB.showFirstModuleName();
});

这个配置中的属性paths只写模块名就能找到对应路径,不过这里有一项要注意的是,路径后面不能跟.js文件后缀名,更多的配置项请参考RequireJS官网。

UMD

UMD 代表通用模块定义(Universal Module Definition)。所谓的通用,就是兼容了CmmonJSAMD规范,这意味着无论是在CmmonJS规范的项目中,还是AMD规范的项目中,都可以直接引用UMD规范的模块使用。

原理其实就是在模块中去判断全局是否存在exportsdefine,如果存在exports,那么以CommonJS的方式暴露模块,如果存在define那么以AMD的方式暴露模块:

代码语言:javascript
复制
(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    define(["jquery", "underscore"], factory);
  } else if (typeof exports === "object") {
    module.exports = factory(require("jquery"), require("underscore"));
  } else {
    root.Requester = factory(root.$, root._);
  }
}(this, function ($, _) {
  // this is where I defined my module implementation
  const Requester = { // ... };
  return Requester;
}));

这种模式,通常会在webpack打包的时候用到。output.libraryTarget将模块以哪种规范的文件输出。

ESM

在ECMAScript 2015版本出来之后,确定了一种新的模块加载方式,我们称之为ES6 Module。它和前几种方式有区别和相同点:

  • 它因为是标准,所以未来很多浏览器会支持,可以很方便的在浏览器中使用
  • 它同时兼容在node环境下运行
  • 模块的导入导出,通过importexport来确定
  • 可以和CommonJS模块混合使用
  • CommonJS输出的是一个值的拷贝。ES6模块输出的是值的引用,加载的时候会做静态优化
  • CommonJS模块是运行时加载确定输出接口,ES6模块是编译时确定输出接口

ES6模块功能主要由两个命令构成:importexportimport命令用于输入其他模块提供的功能。export命令用于规范模块的对外接口。

export的几种用法:

代码语言:javascript
复制
// 输出变量
export const name = 'Clearlove';
export const year = '2021';

// 输出一个对象(推荐)
const name = 'Clearlove';
const year = '2021';
export { name, year}


// 输出函数或类
export function add(a, b) {
  return a + b;
}

// export default 命令
export default function() {
  console.log('foo')
}

import导入模块:

代码语言:javascript
复制
// 正常命令
import { name, year } from './module.js';

// 如果遇到export default命令导出的模块
import ed from './export-default.js';

模块编辑好之后,它有两种形式加载:

浏览器加载

浏览器加载ES6模块,使用<script>标签,但是要加入type="module"属性。

  • 外链js文件:
代码语言:javascript
复制
<script type="module" src="index.js"></script>
  • 内嵌在网页中
代码语言:javascript
复制
<script type="module">
  import utils from './utils.js';
  // other code
</script>

对于加载外部模块,需要注意:

  • 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见
  • 模块脚本自动采用严格模式,不管有没有声明use strict
  • 模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口
  • 模块之中,顶层的**this**关键字返回**undefined**,而不是指向**window**。也就是说,在模块顶层使用this关键字,是无意义的
  • 同一个模块如果加载多次,将只执行一次

Node加载

Node要求 ES6 模块采用.mjs后缀文件名。也就是说,只要脚本文件里面使用import或者export命令,就必须采用.mjs后缀名。Node.js 遇到.mjs文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定use strict

如果不希望将后缀名改成.mjs,可以在项目的package.json文件中,指定type字段为

代码语言:javascript
复制
{
  "type": "module"
}

一旦设置了以后,该目录里面的 JS 脚本,就被解释用 ES6 模块。

代码语言:javascript
复制
# 解释成 ES6 模块 
$ node my-app.js

如果这时还要使用 CommonJS 模块,那么需要将 CommonJS 脚本的后缀名都改成.cjs。如果没有type字段,或者type字段为commonjs,则.js脚本会被解释成 CommonJS 模块。

总结为一句话:.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置。

注意,ES6 模块与 CommonJS 模块尽量不要混用。require命令不能加载.mjs文件,会报错,只有import命令才可以加载.mjs文件。反过来,.mjs文件里面也不能使用require命令,必须使用import

Nodeimport命令只支持异步加载本地模块(file:协议),不支持加载远程模块。

总结

  • 由于 ESM 具有简单的语法,异步特性和可摇树性,因此它是最好的模块化方案
  • UMD 随处可见,通常在 ESM 不起作用的情况下用作备用
  • CommonJS 是同步的,适合后端
  • AMD 是异步的,适合前端

javascriptes6commonjsamdes-modules

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Javascript模块化详解
    • 为什么需要Javascipt模块化?
      • CommonJS
        • AMD
          • UMD
            • ESM
              • 浏览器加载
              • Node加载
            • 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档