JavaScript 模块化


JavaScript 中的模块化

最先的基于当即执行函数,闭包的模块化

const MountClickModule = function(){
  let num = 0;
  const handleClick = ()=>{
    console.log(++num);
  }

  return {
    countClick:()=>{
      document.addEventListener('click',handleClick)
    }
  }
}();

MountClickModule.countClick();

(function(module) {
module.say = ()=>{
  console.log(num)  //undefined
  //do something
}
})(MountClickModule);

MountClickModule.say();

这种闭包的坏处:javascript

  • 扩展模块间没法访问私有变量。
  • 强依赖模块导入的顺序,项目变大后很差维护。

AMD 和 CommonJS 模块化JavaScript 应用

AMD 和 CommonJS 是两个互相竞争的标准,都可定义 JavaScript 模块。除了语法和原理的区别以外,主要区别是 AMD 的设计理念是明确基于浏览器,而 CommonJS 的设计是面向通用 JavaScript 环境

使用 AMD 定义模块依赖

AMD 异步模块定义规范制定了定义模块的规则,这样模块和模块的依赖能够被异步加载。这和浏览器的异步加载模块的环境恰好适应(浏览器同步加载模块会致使性能、可用性、调试和跨域访问等问题)。html

目前,AMD 最流行的实现是 RequireJSjava

define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
       exports.verb = function() {
           return beta.verb();
           //Or:
           return require("beta").verb();
       }
   });

AMD 提供名为 aplha 的函数,它接收一下参数:git

  • 第一个参数,id,是个字符串。它指的是定义中模块的名字,这个参数是可选的。若是没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。
  • 第二个参数,dependencies,是个定义中模块所依赖模块的数组。
  • 第三个参数是初始化模块的工厂函数,该函数接收dependencies做为参数
    AMD异步获取依赖,以免阻塞,若是依赖来自服务器,那么这个过程将花费一些时间,知道依赖所有加载完成后,调用模块的工厂函数,并传入全部的依赖。

能够看出,AMD 有一下几项有点:es6

  • 异步加载模块,避免阻塞。
  • 自动处理依赖,咱们无需考虑模块的引入顺序。
  • 在同一个文件中能够定义多个模块。

CMD

CMD 是 SeaJS 在推广过程当中对模块定义的规范化产出,在 CMD 规范中,一个模块就是一个文件。代码的书写格式以下:github

define(function (require, exports, module) {
  const foo = require('./foo')
  a.doSomething()
  // ...
  const bar = require('./bar') // 依赖能够就近书写
  b.doSomething()
  // do something else
})

CommonJS

AMD 的设计明确基于浏览器,而 CommonJS 的设计是面向通用 JavaScript 环境。CommonJS 目前在 Nodejs 社区中具备最多的用户。CommonJS 使用基于文件的模块,因此每一个文件中都只能定义一个模块,CommonJs 提供变量 module,该变量具备属性 exports,经过 exports 很容易扩展属性。最后,module.exports 做为模块的公共接口。typescript

const beta = require('beta');
function alpha(){
    return beta.verb();
    //Or:
    return require("beta").verb();
}

module.exports = alpha;

CommonJS 要求一个文件就是一个模块,文件中的代码就是模块的一部分,因此不须要使用当即执行函数来包装变量,在模块中定义的变量都是安全的再模块中,不会泄露到全局做用域。只有经过 module.exports 对象暴露的对象或函数才能够在函数外部访问
CommonJS 具备如下特色:api

  • 基于文件系统。
  • 引入模块时文件同步加载,能够访问模块的公共接口。
  • 模块加载相对更快

这是 CommonJS 在服务端更流行的缘由。跨域

ES6 模块的导入导出

ES6 模块结合了CommonJS 和 AMD 的有点,具体以下:数组

  • 与 CommonJS 相似,ES6模块语法相对简单,而且基于文件(每个文件就是一个模块)
  • 与 AMD 相似,ES6 模块支持异步加载模块。

既 ES6 结合了两种模块化的有点,基于文件系统,既支持异步也支持同步,由于浏览器并无实现 ES6 的模块化 API 因此具体是异步仍是同步取决于loader api

ES6 模块的主要思想是必须显示的使用标志符导出模块,才能从外部访问模块。其余标志符,甚至在最顶级做用域中定义的标识符,只能在模块中使用。
ES6 引入两个关键字:

  • export ---- 从模块外部指定标识符。
  • import ---- 导入模块标识符。

从index.js模块中导出:

const hello = 'hello';
  export const name = 'yunfly'

  export function sayHi(){
    return `${hello} ${name}!`
  }

也能够在模块最后一块儿导出:

// foo.js
  const hello = 'hello';
  export const name = 'yunfly'

  export function sayHi(){
    return `${hello} ${name}!`
  }

  export { name, sayHi }

  // export { name as firstName, sayHi }
  

  // bar.js

  // 使用 as 设置导如别名
  import { name as firstName, sayHi } from 'foo'

  console.log(name)
  sayHi()

  //bar2.js
  // 导出所有标识符:
  import * as sayModule from 'foo';

  console.log(sayModule.name)
  sayModule.sayHi()

默认导出 export default 被认为是有害的

// foo.js
class Foo {}

export default Foo


// bar.js
import Foo from './foo';

这存在一些可维护性的问题:

  • 若是你在 foo.ts 里重构 Foo,在 bar.ts 文件中,它将不会被从新命名;
  • 若是你最终须要从 foo.ts 文件中导出更多有用的信息(在你的不少文件中都存在这种情景),那么你必须兼顾导入语法。
  • 在 ts 中默认导出的可发现性很是差,你不能智能的辨别一个模块它是否有默认导出。

文章参考:《JavaScript忍者秘籍》

相关文章
相关标签/搜索