乐趣区

关于模块化:爪哇学习笔记JavaScript模块化变迁史

1. 幼年期——无模块化

按性能将 js 代码放到不同的 JS 文件,在模板中通过 script 标签按需援用

<script src="a.js"></script>
<script src="b.js"></script>

文件拆散是模块化的第一步
存在的问题:

  • 净化全局作用域 => 不利于大型项目的开发及多人团队的共建

2. 成长期——命名空间模式

繁多全局变量

JavaScript 中一个风行的命名空间模式是抉择一个全局变量作为次要的援用对象。比方 jQuery 库就是应用这种形式。

var myApplication = (function () {function(){//...},
    return{//...}
})();

命名空间前缀

命名空间前缀模式其思路十分清晰,就是抉择一个独特的命名空间,而后在其前面申明申明变量、办法和对象。

var myApplication_propertyA = {};
var myApplication_propertyB = {};

function myApplication_myMethod() {}

对象字面量表示法

对象字面量模式能够认为是蕴含一组键值对的对象,每一对键和值由冒号分隔,键也能够是代码新的命名空间。

var myApplication = {
    // 能够很容易的为对象字面量定义性能
    getInfo:function() {// ***},
    
    // 能够进一步撑持对象命名空间
    models:{},
    views:{pages:{}
    },
    collections:{}};

嵌套命名空间

嵌套命名空间模式能够说是对象字面量模式的升级版,它也是一种无效的防止抵触模式,因为即便一个命名空间存在,它也不太可能领有同样的嵌套子对象。

var myApplication = myApplication || {};
 
 // 定义嵌套子对象
 myApplication.routers = myApplication.routers || {};
 myApplication.routers.test = myApplication.routers.test || {};

IIFE(Immediately Invoked Function Expression,立刻调用函数表达式)

IIFE 实际上就是立刻执行匿名函数。在 JavaScript 中,因为变量和函数都是在这样一个只能在外部进行拜访的上下文中被显式地定义,函数调用提供了一种实现公有变量和办法的便捷形式。IIFE 是用于封装利用程序逻辑的罕用办法,以爱护它免受全局名称空间的影响,其在命名空间方面也能够施展其非凡的作用。

var namespace = namespace || {};

(function( o){   
    o.foo = "foo";
    o.bar = function(){return "bar";};
})(namespace);

console.log(namespace);

// 定义一个简略的模块
const iifeModule = (() => {
  let count = 0;
  return {increase: () => ++count,
    reset: () => {count = 0;}
  }
})();

iifeModule.increase();
iifeModule.reset();

// 依赖其余模块的 IIFE
const iifeModule = ((dependencyModule1, dependencyModule2) => {
  let count = 0;
  return {increase: () => ++count,
    reset: () => {count = 0;}
  }
})(dependencyModule1, dependencyModule2);
iifeModule.increase();
iifeModule.reset();

// Revealing Module(揭示模块)模式
const iifeModule = (() => {
  let count = 0;
  function increaseCount() {++count;}
  function resetCount() {count = 0;}

  return {
    increase: increaseCount,
    reset: resetCount
  }
})();

iifeModule.increase();
iifeModule.reset();
/**
 * 揭示模块模式定义:* 在模块模式的根底上,在返回的公有范畴内,从新定义所有的函数和变量。并返回一个匿名的对象。他领有所有指向公有函数的指针。* Module 模式最后被定义为一种在传统软件工程中为类提供公有和共有封装的办法。JS 这里最后应用 IIEF 封装
 **/

命名空间注入

命名空间注入是 IIFE 的另一个变体,从函数包装器外部为一个特定的命名空间“注入”办法和属性,应用 this 作为命名空间代理。这种模式的长处是能够将性能行为利用到多个对象或命名空间。

var myApplication = myApplication || {};
myApplication.utils = {};

(function () {
    var value = 5;
    
    this.getValue = function () {return value;}

    // 定义新的子命名空间
    this.tools = {};}).apply(myApplication.utils);

(function () {this.diagnose = function () {return "diagnose";}
}).apply(myApplication.utils.tools);

命名空间注入是用于为多个模块或命名空间指定一个相似的性能根本集,但最好是在申明公有变量或者办法时再应用它,其余时候应用嵌套命名空间曾经足以满足需要了。

主动嵌套的命名空间

function extend(ns, nsStr) {var parts = nsStr.split("."),
        parent = ns,
        pl;

    pl = parts.length;

    for (var i = 0; i < pl; i++) {
        // 属性如果不存在,则创立它
        if (typeof parent[parts[i]] === "undefined") {parent[prats[i]] = {};}
        parent = parent[parts[i]];
    }
    return parent;
}

// 用法
var myApplication = myApplication || {};
var mod = extend(myApplication, "module.module2");

3. 成熟期

CJS——CommonJS

  • 每个文件就是一个模块,有本人的作用域。在一个文件外面定义的变量、函数、类,都是公有的,对其余文件不可见。如果想在多个文件分享变量,必须定义为 global 对象的属性。
  • CommonJS 标准规定,每个模块外部,module 变量代表以后模块。这个变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。
  • require 办法用于加载模块。
  • 为了不便,Node 为每个模块提供一个 exports 变量,指向 module.exports。这等同在每个模块头部,有一行这样的命令。

    var exports = module.exports;

    造成的后果是,在对外输入模块接口时,能够向 exports 对象增加办法。

    exports.area = function (r) {return Math.PI * r * r;};

    留神,不能间接将 exports 变量指向一个值,因为这样等于切断了 exports 与 module.exports 的分割。

    // 有效写法
    exports = function(x) {console.log(x)};

应用形式

// main.js
// 引入局部
const dependencyModule1 = require(./dependencyModule1);
const dependencyModule2 = require(./dependencyModule2);

// 解决局部
let count = 0;
const increase = () => ++count;
const reset = () => {count = 0;}
// 做一些跟引入依赖相干事宜……

// 裸露接口局部
// 形式 1
exports.increase = increase;
exports.reset = reset;
// 形式 2
module.exports = {
  increase,
  reset
}


// index.js
// 应用模块
const {increase, reset} = require('./main.js');

increase();
reset();

优缺点

  • 长处:
    CommonJS 率先在服务端实现了,从框架层面解决依赖、全局变量净化的问题
  • 毛病:
    次要针对了服务端的解决方案。对于异步拉取依赖的解决整合不是那么的敌对。

AMD 标准

AMD 是 ”Asynchronous Module Definition” 的缩写,意思就是 ” 异步模块定义 ”。它采纳异步形式加载模块,模块的加载不影响它前面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载实现之后,这个回调函数才会运行。

异步模块定义 (AMD) API 指定了一种定义模块的机制,以便能够异步加载模块及其依赖项。这特地实用于模块的同步加载会产生性能、可用性、调试和跨域拜访问题的浏览器环境。

定义模块:define 函数

define(id?: String, dependencies?: String[], factory: Function|Object);
  • id 是模块名称,可选
  • dependencies 指定了所要依赖的模块列表,它是一个数组,也是可选的参数,每个依赖的模块的输入将作为参数一次传入 factory 中。如果没有指定 dependencies,那么它的默认值是 [“require”, “exports”, “module”]。

    define(function(require, exports, module) {})
  • factory 是最初一个参数,它包裹了模块的具体实现,它是一个函数或者对象。如果是函数,那么它的返回值就是模块的输入接口或值。

调用模块:require 函数

require(dependencies?: String[], callback: Function);
  • 示例

    require(['foo', 'bar'], function (foo, bar) {foo.doSomething();
    });
  • 在 define 办法外部应用 require

    define(function (require) {var otherModule = require('otherModule');
      // do something with otherModule
    });

优缺点

  • 长处: 适宜在浏览器中加载异步模块,能够并行加载多个模块
  • 毛病:会有引入老本,不能按需加载

UMD(兼容 AMD 和 CommonJS)

UMD (Universal Module Definition), 心愿提供一个前后端跨平台的解决方案(反对 AMD 与 CommonJS 模块形式)

(function(global, factory) {if(typeof module === "object" && typeof module.exports === "object") {module.exports = factory(global);
    } else if(typeof define === 'function' && define.amd){define([], factory);
    } else {global.returnExports = factory();
    }
})(this, function() {
    // 定义模块代码

    // 返回模块内容
    return {};});

CMD 标准

按需加载
次要利用的框架 sea.js

  define('module', (require, exports, module) => {let $ = require('jquery');
    // jquery 相干逻辑

    let dependencyModule1 = require('./dependecyModule1');
    // dependencyModule1 相干逻辑
  })
  • 长处:按需加载,依赖就近
  • 依赖于打包,加载逻辑存在于每个模块中,扩充模块体积

ES6 模块化

走向新时代

新增定义:
引入关键字 —— import
导出关键字 —— export

模块引入、导出和定义的中央:

  // 引入区域
  import dependencyModule1 from './dependencyModule1.js';
  import dependencyModule2 from './dependencyModule2.js';

  // 实现代码逻辑
  let count = 0;
  export const increase = () => ++count;
  export const reset = () => {count = 0;}

  // 导出区域
  export default {increase, reset}

模板引入的中央

  <script type="module" src="esModule.js"></script>

node 中:

  import {increase, reset} from './esModule.mjs';
  increase();
  reset();

  import esModule from './esModule.mjs';
  esModule.increase();
  esModule.reset();

面试题 6:动静模块
考查:export promise

ES11 原生解决方案:

  import('./esModule.js').then(dynamicEsModule => {dynamicEsModule.increase();
  })
  • 长处(重要性):通过一种最对立的状态整合了 js 的模块化
  • 毛病(局限性):实质上还是运行时的依赖剖析

解决模块化的新思路——前端工程化

背景

为了解决后面那些模块化计划依赖于运行时剖析的问题

解决方案

前端工程化采纳线下执行的形式来解决这个问题(grunt gulp webpack)

  <!doctype html>
    <script src="main.js"></script>
    <script>
      // 给构建工具一个标识位
      require.config(__FRAME_CONFIG__);
    </script>
    <script>
      require(['a', 'e'], () => {// 业务解决})
    </script>
  </html>
  define('a', () => {let b = require('b');
    let c = require('c');

    export.run = () {// run}
  })
工程化实现

step1: 扫描依赖关系表:

  {a: ['b', 'c'],
    b: ['d'],
    e: []}

step2: 从新生成依赖数据模板

  <!doctype html>
    <script src="main.js"></script>
    <script>
      // 构建工具生成数据
      require.config({
        "deps": {a: ['b', 'c'],
          b: ['d'],
          e: []}
      })
    </script>
    <script>
      require(['a', 'e'], () => {// 业务解决})
    </script>
  </html>

step3: 执行工具,采纳模块化计划解决模块化解决依赖

  define('a', ['b', 'c'], () => {
    // 执行代码
    export.run = () => {}
  })

长处:

  1. 构建时生成配置,运行时执行
  2. 最终转化成执行解决依赖
  3. 能够拓展

齐全体 webpack 为外围的工程化 + mvvm 框架组件化 + 设计模式

参考文档:

  • JS 进阶篇 – 命名空间模式解析
  • CommonJS 标准
  • AMD 标准
  • RequireJS 和 AMD 标准
退出移动版