阅读 80

JS模块化的梳理

CommonJS

Node应用使用CommonJS模块规范,Node中每个文件就是一个模块,有自己的作用域,在模块中定义的变量 、函数都是私有的。

模块中有四个重要的变量globalmoduleexportsrequire

Node中的全局变量global,和浏览器的window对象类似,声明在全局下的变量可以在所有模块中访问。module变量代表当前模块,其中module.exports属性表示当前模块对外输出的接口,当其他文件使用require引用该模块时,实际就是读取module.exports变量。

// 模块a.js
const num = 1
module.exports = {
    num,
    add: function(x,y){
        return x+y
    }
}
复制代码
//模块b.js,引入a.js
const a = require('./a.js')
// {num:1, add:fn}
复制代码

exports变量是对module.exports的引用;相当于在顶部声明一个变量var exports = module.exportsexports不能直接赋值,这样就无法指向module.exports了 所以使用exports对外输出接口的写法如下,

// 模块a.js
exports.num = 1
exports.add = function(x,y){
    return x+y
}
复制代码

CommonJS规范加载模块是同步的,加载完成以后,才能执行后面的操作。同时模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

AMD

AMD:Asynchronous Module Definition,异步模块定义,是RequireJS在推广过程中对模块定义的规范化产出。require.js加载完成后,通过回调方法加载data-main中的js,然后require.config方法指定第三方资源路径,define方法来定义自己写的模块,最后使用require方法加载模块

<!-- 引入require.js,main.js中配置require.config -->
<script src="require.js" data-main="main.js"></script>

<!-- main.js -->
<script>
    require.config({
        paths: {
            "Vue": "./vue",
            "jquery": "./jquery"  //js后缀不写
        }
    })
    require(["Vue","jquery"],function(vue,$){
        // 依赖的模块会以参数的形式传进回调函数,这里就可以正常使用Vue和jQuery了
    })
</script>
复制代码

define方法自定义模块,不需要在require.config里配置路径

<!-- 自己的模块比如:module_test.js -->
<script>
    define(function(){
        function add(x,y){
            return x+y
        }
        return {
            add
        }
    })
</script>
<!-- 如果自定义依赖其他模块,先引入其他模块 -->
<script>
    define(['jquery'],function($){
        function add(x,y){
            const total = x+y
            $('body').html(total)
            return total
        }
        return {
            add
        }
    })
</script>
复制代码

CMD

CMD:Common Module Definition,通用模块定义, 是SeaJS在推广过程中对模块定义的规范化产出。和AMD语法类似,区别是AMD在定义模块的时候就要声明其依赖的模块,而CMD只有在用到某个模块的时候再去加载。AMDCMD都实现了前端资源的模块的,而现在ES6和Webpack打包工具的出现这两个应该使用比较少了。

ES6 Module

ES6在语言标准的层面上,实现了模块功能。模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。我们平时的项目一般都是基于webpack来处理,如果想直接在浏览器中加载ES6模块,和原来一样使用<script>便签,同时需要添加type="module"属性。

参考Module的加载实现

// m1.js定义输出
const a = 1
export default a
export const b = 2
export function add(x,y){
    return x+y
}

// m2.js引用
import a, {b, add} from './m2.js'
复制代码

ES6模块与CommonJS模块的两大差异

  • 1、CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用;
  • 2、CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 ES6模块中的原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

UMD

UMD是AMD和CommonJS的糅合,UMD会先判断是否支持Node.js模块的exports,再判断AMD的define方法是否存在,最后都不支持的话就挂载在window全局变量下。 比如打开Vue.js文件

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? 
    module.exports = factory() :
  // 是否支持Node.js模块   
  typeof define === 'function' && define.amd ? 
    define(factory) :
  // 是否支持AMD
  (global = global || self, global.Vue = factory());
  // 最后都不行挂载在window.Vue
}(this, function () { 
    'use strict';
}))
复制代码