作为一个前端方向的探路者,近期写项目应用到模块化开发的知识。参考别人项目加上之前自己的经验积累,我发现自己对require、export、module、define这些模块化用到的语法词汇有些混淆,于是决定一探究竟。
前端模块化路径:函数封装(缺点:污染了全局变量)——>对象的写法(缺点:外部可以随意修改内部成员)——>立即执行函数
经查阅,前端模块化有以下几种:common.js规范、AMD/CMD规范、UMD规范、ES6module。下面依次介绍这几种规范。
一、common.js规范——node.js采用
//require方法默认读取js文件,所以可以省略js后缀
var test = require('./foobar').foobar;
test.bar();
//私有变量
var test = 123;
//公有方法
function foobar () {
this.foo = function () {
// do someing ...
}
this.bar = function () {
//do someing ...
}
}
//exports对象上的方法和变量是公有的
var foobar = new foobar();
exports.foobar = foobar;
(一)要点:
1、CommonJs 是服务器端模块的规范
2、CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。
3、加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。
{
id: '...', //id是模块名
exports: { ... }, //exports是该模块导出的接口
loaded: true, //loaded表示模块是否加载完毕。
...
}
4、根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在一个文件定义的变量(还包括函数和类),都是私有的,对其他文件是不可见的。
(二)适用性:
像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。
(三)浏览器环境能不能用
得力于 Browserify 这样的第三方工具,我们可以在浏览器端使用采用CommonJS规范的js文件。
二、AMD/CMD规范
(一) AMD异步模块加载规范——require.js
//通过数组引入依赖 ,回调函数通过形参传入依赖
define(['someModule1', ‘someModule2’], function (someModule1, someModule2) {
function foo () {
/// someing
someModule1.test();
}
return {foo: foo}
});
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。 但是,加载内部是同步的(加载完模块后立即执行回调)。
(二)CMD通用模块加载规范——SeaJS
//require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接
//口:require(id)
//exports 是一个对象,用来向外提供模块接口
//module 是一个对象,上面存储了与当前模块相关联的一些属性和方法
define(function (require, exports, module) {
// 依赖可以就近书写
var a = require('./a');
a.test();
// ...
if (status) {
// 依赖可以就近书写
var b = requie('./b');
b.test();
}
});
依赖可就近书写
(三)AMD和CMD的区别:
2、对依赖模块的执行时机处理不同:
-
同样都是异步加载模块,AMD在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行
-
CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的
三、UMD规范(通用模块规范)——AMD和CommonJS的糅合
(function (window, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function () {
//module ...
});
四、ES6module
(一)es6的设计思想:
尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出变量。而common.js和AMD模块都只能在运行时确定这些东西。所以es6效率比较高。
(二) es6的模块采用严格模式,所以有以下限制
- 变量必须声明后再使用;
- 函数的参数不能有同名属性,否则报错;
- 不能使用with语句;
- 不能对只读属性赋值;
- 不能使用前缀0表示八进制数;
- 不能删除不可删除的属性;
- 不能删除变量,只能删除属性;
- eval不会在其外层作用域引入变量;
- eval和arguments不能被重新赋值;
- arguments不会自动反映函数参数的变化;
- 不能使用arguments.callee、arguments.caller
- 禁止this指向全局对象;
- 不能使用fn.caller和fn.arguments获取函数调用的堆栈;
- 增加了保留字(如protected、static、interface)
(三)es6通过import、export实现模块的输入输出。
其中import命令用于输入其他模块提供的功能,export命令用于规定模块的对外接口。一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。
1、export
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName,lastName,year};
(1)export可以输出变量、函数或者类
(2)export导出的变量只能位于文件的顶层,如果处于块级作用域内,会报错。
(3)export语句输出的值是动态绑定,绑定其所在的模块。
export var foo='bar';
setTimeout(()=>foo='baz',500);
//代码输出变量foo,值为bar,500毫秒之后变成baz
(4)可以通过as
给变量函数等重新命名
2、import
import {firstName,lastName,year} from './profile';
(1)import命令具有提升效果
(2)import和export可以写在一起
3、模块的整体加载
import * as circle from './circle'
console.log("圆的面积:"+circle.area(4))
//其中area是circle文件里定义的函数
4、export default为模块指定默认输出
默认输出,import语句不需要使用大括号
(四)ES6模块加载的实质
1、CommonJS模块输出的是一个值的拷贝,而
es6模块输出的是值的引用。它并不会缓存运行结果,而是动态地从被加载的模块中取值
。
2、es6输出的模块变量只是一个“符号链接”,所以这个变量是只读的,对它进行重新赋值会报错。
(五)循环加载:
1、理解:a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。
2、commonjs和es6处理循环加载的原理:
- commonjs:
(1)模块加载原理:require命令第一次加载该脚本就会执行整个脚本,然后在内存中生成一个exports对象。以后需要用到这个模块时,就会到exports属性上取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存中取值。
(2)模块的循环加载:一旦出现某个模块被“循环加载”,就只输出已经执行的部分,未执行的部分不会输出。
- es6:
es6模块时动态引用,遇到模块加载明星import时不会去执行模块,只是生成一个指向被加载模块的引用,需要开发者自己保证真正取值时能够取到值。
今天的文章前端模块化之路分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/9912.html