【基础知识】前端模块化

开发者能够只须要实现业务逻辑,其余可加载别人写好的公共模块。javascript

1、script引入文件

<script src="jquery.js"></script>
<script src="jquery_scroller.js"></script>
<script src="bootstarp.js"></script>
<script src="commutil.js"></script>
<script src="main.js"></script>
复制代码

①最原始的写法

/** commutil.js(main.js)中: 模块就是实现特定功能的一组方法。 只要把不一样的函数简单地放在一块儿,就算是一个模块。 */
function fun1(){
    ...
}
function fun2(){
    ...
}
...
复制代码

缺点:前端

  1. 全部的模块都处于全局做用域下, 容易形成命名冲突
  2. 依赖关系不明显, 好比 main.js 中有使用 jquery, 那么 jquery 就必定要先加载,
  3. 可是从引入方式中咱们没法直观的察觉依赖关系, 不利于维护

②将上述的多个方法写在一个对象中

var obj = new Object({
     _count : 0,
     fun1 : function (){
        ...
     },
     fun2 : function (){
        ...
     }
 });
复制代码
//调用方法
obj.fun1()
obj.fun2()
复制代码

缺点:java

  1. 这样的写法会暴露全部模块成员,内部状态能够被外部改写。
  2. 好比,外部代码能够直接改变内部计数器的值。
obj._count = 1;
复制代码

③当即执行函数写法

使用”当即执行函数”(Immediately-Invoked Function Expression,IIFE),能够达到不暴露私有成员的目的。node

var obj = (function() {
    var _count = 0;
    var fun1 = function() {
        alert(_count)
    }
    var fun2 = function() {
        alert(_count + 1)
    }   
    return {
        fun1, fun2
    }
})()
复制代码

使用上面的写法,外部代码没法读取内部的_count变量。jquery

console.info(obj._count);  //undefined
//调用内部函数能够
obj.fun1()
复制代码
  • jQuery用的就是当即执行函数。
  • 当即执行函数经常使用于第三方库,好处在于隔离做用域。
  • 任何一个第三方库都会存在大量的变量和函数,为了不变量污染(命名冲突),【各类模块化概念出来以前】开发者们想到的解决办法就是使用当即执行函数。

2、require导入模块【运行时加载】

随着web项目愈来愈大,JS的代码量也与日俱增,因而社区就自发约定了几种模块化的方案:requirejs遵循AMDseajs遵循CMDnodemodule遵循CommonJS规范,虽然写法上有所不一样,都是为了可以间接实现模块化的基础上保持较为一致的代码风格。程序员

  • 2009年,美国程序员Ryan Dahl 创造了node.js项目,将javascript语言用于服务器端编程。 这标志“Javascript模块化编程”正式诞生。
  • 前端的复杂程度有限,没有模块也能够,但服务器端,须要与操做系统和其余应用程序互动,没有有模块,根本无法编程。
  • node编程最重要的思想之一就是模块,正是这个思想,让JavaScript的大规模工程成为可能。
  • 模块化编程在js界流行,也是基于此,随后在浏览器端,requirejsseajs之类的工具包也出现了。
  • 能够说在对应规范下,require统治了ES6以前的全部模块化编程,即便如今,在ES6 module被彻底实现以前,仍是这样。

①CommonJS

一个文件就是一个模块, 其内部定义的变量, 方法都处于该模块内, 不会对外暴露.es6

//新建 a.js, 导出sayHello和introSelf
// a.js
function sayHello(name) {
    console.log(`Hello ${name}`)
}
function introSelf(name, age){
    var msg = "myName is " + name + " and I'm " + age + " old";
    console.log(msg);
}
module.exports.sayHello = sayHello
module.exports.introSelf = introSelf
/**或 module.exports = { sayHello: sayHello, introSelf: introSelf } */

//在 b.js 中引入 a 并调用
// b.js
const a = require('./a')
a.sayHello('Lucy') 
a.introSelf('Lucy', 20) 
复制代码
  • 因为 CommonJs 是同步加载的模块的, 在服务端(node), 文件都在硬盘上, 因此同步加载也无所谓, 可是在浏览器端, 同步加载就体验很差了. 因此 CommonJs 主要使用于 node 环境下.
  • 可是,对于浏览器,这倒是个大问题,由于模块都放在服务器端,等待时间取决于网速的快慢,若是等待时间过长,浏览器会处于“假死”状态。
  • 所以,浏览器端的模块,不能采用”同步加载”(synchronous),只能采用”异步加载”(asynchronous)。这就是AMD规范诞生的背景。

②AMD 和 CMD

  1. AMDAsynchronous Module Definition的缩写,意思就是”异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。全部依赖这个模块的语句,都定义在一个回调函数中,等到加载完成以后,这个回调函数才会运行。
  2. CMD (Common Module Definition),阿里的玉伯提出, 是seajs推崇的规范,CMD则是依赖就近,用的时候再require(实现了按需加载)。 模块必须采用特定的define()函数来定义。
  3. AMDCMD模块必须采用特定的define()函数来定义。
define(id?, dependencies?, factory)
    /** - id:字符串,模块名称(可选) - dependencies: 是咱们要载入的依赖模块(可选), 使用相对路径。,注意是数组格式 - factory: 工厂方法,返回一个模块函数 */
复制代码

若是一个模块不依赖其余模块,那么能够直接定义在define()函数之中。web

  1. 对于依赖的模块 AMD 推崇依赖前置(js能够方便知道依赖模块是谁,当即加载),而 CMD 推崇依赖就近(须要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是不少人诟病CMD的一点)
//AMD2.0以前
 require(['./a', './b'], function(a, b) {
 a.doSomething();
 b.doSomething();
 })
 // AMD2.0以后
 define(['./a', './b'], function(a, b) {
 a.doSomething();
 b.doSomething();
 })
 
 // CMD
 define(function(require, exports, module) {
 var a = require('./a');
 a.doSomething();
 var b = require('./b');
 b.doSomething();
 })
复制代码
  1. 调用模块
require([module], callback);
复制代码
//若有一个math.js
require(['math'], function (math) {
  math.add(2, 3);
});
复制代码

3、ES6 MODULES【编译时加载】

随着ES2015的发布,官方标准定义了一种模块化的方案,那就是importexport。但是,标准毕竟是标准,各大浏览器和node终端要实现标准仍是有一段距离的。编程

  1. 阮一峰ES6-export命令
  • 模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,import命令用于输入其余模块提供的功能。
  • 一个模块就是一个独立的文件。该文件内部的全部变量,外部没法获取。若是你但愿外部可以读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
复制代码
  • 上面代码是profile.js文件,保存了用户信息。ES6 将其视为一个模块,里面用export命令对外部输出了三个变量。
  • export的写法,除了像上面这样,还有另一种。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
复制代码
  • 导入模块
import { firstName, lastName, year } from './profile.js';
//也能够用as
import { lastName as surname } from './profile.js';
复制代码
  • 上面代码在export命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在var语句前)是等价的,可是应该优先考虑使用这种写法。由于这样就能够在脚本尾部,一眼看清楚输出了哪些变量。
  • export命令除了输出变量,还能够输出函数或类(class)。
export function multiply(x, y) {
  return x * y;
};
复制代码
  • 上面代码对外输出一个函数multiply
  • 一般状况下,export输出的变量就是原本的名字,可是可使用as关键字重命名。
function v1() { ... }
function v2() { ... }
export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
复制代码
  • 上面代码使用as关键字,重命名了函数v1v2的对外接口。重命名后,v2能够用不一样的名字输出两次。
  • 须要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量创建一一对应关系。
// 报错
export 1;

// 报错
var m = 1;
export m;
复制代码
  • 上面两种写法都会报错,由于没有提供对外的接口。第一种写法直接输出 1,第二种写法经过变量m,仍是直接输出 1。1只是一个值,不是接口。正确的写法是下面这样。
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};
复制代码
  • 上面三种写法都是正确的,规定了对外的接口m。其余脚本能够经过这个接口,取到值1。它们的实质是,在接口名与模块内部变量之间,创建了一一对应的关系。
  • 一样的,functionclass的输出,也必须遵照这样的写法。
// 报错
function f() {}
export f;

// 正确
export function f() {};

// 正确
function f() {}
export {f};
复制代码
  1. 阮一峰ES6-export default命令
  • 从前面的例子能够看出,使用import命令的时候,用户须要知道所要加载的变量名或函数名,不然没法加载。可是,用户确定但愿快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
  • 为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
// export-default.js
export default function () {
  console.log('foo');
}
复制代码
  • 上面代码是一个模块文件export-default.js,它的默认输出是一个函数。
  • 其余模块加载该模块时,import命令能够为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
复制代码
  • 上面代码的import命令,能够用任意名称指向export-default.js输出的方法,这时就不须要知道原模块输出的函数名。须要注意的是,这时import命令后面,不使用大括号。
  • export default命令用在非匿名函数前,也是能够的。
// export-default.js
export default function foo() {
  console.log('foo');
}

// 或者写成

function foo() {
  console.log('foo');
}

export default foo;
复制代码
  • 上面代码中,foo函数的函数名foo,在模块外部是无效的。加载的时候,视同匿名函数加载。
相关文章
相关标签/搜索