JavaScript之无题之让人烦躁的模块化
我怎么记得我好像写过相关类型的文章,但是我找遍了我的博客没有~那就再写一遍吧,其实模块化的核心内容也算不上是复杂,只不过需要整理一下,规划一下罢了。嘻嘻。
开始写标题的时候我就在纠结一件事情,就是,先吃喜欢吃的,还是后吃喜欢吃的,翻译过来就是我应该先写CommonJS和ES6 Module,还是先写CMD和AMD。嗯,我决定了,谁先做好了我就先吃谁。
其实模块化的缘由很简单,就一句话,不对,就一个词,两个字,分类。如果一定让我在加一点,那应该是“隔离”。没了~~但是这么少不太好,我举个可能不那么恰当的例子吧。
刚开始这个世界上只有三个人,起名字的时候会“刻意”的避开彼此已经叫过的名字,他们在一起生活,日子欣欣向荣,一片美好,对未来充满了期待。
时间飞逝,三个人变成了三十人,他们勉强还是住在一起,扩建了房屋,起名字的时候虽然费事一点,但是也还能不重复,日子还是欣欣向荣,一片美好,对未来充满了期待。
时间又飞逝,三十人变成了三百人,那这不太好管理了,于是三位首领就说,你们有领导能力的几个人站出来,组成各自的部落,去吧,我相信你们可以的。于是每个部落住在一起,部落与部落之间的人可以重名,叫名字的时候再加上一个部落的名称呗,嗯~又一片欣欣向荣。
时间继续飞逝,三百人变成了三千人,这三千人住在几个大部落里也很不方便,你拿我的苹果,我偷了你得猪,这肯定不行,有碍于社会的稳定发展,于是三个创始者叫上部落的组长说,我们给每个人分一块地,盖一个房子,把三五个人分割成一个家庭,家庭之间由部落作为纽带,关联彼此,在形式上又相互独立,不可以随便拿别家的苹果。很完美~
时间飞飞飞飞逝,三千人变成了三百万人……我们需要法律了。
OK,上面的小例子,人,就是函数,部落就是命名空间,房子就是IIFE,法律就是后续发展的模块化规范。那么我们依照上面的描述,如何转换成代码?
一、社会的起源与法律的雏形
最开始的时候,浏览器只需要简单的图文展示就是可以了,没什么复杂的交互和逻辑的处理,所以,当我们只有三个人的时候,我们可以很自由,很随意:
function a(){} function b(){}
随着Web的发展,交互的增多,项目的扩大,很容易有人也声明了同样名称的函数,于是纷争开始了,那咋解决纷争呢?嗯,命名空间也就是拆分部落,就像这样:
var zaking1 = {
a:function(){},
b:function(){}
}
var zaking2 = {
a:function(){},
b:function(){}
}
但是这样并不能真正的解决问题,因为虽然从形式上区分了部落,但是部落之间没有任何的隔离,部落内部也是混乱的,所以各个首领就制定了一个方案,IIFE,利用闭包的特性,来实现数据的隔离,暴露出对外的入口:
var module = (function () {
var name = "zaking";
function getName() {
console.log(name);
}
return { getName };
})();
module.getName();
我们盖好了房子,还给房子建好了可以出入的门,但是我怎么邀请别人进来呢?
var module = (function (neighbor) {
var name = "zaking";
function getName() {
console.log(name + "和邻居:" + neighbor);
}
return { getName };
})("xiaowangba");
module.getName();
传个参数呗,这就是依赖注入。在这个阶段,最有代表性的就是jQuery了,它的封闭性的核心实现,跟上面的代码几乎无异,我们可以看下jQuery的模块的实现:
(function (global, factory) {
factory(global);
})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
if (typeof noGlobal === "undefined") {
window.jQuery = window.$ = jQuery;
}
return jQuery;
});
当然我这里略了很多,你看它,无非就是一个闭包,传入了window和jQuery本身,然后再绑定到window上,这样,我们就只能访问到暴露出来的$以及$上的方法和属性,我们根本无法修改内部的数据。
OK,到了这个阶段,其实算是一个转折点,我们有了初步的法律,还需要后续针对法律的完善,
二、法律的初现与CommonJs
随着社会的发展,出现一种规则已成必然,于是commonJs统领举起模块化的大旗,让JavaScript迈向了另一个阶段。commonJs最初由 JavaScript
社区中的 Mozilla
的工程师Kevin Dangoor
在Google Groups中创建了一个ServerJs小组。该组织的目标是为web服务器、桌面和命令行应用程序以及浏览器构建JavaScript生态系统。嗯,它的野心很大~,后来,他就就把ServerJs改成了commonJs,毕竟ServerJs的范围有点小,commonJs更符合他们的初衷。
而后,在同一年的年底,NodeJs出现了,Javascript不仅仅可以用于浏览器,在服务器端也开始攻城略地。NodeJs的初衷是基于commonJs社区的模块化规范,但是NodeJs并没有完全遵循于社区的一些腐朽过时的约束,它实现了自己的想法。
commonJs规范的写法,如果大家写过NodeJs一定都有所了解,大概是这样的:
// a.js
module.exports = 'zaking'
// b.js
const a = require("./a");
console.log(a); // zaking
看起来挺简单的,但是这里隐藏了一些不那么容易被理解的特性。
在NodeJs中,一个文件就是一个模块,有自己的作用域,在一个文件里面定义的函数、对象都是私有的,对其他文件不可见。并且,当第一次加载某个模块的时候,NodeJ会缓存该模块,待再次加载的时候,会直接从模块中取出module.exports属性返回。比如:
// a.js
var name = "zaking";
exports.name = name; // b.js
var a = require("./a.js");
console.log(a.name); // zaking
a.name = "xiaoba";
var b = require("./a.js");
console.log(b.name); // xiaoba
诶?为啥你写的是“exports.”,不是module.exports?NodeJs在实现CommonJs规范的时候为了方便,给每个模块都提供了一个exports私有变量,指向module.exports。有一点要尤其注意,exports
是模块内的私有局部变量,它只是指向了 module.exports
,所以直接对 exports
赋值是无效的,这样只是让 exports
不再指向 module.exports
了而已。
我们回到上面的代码,按理来说,我第二次引入的b的name应该是“zaking”啊。但是实际上,在第一次引入之后的引入,并不会再次执行模块的内容,只是返回了缓存的结果。
另外一个核心的点是,我们导入的是导出值的拷贝,也就是说一旦引入之后,模块内部关于该值的变化并不会被影响。
// a.js
var name = "zaking";
function changeName() {
name = "xiaowangba";
}
exports.name = name;
exports.changeName = changeName; // b.js
var a = require("./a.js");
console.log(a.name); // zaking
a.changeName();
console.log(a.name); // zaking
嗯,一切看起来都很不错。
三、争奇斗艳,百家争鸣
在上一小节,我们简单介绍了模块化的始祖也就是CommonJs以及实现了该规范的NodeJs的一些核心内容。但是NodeJs的实现的一个关键的点是,它在读取或者说加载模块的时候是同步的,这在服务器没什么问题,但是对于浏览器来说,这个问题就很严重,因为大量的同步模块加载意味着大量的白屏等待时间。
基于这样的问题,从CommonJs中独立出了AMD规范。
1、AMD规范与RequireJs
AMD,即Asynchronous Module Definition,翻译过来就是异步模块化规范,它的主要目的就是解决CommonJs不能在浏览器中使用的问题。但是RequireJs在实现上,希望可以通吃,也就是可以在任何宿主环境下使用。
我们先来看个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<script src="https://requirejs.org/docs/release/2.3.6/comments/require.js"></script>
<body></body>
<script>
require(["./a"]);
</script>
</html>
然后,我们的a.js是这样的:
define(function () {
function fun1() {
alert("it works");
} fun1();
});
define用来声明一个模块,require导入。我们还可以这样:
require(["./a"], function () {
alert("load finished");
});
导入前置依赖的模块,在第二个参数也就是回调中执行。RequireJs会在所有的模块解析完成后执行回调函数。就算你倒入了一个没有使用的模块,RequireJs也一样会加载:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<script src="https://requirejs.org/docs/release/2.3.6/comments/require.js"></script>
<body></body>
<script>
require(["./a", "./b"], function (a, b) {
a.fun1();
});
</script>
</html>
然后分别是a.js和b.js:
// a.js
define(function () {
function fun1() {
alert("it works fun1");
} return {
fun1: fun1,
};
}); // b.js
define(function () {
function fun2() {
alert("it works fun2");
} return {
fun2: fun2,
};
});
结果大家可以试一试~
以上就是RequireJs的简单用法,我们据此知道了两个核心内容,RequireJs基于AMD规范,RequireJs会加载你引入的所有模块,哪怕你并不会真的用到它。
2、CMD规范与SeaJs
由于RequireJs的一些问题,又出现了基于CMD规范的SeaJs,SeaJs和RequireJs有一个最大的不同就是RequireJs希望可以通吃,但是SeaJs则更专注于浏览器,哦对了,CMD的全称叫做:Common Module Definition,即通用模块规范。
SeaJs的简单用法如下:
// 所有模块都通过 define 来定义
define(function(require, exports, module) { // 通过 require 引入依赖
var a = require('xxx')
var b = require('yyy') // 通过 exports 对外提供接口
exports.doSomething = ... // 或者通过 module.exports 提供整个接口
module.exports = ... })
// a.js
define(function(require, exports, module){
var name = 'morrain'
var age = 18 exports.name = name
exports.getAge = () => age
})
// b.js
define(function(require, exports, module){
var name = 'lilei'
var age = 15
var a = require('a.js') console.log(a.name) // 'morrain'
console.log(a.getAge()) //18 exports.name = name
exports.getAge = () => age
})
上面的代码是从网上抄的,大概说明白了基本的使用方法。我们可以看到,SeaJs的导入和导出的方式,跟NodeJs好像~~而SeaJs从书写形式上,更像是CommonJs和AMD的结合。当然,我只是说书写形式上。
而AMD和CMD,RequireJs和SeaJs,都是由社区发起的,并没有语言层面的规范,包括CommonJs以及NodeJs,所以,这个时代还是一个百花争艳,没有统一的时代,不过在现在,这些都不重要了。如果非要我说些什么,那就是,忘记这两个东西,去学下面的重点。
四、大一统
百花争艳的时代确实有些烦人,这个那个那个这个,又都不被官方认可,还好,官方终于还是出手了,ES6的出现,在语言层面上就提出了对于模块化的规范,也就是ES6 Module。它太重要了,具体语法我就不多说了,文末的链接附上了阮一峰大神的《ES6入门指南》关于ES6 Module的地址。
所以到了ES6的时候,你要学习的就是ES6 Module,NodeJs也在逐步实现对ES6 Module的支持。最终,秦始皇会一统天下,这是必然的结果。
这篇文章到这里就结束了,说实话,模块化的问题和历史由来已久,从萌芽到统一,至少十几年的过程,而市面上也已有大量的文章介绍彼此的区别和各自的特点,我写来写去,也不过是复制一遍,毫无意义。
但是我又想学一下模块化,以及模块化的历史,额……,请原谅我的无知,所以才有了这篇文章,但是写着写着,发现我能表达出来的东西并不多,因为都是故事,都是历史,并且对于未来的开发好像也没什么实际的意义和价值。
所以,在如此纠结的心态下有了这篇文章,原谅我无知又想逼逼的心情吧。
最后的最后,如果你想学习模块化,在现阶段,只需要去深入学习ES6 Module,和学习一下NodeJs的CommonJs,以及了解一下各模块化的区别即可,因为现在是即将统一,还未完全统一的时候。
参考资料:
https://wiki.commonjs.org/wiki/CommonJS
https://github.com/seajs/seajs/issues/242
https://es6.ruanyifeng.com/#docs/module
JavaScript之无题之让人烦躁的模块化的更多相关文章
- 说说JSON和JSONP,也许你会豁然开朗,含jQuery用例 分类: JavaScript 2014-09-23 10:41 218人阅读 评论(1) 收藏
前言: 由于Sencha Touch 2这种开发模式的特性,基本决定了它原生的数据交互行为几乎只能通过AJAX来实现. 当然了,通过调用强大的PhoneGap插件然后打包,你可以实现100%的Sock ...
- UC浏览器 分享到朋友圈和微信好友 分类: JavaScript 2015-04-28 14:45 615人阅读 评论(1) 收藏
用手机UC浏览器访问新浪微博,会注意到有这样的两个分享按钮: 在手机端浏览器里,点击分享按钮,就可以启动微信客户端并分享到微信.研究了下其源代码,存在这样的一个js:http://mjs.sinaim ...
- 百度地图-省市县联动加载地图 分类: Demo JavaScript 2015-04-26 13:08 530人阅读 评论(0) 收藏
在平常项目中,我们会遇到这样的业务场景: 客户希望把自己的门店绘制在百度地图上,通过省.市.区的选择,然后加载不同区域下的店铺位置. 先看看效果图吧: 实现思路: 第一步:整理行政区域表: 要实现通过 ...
- jQuery easyUI datagrid 增加求和统计行 分类: JavaScript 2015-01-14 17:46 2178人阅读 评论(0) 收藏
在datagrid的onLoadSuccess事件增加代码处理. <style type="text/css"> .subtotal { font-weight: bo ...
- 不定义JQuery插件,不要说会JQuery 分类: JavaScript 2014-11-24 14:18 155人阅读 评论(0) 收藏
一:导言 有些WEB开发者,会引用一个JQuery类库,然后在网页上写一写$("#"),$("."),写了几年就对别人说非常熟悉JQuery.我曾经也是这样的人 ...
- 前端笔记之JavaScript(六)让人头疼的正则表达式
一.正则表达式 1.1正则概述和体验 正则表达式是被用来匹配字符串中的字符组合的模式,常用来做表单验证.在JavaScript中,正则表达式也是对象,是一种引用类型. 案例:正确输入一个电话号码,01 ...
- javascript中函数声明和函数表达式的区别 分类: JavaScript 2015-05-07 21:41 897人阅读 评论(0) 收藏
1.js中函数表达式的定义 表达式(expression)JavaScript中的一个短语,javascript会将其计算(evaluate)出一个结果.程序中的常量是一个最简单的表达式.变量名也是一 ...
- javascript闭包获取table中tr的索引 分类: JavaScript 2015-05-04 15:10 793人阅读 评论(0) 收藏
使用javascript闭包获取table标签中tr的索引 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN& ...
- js函数 标签: javascript 2016-08-12 16:48 56人阅读 评论(0) 收藏
函数实际上是对象,函数名实际上也是一个指向函数对象的指针. 使用不带圆括号的函数名是访问函数指针,而非调用函数. 函数声明和函数表达式: alert(test(2,3)); function test ...
随机推荐
- Linux 启动流程及相关知识
基础知识 linux系统的组成 内核(kerner) 根文件系统(rootfs) 内核提供操作系统的功能,根文件系统包含常用的一些工具,这些工具.这些工具的运行离不开glibc库文件. 程序:二进制程 ...
- 倍增求RMQ
RMQ,即区间最值查询,给定一个序列,求区间l-r的最大值.最小值. st表求RMQ,预处理On*logn,查询O1. 预处理: void init_rmq() { for(rll j=1;j< ...
- ESP8266 使用 DRV8833驱动板驱动N20电机
RT 手里这块ESP8266是涂鸦的板子,咸鱼上三块一个买了一堆,看ESP8266-12F引脚都差不多的.裸焊了个最小系统,加两个按钮(一个烧录,一个复位) 1. 准备工作 搜索过程中发现 DRV88 ...
- DelayQueue达到定时触发效果
DelayQueue的特点就是插入Queue中的数据可以按照自定义的delay时间进行排序.只有delay时间小于0的元素才能够被取出. 这样子,只要开启一个线程循环从DelayQueue中取值执行, ...
- vscode problem
1.Inconsistent use of tabs and spaces in indentation 原因:tab和空格键不能同时使用 vs code按住ctrl + p,输入以下内容 >c ...
- 关于奇妙的 Fibonacci 的一些说明
奇妙的 Fibonacci,多次模拟赛中出现 同时也是 BZOJ 2813 一 Fibonacci 的 GCD 如果 \(F\) 是 Fibonacci 数列,那么众所周知的有 \(\gcd(F_i, ...
- 2539-SpringSecurity系列--在有安全验证的情况下做单元测试Test
在有安全验证的情况下做单元测试Test 版本信息 <parent> <groupId>org.springframework.boot</groupId> < ...
- codeforces600E Lomsat gelral【线段树合并/DSU】
第一次AC这道题,是三年前的一个下午,也许晚上也说不定.当时使用的\(DSU\) \(on\) \(tree\)算法,如今已经淡忘,再学习新的算法过程中,却与旧物重逢.生活中充满不可知会的相遇,即使重 ...
- django中的静态文件
静态文件 1.什么是静态文件 在django中静态文件是指那些图片.css样式.js样式.视频.音频等静态资源. 2.为什么要配置静态文件 这些静态文件往往不需要频繁的进行变动,如果我们将这些静态文件 ...
- CF 559C - Gerald and Giant Chess (组合计数)
\(C_{x+y}^y\)的公式,DP容斥删多余贡献. #include <cstdio> #include <iostream> #include <cstring&g ...