推荐博客
30分钟学会前端模块化开发
common js规范
不同于java,JavaScript在早期的设计中,就没有模块、包、类的概念,所以需要开发者模拟出类似的功能,来隔离、阻止复杂的JavaScript代码,这个过程被称之为模块化。
模块化开发是当下最重要的前端开发范式之一。
模块就是一个实现特定功能的文件,在开发的时候,需要什么功能,便将这个功能引入进来。
模块化开发的四点好处:
为了实现模块化开发,经历了几个历程:
又有人叫他全局function模式 ,即将不同的功能封装成不同的全局函数。其实就是函数封装。
将每个功能以及它相关的一些状态数据,单独存放到不同的文件当中,约定每一个文件就是一个独立的模块。使用这个模块,就将这个模块引入到页面中,然后直接调用模块中的成员(变量/ 函数)。
一个script标签就对应一个模块,所有模块都在全局范围内工作。
在以上的例子里,index.html文件同时引用了module1.js和module2.js两个文件,但是函数foo1命名冲突了。于是后引用的module2.js的foo1函数会覆盖掉module1.js里的foo1函数。
控制台里显示如图。
缺点:
污染全局作用域
命名冲突问题
无法管理模块依赖关系
早期模块化完全依靠约定。
又叫namespace模式,即对象封装。
具体做法: 我们约定每个模块只暴露一个全局的对象,我们所有的模块成员都挂载到这个全局对象下面。
在第一阶段的基础上,通过将每个模块「包裹」为一个全局对象的形式实现,有点类似于为模块内的成员添加了「命名空间」的感觉。
module1.foo1();
module2.foo1();
在module1.js中
var module1 ={
var1:1,
foo1:function(){
console.log('module1.js的foo1函数,var1为'+module1.var1)
}
}
var module2 ={
var1:100,
foo1:function(){
console.log('module2.js的foo1函数,var1为'+module2.var1)
}
}
module1.var1 = 5000;
缺点:
没有私有空间
模块成员仍然可以在外部被访问/修改
无法管理模块依赖关系
即匿名函数自调用(闭包)。
使用立即执行函数的方式,去为我们的模块提供私有空间。将模块中每个成员都放在一个函数提供的私有作用域当中,对于需要暴露给外部的成员,我们可以通过挂载到全局对象上的这种方式去实现。确保了私有成员的安全。
有了私有成员的概念,私有成员只能在模块成员内通过闭包的形式访问。
我的理解是,写一个立即执行函数,形成一个私有空间,把想要访问的成员添加到全局对象window中暴露,以闭包的形式访问函数。没有暴露的成员,不能访问和修改。
例如在module1.js中写
(function(){
var var1 = 1;
function foo1(){
console.log('module1.js的foo1函数,var1为'+var1)
}
window.module1={
foo1,
}
})()
在module2.js中写
(function(){
var var1 = 1;
function foo1(){
console.log('module2.js的foo1函数,var1为'+var1)
}
window.module2={
foo1,
}
})()
此时因为将foo1暴露了,就可以调用了。
但是因为我没有把var1这个值暴露,所以无法修改,即使加上一行:
module1.var1 = 5000;
结果也没有改变。
把jquery库当作参数传入,这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。
我的理解就是用jquery。
方式类似:
(function(window, $) {
function foo1() {
var var1 = 1;
console.log('module1.js的foo1函数,var1为'+var1)
}
window.module1 = {
foo1,
}
})(window, jQuery)
常见的的JavaScript模块规范有:CommonJS、AMD、CMD、UMD、原生模块化。
在讲解这些之前,还要了解一个软件:Node.js。
是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O模型, 让JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。
Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好,V8引擎执行Javascript的速度非常快,性能非常好,基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。
node.js
官方介绍
Node.js安装教程
Node.js配置
Node.jsAPI文档
配置过程经过整理,大致是以下步骤:
npm install browserify -g开发依赖
npm install browserify --save-dev运行依赖
默认监听的端口号是3000,想要更改端口号的话,在myapp/bin文件夹里,将文件WWW重命名为WWW.txt打开,在第十五行修改,将3000改成你喜欢的端口号:
随后再将WWW.txt改回WWW,重启服务器就可以了。
Node中模块分为两类:一类是Node提供的模块,称为核心模块;另一类是用户编写的模块,称为文件模块。
我们在app.js里编写的,是文件模块。
nodejs是区别于javascript的,在javascript中的顶层对象是window,而在node中的顶层对象是global。
实际上,javascript也存在global对象,只是其并不对外访问,而使用window对象指向global对象而已。
区别演示:
var a = 100;
window.a=200;
console.log(a);
console.log(window.a);
在node.js中写
var a = 100;
global.a=200;
console.log(a);
console.log(global.a);
在nodejs中,一个文件就是一个模块,每个模块都有自己的作用域。使用var来声明的一个变量,它并不是全局的,而是属于当前模块下。
所以a≠global.a。
common js通常运行在Node.js上。
每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。
module
exports
require
global
CommonJS规范规定:
每个模块内部,module变量代表当前模块。
这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。
加载某个模块,其实是加载该模块的module.exports属性。
module.exports.x = 5;
require命令用于加载模块文件。require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。
一个例子。想要加载相对路径下的lib文件:
var counter = require('./lib');
(require命令会自动省略掉.js的后缀,Node会先查找是否存在没有后缀的该文件,如果没有,再按.js、.json、.node的次序补足扩展名,依次尝试)
根据命令不同,require加载的路径也不同。
app.js
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
module1.foo1();
module2();
console.log(module1.msg)
module1.js代码
module.exports={
msg:"这是暴露的信息",
foo1(){
console.log(this.msg);
}
}
module2.js代码
module.exports = function(){
console.log("直接暴露一个函数");
}
结果
第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。
var module1 = require('./modules/module1');
console.log(module1.msg);
//加载一次,并将msg打印出来
module1.msg="hello";
console.log(module1.msg);
//修改msg重新打印一次
require('./modules/module1');
console.log(module1.msg);
//重新加载一次再打印
结果是,即使最后重新加载了一次模块,msg仍然是缓存中的hello。
所有缓存的模块保存在require.cache之中,如果想删除模块的缓存,可以像下面这样写。
// 删除指定模块的缓存
delete require.cache[moduleName];
// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})
一些没讲到的
个人认为这些都是没那么重要、或比较进阶的东西,不详细说了。
AMD((Asynchromous Module Definition) 异步模块定义。AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出。
它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
define(name, dependencies, factory);
// module1
define('module1', function() {
// ...
return {
add: function() {
return ...;
}
// ...
}
});
// module2
define('module2',['module1'] function(module1) {
// 使用module1模块中的方法
module1.add();
// ...
return {
// ...
}
});
AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
require([module], callback);
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。
require(['math'], function (math) {
math.add(2, 3);
}
math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。
还可以吧jquery当做模块传过去:
define('myModule', ['jquery'], function($) {
// $ 是 jquery 模块的输出
$('body').text('hello world');
});
// 使用
require(['myModule'], function(myModule) {});
AMD允许兼容commonJS的规范。
define(function (require, exports, module) {
var reqModule = require("./someModule");
requModule.test();
exports.asplode = function () {
//someing
}
});
相关博客AMD规范
CMD和AMD的区别
CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同
CMD推崇依赖就近,所以一般不在define的参数中写依赖
define(function(require,exports,module)
{}
)
//CMD
define(function(require,exports,module){
var a=require('./a')
a.doSomethimg()
var b=require('./b')
b.doSomething()
})
//AMD
define(['./a','./b'],function(a,b){
a.doSomething()
b.dosomething()
})
RequireJS是一个工具库,主要用于客户端的模块管理。它的模块管理遵守AMD规范。
开发时经常会遇到这种情况:
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>
引入的js文件过多,会导致很多问题:
请求过多
首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多。加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长。
依赖模糊
我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。
比如jquery的文件必须放在最前面引用。
以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。
实现js文件的异步加载,避免网页失去响应;
管理模块之间的依赖性,便于代码的编写和维护。
官网下载链接
下载require.js文件后,在网页中引用。
加载这个文件,也可能造成网页失去响应。解决办法有两个,一个是把它放在网页底部加载,另一个是写成下面这样:
<script src="js/require.js" defer async="true" ></script>
async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上。
加载require.js以后,下一步就要加载我们自己的代码了。我的代码名为main.js。
<script src="js/require.js" defer async="true" data-main="js/main"></script>
data-main属性的作用是,指定网页程序的主模块。main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。
main.js,我把它称为"主模块",意思是整个网页的入口代码。
require(['module1', 'module2'], function (module1, module2) {
console.log(module1.msg1);
console.log(module2.msg2);
});
配置:
require.config({
paths: {
module1:"./modules/module1",
module2:"./modules/module2"
}
});
在paths中,为依赖的模块指定路径。(不用加.js后缀)
在module1.js里的写法:
define(function() {
msg1 = 1;
return {
msg1,
}
});
module2.js
define(function() {
msg2 = 2;
return {
msg2,
}
requirejs
怎么提高代码质量?-来自Google的研发经验总结
如何提高JavaScript代码质量(一)
如何提高JavaScript代码质量(二)