js中的模块化
前阵子一直忙着找实习,发现已经有一段时间没写博客了,面试很多时候会被问到模块化,今天就让我们一起来总结下把
一、什么是模块化
在js出现的时候,js一般只是用来实现一些简单的交互,后来js开始得到重视,用来实现越来越复杂的功能,而为了维护的方便,我们也把不同功能的js抽取出来当做一个js文件,但是当项目变的复杂的时候,一个html页面可能需要加载好多个js文件,而这个时候就会出现各种命名冲突,如果js也可以像java一样,把不同功能的文件放在不同的package中,需要引用某个函数或功能的时候,import下相关的包,这样可以很好的解决命名冲突等各种问题,但是js中没有模块的概念,又怎么实现模块化呢
模块化开发是一种管理方式,是一种生产方式,一种解决问题的方案,一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块,但是模块开发需要遵循一定的规范,否则就都乱套了,因此,才有了后来大家熟悉的AMD规范,CMD规范
接下来,我们就一起学习下AMD,CMD和es6中的模块化吧
二、AMD
AMD 即Asynchronous Module Definition,中文名是“异步模块定义”的意思,它采用异步方式加载模块,模块的加载不影响它后面语句的运行,所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行
一般来说,AMD是 RequireJS 在推广过程中对模块定义的规范化的产出,因为平时在开发中比较常用的是require.js进行模块的定义和加载,一般是使用define来定义模块,使用require来加载模块
1、定义模块
AMD规范只定义了一个函数define,它是全局变量,我们可以用它来定义一个模块
define(id?, dependencies?, factory);
其中,id是定义中模块的名字,这个参数是可选的,如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字,如果提供了该参数,模块名必须是“顶级”的和绝对的
dependencies是定义的模块中所依赖模块的数组,依赖模块必须根据模块的工厂方法优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂方法中
factory是模块初始化要执行的函数或对象,如果为函数,它应该只被执行一次,如果是对象,此对象应该为模块的输出值
下面来看一个定义模块的例子
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
上面的代码定义了一个alpha的模块,这个模块依赖require,exports,beta,因此需要先加载它们,再执行后面的factory
2、加载模块
require.js中采用require()语句加载模块,在定义好了模块后,我们可以使用require进行模块的加载
require([module], callback);
require要传入两个参数,第一个参数[module],是一个数组,里面的成员就是要加载的模块,第二个参数callback,则是加载成功之后的回调函数
下面我们来看一个例子
require([increment'], function (increment) {
increment.add(1);
});
上面的代码中,比如我们现在已经定义了一个模块,名字为increment,里面有一个add方法,我们现在需要用到里面的方法,只要像上面一样将模块加载进来,然后调用方法就可以了
3、requirejs使用例子
在使用require.js时,可以通过define()定义模块,这时候里面的模块的方法和变量外部是无法访问到的,只有通过return,然后再加载这个模块,才可以进行访问
define('math',['jquery'], function ($) {//引入jQuery模块
return {
add: function(x,y){
return x + y;
}
};
});
上面的代码定义了一个math模块,返回了一个add方法,要使用这个模块的方法,我们需要向下面这样进行访问
require(['jquery','math'], function ($,math) {
console.log(math.add(10,100));//
});
通过require,我们加载了math模块,这样就可以使用math模块里面的add方法了
三、CMD
define(factory);
define接受factory参数,factory可以是一个函数,也可以是一个对象或字符串
factory为对象、字符串时,表示模块的接口就是该对象、字符串,比如可以如下定义一个 JSON 数据模块
define({ "foo": "bar" });
也可以通过字符串定义模板模块
define('I am a template. My name is {{name}}.');
factory为函数时,表示是模块的构造方法,执行该构造方法,可以得到模块向外提供的接口,factory方法在执行时,默认会传入三个参数:require,exports和 module
define(function(require, exports, module) { // 模块代码 });
其中,require用来加载其它模块,而exports可以用来实现向外提供模块接口
define(function(require, exports) { // 对外提供 foo 属性
exports.foo = 'bar'; // 对外提供 doSomething 方法
exports.doSomething = function() {}; });
module是一个对象,上面存储了与当前模块相关联的一些属性和方法,传给factory构造方法的exports参数是module.exports对象的一个引用,只通过exports参数来提供接口,有时无法满足开发者的所有需求,比如当模块的接口是某个类的实例时,需要通过module.exports来实现
define(function(require, exports, module) { // exports 是 module.exports 的一个引用
console.log(module.exports === exports); // true // 重新给 module.exports 赋值
module.exports = new SomeClass(); // exports 不再等于 module.exports
console.log(module.exports === exports); // false });
说了这么多,相信大家可能有点乱,来个简单的例子,我们看看使用AMD和CMD定义的模块的写法
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
}) // AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
在上面的代码中,相信大家很容易可以看出区别吧,AMD和CMD都是通过define()定义模块,AMD需要把依赖的模块先写出来,可以通过return暴露接口,CMD在定义模块需要传入require,exports和module这几个参数,要加载某个模块时,使用require进行加载,要暴露接口时,可以通过exports,module.exports和return
2、加载模块
在前面定义模块时,我们说过,当factory为函数时,require会作为默认参数传递进去,而require可以实现模块的加载
require是一个方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口
define(function(require, exports) { // 获取模块 a 的接口
var a = require('./a'); // 调用模块 a 的方法
a.doSomething(); });
从上面定义模块和加载模块的方式上,我们也可以看出AMD和CMD主要有下面几个不同:
// 定义模块 myModule.js
define(function(require, exports, module) {
var $ = require('jquery.js')
$('div').addClass('active');
exports.data = 1;
}); // 加载模块
seajs.use(['myModule.js'], function(my){
var star= my.data;
console.log(star); //
});
上面的代码中定义了myModule.js模块,因为该模块依赖于jquery.js,因此在需要使用该模块时可以使用require进行模块的加载,然后通过exports暴露出接口,通过SeaJS的use方法我们可以加载该模块,并且使用该模块暴露出的接口
四、es6中的模块化
在es6没有出来之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种,前者用于服务器,后者用于浏览器,ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案
es6中的模块化有一个比较大的特点,就是实现尽量的静态化,比如说在CommonJS中我们要加载fs中的几个方法,需要这样写
// CommonJS模块
let { stat, exists, readFile } = require('fs'); // 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
上面的代码其实是加载了fs中的所有方法,生成一个对象,再从这个对象上读取方法,这种加载其实叫做运行时加载,也就是只有运行时才能得到这个对象,不能实现在编译时实现静态优化
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入
// ES6模块
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其他方法不加载,这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高,当然,这也导致了没法引用 ES6 模块本身,因为它不是对象
1、export
模块功能主要由两个命令构成:export和import,export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能
一般来说,一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取,如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
如果要输出函数,可以像下面这样定义
function v1() { ... }
function v2() { ... } export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
上面的代码中,我们使用了as对函数的对外接口进行了重命名
2、import
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
// main.js
import {firstName, lastName, year} from './profile.js'; function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同
我们也可以对加载的模块进行重命名
import { lastName as surname } from './profile.js';
除了指定加载某个输出值,还可以使用整体加载,即用星号(*
)指定一个对象,所有输出值都加载在这个对象上面
下面是一个circle.js文件,它输出两个方法area和circumference
// circle.js export function area(radius) {
return Math.PI * radius * radius;
} export function circumference(radius) {
return 2 * Math.PI * radius;
}
整体加载的写法如下
import * as circle from './circle'; console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
这里有一个地方需要注意,模块整体加载所在的那个对象(上例是circle
),应该是可以静态分析的,所以不允许运行时改变,下面的写法都是不允许的
import * as circle from './circle'; // 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};
关于import其实还有很多用法,具体的大家可以查看相关的文档
今天就先介绍到这里,其实还有commonjs,还没有进行介绍,如果大家感兴趣,可以查看相关的用法呢
js中的模块化的更多相关文章
- Node.js中的模块化
每天一篇文章来记录记录自己的成长吧.大二,该静心了.加油~ 好了,废话不多说,今天说说nodejs中的模块化.(注:此文为自己对书nodejs实战的总结) nodejs一个重要的特性就是模块化,模块就 ...
- js中的模块化编写思维
作为一个新手程序员,在编程时一定要刻意锻炼自己的模块化编写思路,但是究竟什么才是模块化编写对于新人来说还是不太能够直观的理解,下面就举个简单的例子来说明一下 概念:最早接触模块化的说法是从java上, ...
- 浅析JS中的模块规范(CommonJS,AMD,CMD)////////////////////////zzzzzz
浅析JS中的模块规范(CommonJS,AMD,CMD) 如果你听过js模块化这个东西,那么你就应该听过或CommonJS或AMD甚至是CMD这些规范咯,我也听过,但之前也真的是听听而已. ...
- JS中的模块规范(CommonJS,AMD,CMD)
JS中的模块规范(CommonJS,AMD,CMD) 如果你听过js模块化这个东西,那么你就应该听过或CommonJS或AMD甚至是CMD这些规范咯,我也听过,但之前也真的是听听而已. 现在就看看吧, ...
- js中的AMD规范
回首萧瑟,残月挂角,孤草弄影. 看了一下上一篇随笔的日期,距离上一篇日志又过去了许久.在这段时间中,我尽全力去拯救那间便利店,可惜到最后依然失败,这一次是所有的出路全部没有了,我也做了所有的努力.闲下 ...
- js中的面向对象入门
什么是对象 我们先来看高程三中是如何对对象进行定义的 "无序属性的集合,其属性可以包括基本值.对象或者函数",对象是一组没有特定顺序的的值.对象的没个属性或方法都有一个俄名字,每个 ...
- Node.js学习(第一章:Node.js安装和模块化理解)
Node.js安装和简单使用 安装方法 简单的安装方式是直接官网下载,然后本地安装即可.官网地址:nodejs.org Windows系统下,选择和系统版本匹配的.msi后缀的安装文件.Mac OS ...
- JS中的闭包(closure)
JS中的闭包(closure) 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.下面就是我的学习笔记,对于Javascript初学者应该是很有用 ...
- jQuery 对AMD的支持(Require.js中如何使用jQuery)
AMD 模块 AMD(异步模块定义,Asynchronous Module Definition)格式总体的目标是为现在的开发者提供一个可用的模块化 JavaScript 的解决方案. AMD 模块格 ...
随机推荐
- linux就该这么学,第十天了
今天老师主要让要考试的提前预习课程了,提前预习, 今天讲了,防火墙,iptable.firewall-config,firewall-cmd 防火墙和网卡的配置方法,四种,1配置文件方法,主要开启 ...
- leveldb 学习记录(四)Log文件
前文记录 leveldb 学习记录(一) skiplistleveldb 学习记录(二) Sliceleveldb 学习记录(三) MemTable 与 Immutable Memtablelevel ...
- 相对于父元素的fixed定位的实现
问题描述 之前在项目中,遇到了一个场景,需要实现相对于父元素的fixed定位:在父元素内拖动滚动条时,"fixed"定位的元素不能滑动,在外层拖动滚动条时,父元素及父元素内的所有元 ...
- 《Linux就该这么学》第五天课程
今天我很迷茫! 一下是一些命令的整合 原创地址:https://www.linuxprobe.com/chapter-04.html 下面是Linux系统中最重要的10个环境变量 变量名称 作用 HO ...
- CUDA[4] sample program: matrix-vector multiplication
Use Compressed Sparse Row Format (CSR) to represent matrix #include "cuda_runtime.h" #incl ...
- java框架学习_mybaties
Mybatis第一天 框架课程 1. 课程计划 第一天: 1.Mybatis的介绍 2.Mybatis的入门 a) 使用jdbc操作数据库存在的问题 b) Mybatis的架构 c) Mybati ...
- Maven二
1 回顾 1.1 Maven的好处 节省空间 对jar包做了统一管理 依赖管理 一键构建 可跨平台 应用在大型项目可提高开发效率 1.2 Maven安装部署配置 1.3 Maven的仓库 本地仓库 远 ...
- VDD,VCC,VSS,VEE,VDDA,VSSA,
VDD是主供电电源,也是IO口输出电平的输入电源.VDDA(A表示模拟)是模拟电源,当使用到模拟信号的时候,比如AD(模数)或者DA(数模)的时候,系统会使用VDDA的电压作为参考电压来.不要求精准使 ...
- OC 中property的使用
property在使用的时候需要在其前面加上@符号,需要将其写在类的头文件中 当在定义一个对象的属性变量时,例如定义一个Person类中的name属性,可定义为: @property (nonatom ...
- delphi如何在form显示出来后处理指定的事件(例如自动登录)
最近写一个delphi客户端,遇到一个自动登录问题,已经解决了思路如下: 1.在Form的oncreate事件中读取用户配置文件,检查及处理是否保存了用户密码,是否自动登录,如果需要自动登录, 自动登 ...