前端模块化总结
栏目: JavaScript · 发布时间: 5年前
内容简介:开发者可以只需要实现业务逻辑,其他可加载别人写好的公共模块。缺点:缺点:
开发者可以只需要实现业务逻辑,其他可加载别人写好的公共模块。
一、 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(){ ... } ... 复制代码
缺点:
- 所有的模块都处于全局作用域下, 容易造成命名冲突
- 依赖关系不明显, 比如 main.js 中有使用
jquery
, 那么jquery
就一定要先加载, - 但是从引入方式中我们无法直观的察觉依赖关系, 不利于维护
②将上述的多个方法写在一个对象中
var obj = new Object({ _count : 0, fun1 : function (){ ... }, fun2 : function (){ ... } }); 复制代码
//调用方法 obj.fun1() obj.fun2() 复制代码
缺点:
- 这样的写法会暴露所有模块成员,内部状态可以被外部改写。
- 比如,外部代码可以直接改变内部计数器的值。
obj._count = 1; 复制代码
③立即执行函数写法
使用”立即执行函数”(Immediately-Invoked Function Expression, IIFE
),可以达到不暴露私有成员的目的。
var obj = (function() { var _count = 0; var fun1 = function() { alert(_count) } var fun2 = function() { alert(_count + 1) } return { fun1, fun2 } })() 复制代码
使用上面的写法,外部代码无法读取内部的_count变量。
console.info(obj._count); //undefined //调用内部函数可以 obj.fun1() 复制代码
jQuery
二、 require
导入模块【运行时加载】
随着 web
项目越来越大, JS
的代码量也与日俱增,于是社区就自发约定了几种模块化的方案: requirejs
遵循 AMD
, seajs
遵循 CMD
, node
的 module
遵循 CommonJS
规范,虽然写法上有所不同,都是为了能够间接实现模块化的基础上保持较为一致的代码风格。
- 2009年,美国程序员
Ryan Dahl
创造了node.js
项目,将javascript
语言用于服务器端编程。 这标志“Javascript模块化编程”正式诞生。 - 前端的复杂程度有限,没有模块也可以,但服务器端,需要与操作系统和其他应用程序互动,没有有模块,根本没法编程。
-
node
编程最重要的思想之一就是模块,正是这个思想,让JavaScript
的大规模工程成为可能。 - 模块化编程在
js
界流行,也是基于此,随后在浏览器端,requirejs
和seajs
之类的 工具 包也出现了。 - 可以说在对应规范下,
require
统治了ES6
之前的所有模块化编程,即使现在,在ES6 module
被完全实现之前,还是这样。
①CommonJS
一个文件就是一个模块, 其内部定义的变量, 方法都处于该模块内, 不会对外暴露.
//新建 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
-
AMD
是Asynchronous Module Definition
的缩写,意思就是”异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。 -
CMD (Common Module Definition)
,阿里的玉伯提出, 是seajs推崇的规范,CMD
则是依赖就近,用的时候再require
(实现了按需加载)。 模块必须采用特定的define()
函数来定义。 -
AMD
和CMD
模块必须采用特定的define()函数来定义。
define(id?, dependencies?, factory) /** - id:字符串,模块名称(可选) - dependencies: 是我们要载入的依赖模块(可选), 使用相对路径。,注意是数组格式 - factory: 工厂方法,返回一个模块函数 */ 复制代码
如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。
- 对于依赖的模块
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(); }) 复制代码
- 调用模块
require([module], callback); 复制代码
//如有一个math.js require(['math'], function (math) { math.add(2, 3); }); 复制代码
三、ES6 MODULES【编译时加载】
随着ES2015的发布,官方标准定义了一种模块化的方案,那就是import、export。可是,标准毕竟是标准,各大浏览器和node终端要实现标准还是有一段距离的。
- 模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,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关键字,重命名了函数v1和v2的对外接口。重命名后,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。它们的实质是,在接口名与模块内部变量之间,建立了一一对应的关系。
- 同样的,function和class的输出,也必须遵守这样的写法。
// 报错 function f() {} export f; // 正确 export function f() {}; // 正确 function f() {} export {f}; 复制代码
- 从前面的例子可以看出,使用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,在模块外部是无效的。加载的时候,视同匿名函数加载。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。