模块化

js中的模块化的作用

  • 解决代码中变量的命名冲突
  • 提供代码复用性
  • 提高代码可维护性

实现模块的几种方式

立即执行函数

使用立即执行函数实现模块化是常见的手段,通过函数作用域解决了命名冲突、污染全局作用域的问题

(function(variable){
     variable.test = function() {}
     // ... 声明各种变量、函数都不会污染全局作用域
})(variable)
(function(variable){
     variable.test = function() {}
     // ... 声明各种变量、函数都不会污染全局作用域
})(variable)

AMD & CMD

AMD (Asynchronous Module Definition)是 RequireJS 在推广过程中对模块定义的规范化产出.
CMD (Common Module Definition) 是 SeaJS 在推广过程中对模块定义的规范化产出。

对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)

CMD 推崇依赖就近,AMD 推崇依赖前置

  // AMD
  define(['./a', './b'], function(a, b) {
    // 加载模块完毕可以使用
    a.do()
    b.do()
  })
  // CMD
  define(function(require, exports, module) {
    // 加载模块
    // 可以把 require 写在函数体的任意地方实现延迟加载
    var a = require('./a')
    a.doSomething()
  })
  // AMD
  define(['./a', './b'], function(a, b) {
    // 加载模块完毕可以使用
    a.do()
    b.do()
  })
  // CMD
  define(function(require, exports, module) {
    // 加载模块
    // 可以把 require 写在函数体的任意地方实现延迟加载
    var a = require('./a')
    a.doSomething()
  })

ES6 Module

  • CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响。
  • CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是 ES Module 采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
  • ES Module 会编译成 require/exports来执行的
 // 引入模块 API
 import XXX from './a.js'
 import { XXX } from './a.js'
 // 导出模块 API
 export function a() {}
 export default function() {}
 // 引入模块 API
 import XXX from './a.js'
 import { XXX } from './a.js'
 // 导出模块 API
 export function a() {}
 export default function() {}

CommonJS

CommonJS 是以在浏览器环境之外构建 javaScript 生态系统为目标而产生的写一套规范,主要是为了解决 javaScript 的作用域问题而定义的模块形式,可以使每个模块它自身的命名空间中执行,该规范的主要内容是,模块必须通过 module.exports 导出对外的变量或者接口,通过 require() 来导入其他模块的输出到当前模块的作用域中;目前在服务器和桌面环境中,node.js 遵循的是 CommonJS 的规范;CommonJS 对模块的加载时同步的;

ES6 模块与 CommonJS 模块的差异
  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
定义模块
  // module.js
  let name = 'Vincent chen';
  let sayName = function () {
    console.log(name);
  };
  module.exports = { name, sayName }
  // 或者
  exports.sayName = sayName;
  // module.js
  let name = 'Vincent chen';
  let sayName = function () {
    console.log(name);
  };
  module.exports = { name, sayName }
  // 或者
  exports.sayName = sayName;
加载模块
 // 通过 require 引入依赖
 let module = require('./module.js');
 module.sayName(); //  Vincent chen
 // 通过 require 引入依赖
 let module = require('./module.js');
 module.sayName(); //  Vincent chen
module.export、exports的区别
  • module.exports 方法还可以单独返回一个数据类型(String、Number、Object…),而 exports 只能返回一个 Object 对象
  • 所有的 exports 对象最终都是通过 module.exports 传递执行,因此可以更确切地说,exports 是给 module.exports 添加属性和方法
exports.name = 'Vincent chen';
exports.age = 30;
console.log(module.exports); // result: { name: 'Vincent chen', age: 30 }
exports.name = 'Vincent chen';
exports.age = 30;
console.log(module.exports); // result: { name: 'Vincent chen', age: 30 }
  • 同时用到module.export跟exports的时候
 // case1
 module.exports = { a: 1 }
 exports.b = 2;
 console.log(module.exports); // { a:1 }

 // case2
 module.exports.a =1;
 exports.b = 2;
 console.log(module.exports); // { a:1, b:2 }
 // case1
 module.exports = { a: 1 }
 exports.b = 2;
 console.log(module.exports); // { a:1 }

 // case2
 module.exports.a =1;
 exports.b = 2;
 console.log(module.exports); // { a:1, b:2 }

参考链接