js 模块化进程

JS模块化进程

1、上古时代

第一步javascript

只要把不一样的函数(以及记录状态的变量)简单地放在一块儿,就算是一个模块。Global全局污染,容易命名冲突html

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

第二步 Namespacejava

虽然减小了Global变量的数量,可是这样的写法会暴露全部模块成员,内部状态能够被外部改写。jquery

var name = {
    foo: function(){
        
    },
    bar: function(){
        
    }
}

// 使用
name.foo();

第三步 IIFE模式webpack

闭包模式,经过当即执行函数写法,能够达到不暴露私有变量的目的。且外部没法访问内部的值。es6

var module = (function(){
    var _private = 'sss';
    var foo = function(){
        
    }   
    return {
        foo: foo
    }
})();

module.foo();
module._private; // undefined

引入依赖web

// 改进一些
var Module = (function($){
    var _$body = $("body");     // we can use jQuery now!
    var foo = function(){
        console.log(_$body);    // 特权方法
    }

    // Revelation Pattern
    return {
        foo: foo
    }
})(jQuery)

Module.foo();
// 依赖其余
var module1 = (function (mod){

  mod.m3 = function () {
    //...
  };

  return mod;

})(module1);
// 其余扩展
var MODULE = (function (my) {
  // add capabilities...
  return my;
}(MODULE || {}));
// 能够传入空对象
var MODULE = (function (my) {
  var old_moduleMethod = my.moduleMethod;

  my.moduleMethod = function () {
    // 方法重载
    // 可经过 old_moduleMethod 调用之前的方法...
  };

  return my;
}(MODULE));
// 有时咱们要求在扩展时调用之前已被定义的方法,这也有可能被用于覆盖已有的方法。这时,对模块的定义顺序是有要求的。
  • 这就是模块模式,也就是现代模块实现的基石

2、远古时代

Script Loader
经过script标签引入一堆的依赖,按序执行。一样难以维护,依赖过多。编程

body
    script(src="zepto.js")
    script(src="jhash.js")
    script(src="fastClick.js")
    script(src="iScroll.js")
    script(src="underscore.js")
    script(src="handlebar.js")
    script(src="datacenter.js")
    script(src="deferred.js")
    script(src="util/wxbridge.js")
    script(src="util/login.js")
    script(src="util/base.js")
    script(src="util/city.js")
    script(src="util/date.js")
    script(src="util/cookie.js")
    script(src="app.js")
这样的写法有很大的缺点。首先,加载的时候,浏览器会中止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,因为js文件之间存在依赖关系,所以必须严格保证加载顺序(好比上例的1.js要在2.js的前面),依赖性最大的模块必定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。

3、中古时代

CommonJS 征服世界的第一步是跳出浏览器。segmentfault

CommonJS规范的提出,主要时为了弥补js没有标准的缺陷,使得经过CommonJS Api编写的应用能够在不一样的宿主环境中执行。浏览器

由于老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;可是在服务器端,必定要有模块,与操做系统和其余应用程序互动,不然根本无法编程。所以Node就在Commonjs的基础上应运而生了。

NodeJSCommonJS规范的实现,webpack 也是以CommonJS的形式来书写】

3、近代奇兵

AMD/CMD 浏览器环境模块化方案

AMD(Asynchronous Module Definition:异步模块定义)RequireJS 在推广过程当中对模块定义的规范化产出。

CMD(Common Module Definition:公共模块定义)SeaJS 在推广过程当中对模块定义的规范化产出。

1. AMD

有了服务器端模块之后,很天然地,你们就想要客户端模块。并且最好二者可以兼容,一个模块不用修改,在服务器和浏览器均可以运行。

可是,因为一个重大的局限,使得CommonJS规范不适用于浏览器环境。下面的代码,若是在浏览器中运行,会有一个很大的问题,你能看出来吗?

var math = require('math');
math.add(2, 3);

第二行math.add(2, 3),在第一行require('math')以后运行,所以必须等math.js加载完成。也就是说,若是加载时间很长,整个应用就会停在那里等。

这对服务器端不是一个问题,由于全部的模块都存放在本地硬盘,能够同步加载完成,等待时间就是硬盘的读取时间。可是,对于浏览器,这倒是一个大问题,由于模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。

所以,在浏览器就须要"异步加载"(asynchronous)。这就是AMD规范诞生的背景。

目前,主要有两个Javascript库实现了AMD规范:require.jscurl.js


Require.JS

Require.JS 的基本功能"模块化加载"。

实例1

// math1.js
/*
    语法结构:
    1. define({函数方法})
*/
// 一个没有依赖性的模块能够直接定义对象

define({
  name: '测试',
  add: function(num1, num2) {
    return num1+num2;
  }
})
// test1.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <script src="./lib/require.js" charset="utf-8"></script>
    <script type="text/javascript">
      require(['math1'], function(math) {
        console.log(math.name);
        console.log(math.add(1,3));
      })
    </script>
  </body>
</html>

实例二

// math2.js
/*
    语法结构:
    2. define([引入其余模块地址],回调函数(引入模块别名));
    别名能够在函数里面去调用其余模块提供的方法
*/
// 一个返回对象的匿名模块
define(['math1'], function(math) {
  var sub = function(num1,num2) {
    return num1 - num2;
  }

  return {
    add: math.add,
    sub: sub
  }
})
// test2.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <script src="./lib/require.js" charset="utf-8"></script>
    <script type="text/javascript">
      require(['math2'], function(math) {
        console.log(math.sub(4,2));
      })
    </script>
  </body>
</html>

实例三

// math3.js
// 定义一个命名模块

define('mymath', ['math2'], function(math) {
  var multiplication = function(num1, num2) {
    return num1 * num2;
  }
  return {
    add: math.add,
    sub: math.sub,
    mult: multiplication
  }
})
// test3.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
    <script src="./lib/require.js" charset="utf-8"></script>
    <script type="text/javascript">
    //须要配置一下引入的模块的地址
      require.config({
        paths:{
          'mymath': 'math3'
        }
      });
      require(['mymath'], function(math){
        console.log(math.mult(3,5));
      })
    </script>
  </body>
</html>

下图有一些注意事项,可是截图不是本实例的截图

实例四

// math4.js
// 一个使用了简单CommonJS转换的模块定义
define(function(require,exports,module){
    // 引入其余模块
    var math = require('js/1_math');
    console.log(math);

    // 导出(暴露方法:2种方式)
    // 第一种
    // exports.a = math.add;
    // 第二种
    module.exports = {
        a : math.add
    }
});
// test4.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    
</body>
<script type="text/javascript" src="js/require.js"></script>
<script type="text/javascript">
require(['js/4_math'],function(mytool){
    console.log(mytool.a(11,22));//33
});
</script>
</html>


一个小demo
  • 目录结构

图片描述

  • 代码
// index.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
      <h1>My Sample Project</h1>
      <script src="./script/require.js" data-main="./script/main" charset="utf-8"></script>
  </body>

</html>
// main.js
require.config({
  baseUrl: 'script',
  paths: {
    math: './lib/math1',
    utils: './utils/utils',
    jquery: './lib/jquery.min'
  }
});

require(['math','utils','jquery'], function(math, utils, $) {
  $(function(){
    console.log(math.add(1,2));
    console.log(utils.utils.sub(4,2));
    alert('在加载完math、utils、jquery以后,我执行了');
  })
})
//math.js 
define({
  add:function(num1, num2) {
    return num1+num2;
  }
});
// utils.js
define(function(require, exports, module) {
  var utils = {
    sub:function(num1, num2) {
      return num1-num2;
    }
  };

  module.exports = {
    utils: utils
  }
})

图片描述


2.CMD

SeaJS 是一个适用于 Web 浏览器端的模块加载器。这里就先不写了~

你们能够参考文档学习

SeaJs中文


4、展望将来

面向将来的ES6模块标准

2015年6月,ECMAScript2015也就是ES6发布了,JavaScript终于在语言标准的层面上,实现了模块功能,使得在编译时就能肯定模块的依赖关系,以及其输入和输出的变量,不像 CommonJS、AMD之类的须要在运行时才能肯定(例如FIS这样的工具只能预处理依赖关系,本质上仍是运行时解析),成为浏览器和服务器通用的模块解决方案。

更多关于ES6 Modules的资料,能够看一下ES6阮一峰


感谢您的支持!!
相关文章
相关标签/搜索