前端模块化

106 阅读4分钟

什么是模块

早期js代码的书写方式

function bar () {
  //...
}
function foo () {
  //...
}

将不同的功能封装成不同的函数,挂载在全局,命名污染

简单封装:namespace方法(减少了变量数,仍未解决gobal污染的问题)

var myModule = {
    foo:function() {},
    bar:function() {}
}
myModule.foo()

IIFE模式(匿名闭包)

var myModule = (function(){ 
    var _private = "private data"; 
    var foo = function(){ 
        console.log(_private) 
    }
    return { foo: foo } 
})() 
//依赖注入
myModule.foo();
myModule._private; // undefined

引申,javascript的作用域(Scope)

  1. ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6的到来,为我们提供了‘块级作用域’,可通过新增命令let和const来体现。 特性:作用域是分层的,内层作用域可以访问外层作用域的变量

js是词法作用域

var value = 10;
function foo() {
    console.log(value);
}

function bar() {
    var value = 20;
    foo();
}

bar();

结论1:函数是 JavaScript 唯一的 Local Scope

SCRIPT LOADER 只有封装性不够,还需按需加载

//body
    script(src="jquery.js")
    script(src="app.js")
    ...
    ...

按照代码的顺序执行,如需要体积大的资源,阻塞渲染

引申 script的defer和async

async:异步加载,下载完后执行 defer:异步加载,渲染完后执行

CommonJs

  • 通过require函数进行导入
  • 通过export或者module.exports导出
  • 同步加载
  • 四个重要的moduleexportsrequireglobal

实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。

exports.add = function(a, b){ return a + b; }
module.exports = {
    //在这里写上需要向外暴露的函数、变量
    add: ...
}
// main.js 
var math = require('math') // ./math in node 
console.log(math.add(1, 2));

commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。

AMD(Async Module Definition)(RequireJS 对模块定义的规范化产出)

RequireJS 是一个JavaScript模块加载器。它非常适合在浏览器中使用,但它也可以用在其他脚本环境,就像 Rhino and Node。使用RequireJS加载模块化脚本将提高代码的加载速度和质量。

/** AMD写法 **/ 
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 
    // 等于在最前面声明并初始化了要用到的所有模块 a.doSomething(); 
    // 已加载所有
    if (false) { 
        // 即便没用到某个模块 b,但 b 还是提前执行了 
        b.doSomething() 
    } 
});

优点:
适合在浏览器环境中异步加载模块。可以并行加载多个模块。
缺点:
提高了开发成本,并且不能按需加载,而是必须提前加载所有的依赖。

CMD(Common Module Definition)(SeaJS 对模块定义的规范化产出)

/** CMD写法 **/ 
define(function(require, exports, module) {
    var a = require('./a'); //在需要时申明,代码运行到这才加载
    a.doSomething(); 
    if (false) { 
        var b = require('./b'); b.doSomething(); 
    } 
});

优点:
同样实现了浏览器端的模块化加载,可以按需加载,依赖就近。
缺点:
依赖SPM打包,模块的加载逻辑偏重。

UMD模块(兼容node端,和浏览器端)

factory负责返回你需要导出的内容(对象,函数,变量等)。

(function(root, factory) {
  if (typeof module === 'object' && typeof module.exports === 'object') {
      console.log('是commonjs模块规范,nodejs环境')
      module.exports = factory();
  } else if (typeof define === 'function' && define.amd) {
      console.log('是AMD模块规范,如require.js')
      define(factory)
  } else if (typeof define === 'function' && define.cmd) {
      console.log('是CMD模块规范,如sea.js')
      define(function(require, exports, module) {
          module.exports = factory()
      })
  } else {
      console.log('没有模块环境,直接挂载在全局对象上')
      root.umdModule = factory();
  }
}(this, function() {
  return {
      name: '我是一个umd模块'
  }
}))

ES6 Module

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口

// math.js 
export default math = { 
    PI: 3.14, 
    foo: function(){} 
}
// app.js 
import math from "./math";
math.PI

ESModule 需要经历三个步骤:

1: Construction(构造)- 找到,下载所有的文件并且解析为module records。
2: Instantiation(实例化)- 在内存里找到所有的“盒子”,把所有导出的变量放进去(但是暂时还不求值)。然后,让导出和导入都指向内存里面的这些盒子。这叫做“linking(链接)”。
3: Evaluation(求值)- 执行代码,得到变量的值然后放到这些内存的“盒子”里。