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 ...
随机推荐
- 西文字符与中文GBK编码的区别
一般来讲二者读取的时候西文字符的数值是正,而中文字符的数值是负的,此时读取的是中文字符的前一半,需要再读取一个char类型的数据,在大多数运行环境下这个规则都是用. ps:转自算法竞赛的笔记,要注意在 ...
- CF1612D X-Magic Pair
题意: 给一个数对 \((a,b)\) ,每次可以进行操作 \((a,b) \to (|a-b|,b)\) 或 \((a,b) \to (a,∣a−b∣)\),问最后能否令 \(a=x\) 或 \(b ...
- display: table-cell里面文字打点的方法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Cayley 公式的另一种证明
Cayley 公式的一些广为人知的证法: Prufer 序列 Matrix-Tree 定理 然而我都不会 233,所以下面说一个生成函数角度的证法 . 我们知道 \(n\) 个节点的有标号无根树有 \ ...
- 以太坊 layer2: optimism 源码学习 (一)
作者:林冠宏 / 指尖下的幽灵.转载者,请: 务必标明出处. 掘金:https://juejin.im/user/1785262612681997 博客:http://www.cnblogs.com/ ...
- 使用python3.7和opencv4.1来实现人脸识别和人脸特征比对以及模型训练
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_126 OpenCV4.1已经发布将近一年了,其人脸识别速度和性能有了一定的提高,这里我们使用opencv来做一个实时活体面部识别的 ...
- 日夕如是寒暑不间,基于Python3+Tornado6+APScheduler/Celery打造并发异步动态定时任务轮询服务
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_220 定时任务的典型落地场景在各行业中都很普遍,比如支付系统中,支付过程中因为网络或者其他因素导致出现掉单.卡单的情况,账单变成了 ...
- ToString()格式化输出
C 货币 2.5.ToString("C") ¥2.50 D 十进制数 25.ToString("D5") 00025 E 科学型 25000.ToString ...
- 为MySQL MGR实现简单的负载均衡代理
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 原创:万里数据库,花家舍 导读 在多写(多节点写入)数据库(例如MySQL MGR的multi-primary mode) ...
- Excel 运算符(二):比较运算符
比较运算符用于对两个数据进行比较运算,其结果为 TRUE(真)或 FALSE(假). 运算符 含义 实例 结果 = 等于 =2=3 FALSE < 小于 =5<2 FALSE > 大 ...