我怎么记得我好像写过相关类型的文章,但是我找遍了我的博客没有~那就再写一遍吧,其实模块化的核心内容也算不上是复杂,只不过需要整理一下,规划一下罢了。嘻嘻。

  开始写标题的时候我就在纠结一件事情,就是,先吃喜欢吃的,还是后吃喜欢吃的,翻译过来就是我应该先写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之无题之让人烦躁的模块化的更多相关文章

  1. 说说JSON和JSONP,也许你会豁然开朗,含jQuery用例 分类: JavaScript 2014-09-23 10:41 218人阅读 评论(1) 收藏

    前言: 由于Sencha Touch 2这种开发模式的特性,基本决定了它原生的数据交互行为几乎只能通过AJAX来实现. 当然了,通过调用强大的PhoneGap插件然后打包,你可以实现100%的Sock ...

  2. UC浏览器 分享到朋友圈和微信好友 分类: JavaScript 2015-04-28 14:45 615人阅读 评论(1) 收藏

    用手机UC浏览器访问新浪微博,会注意到有这样的两个分享按钮: 在手机端浏览器里,点击分享按钮,就可以启动微信客户端并分享到微信.研究了下其源代码,存在这样的一个js:http://mjs.sinaim ...

  3. 百度地图-省市县联动加载地图 分类: Demo JavaScript 2015-04-26 13:08 530人阅读 评论(0) 收藏

    在平常项目中,我们会遇到这样的业务场景: 客户希望把自己的门店绘制在百度地图上,通过省.市.区的选择,然后加载不同区域下的店铺位置. 先看看效果图吧: 实现思路: 第一步:整理行政区域表: 要实现通过 ...

  4. jQuery easyUI datagrid 增加求和统计行 分类: JavaScript 2015-01-14 17:46 2178人阅读 评论(0) 收藏

    在datagrid的onLoadSuccess事件增加代码处理. <style type="text/css"> .subtotal { font-weight: bo ...

  5. 不定义JQuery插件,不要说会JQuery 分类: JavaScript 2014-11-24 14:18 155人阅读 评论(0) 收藏

    一:导言 有些WEB开发者,会引用一个JQuery类库,然后在网页上写一写$("#"),$("."),写了几年就对别人说非常熟悉JQuery.我曾经也是这样的人 ...

  6. 前端笔记之JavaScript(六)让人头疼的正则表达式

    一.正则表达式 1.1正则概述和体验 正则表达式是被用来匹配字符串中的字符组合的模式,常用来做表单验证.在JavaScript中,正则表达式也是对象,是一种引用类型. 案例:正确输入一个电话号码,01 ...

  7. javascript中函数声明和函数表达式的区别 分类: JavaScript 2015-05-07 21:41 897人阅读 评论(0) 收藏

    1.js中函数表达式的定义 表达式(expression)JavaScript中的一个短语,javascript会将其计算(evaluate)出一个结果.程序中的常量是一个最简单的表达式.变量名也是一 ...

  8. javascript闭包获取table中tr的索引 分类: JavaScript 2015-05-04 15:10 793人阅读 评论(0) 收藏

    使用javascript闭包获取table标签中tr的索引 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN& ...

  9. js函数 标签: javascript 2016-08-12 16:48 56人阅读 评论(0) 收藏

    函数实际上是对象,函数名实际上也是一个指向函数对象的指针. 使用不带圆括号的函数名是访问函数指针,而非调用函数. 函数声明和函数表达式: alert(test(2,3)); function test ...

随机推荐

  1. WSL2安装Ubuntu20.04

    前言:听说WSL2需要Window版本在1904以上(我的window版本是1909,所以未能验证真实性) 启用WSL 控制面板 → 程序 → 程序和功能 → 启用或关闭Windows功能 勾选 适用 ...

  2. 2022-7-20 第七组 pan小堂 String

    字符串 String 字符串部分方法 字符串对象的特点: 1.Java程序中所有双引号引起来的内容,都是String类的对象 2.字符串内容不可变,它们的值在创建后不能被更改(在底层被final修饰, ...

  3. Calendar类介绍_获取对象的方式和Calendar类的常用成员方式

    java.util.Calendar是日历类,在Date后出现,替换掉了许多Date方法.该类将所有可能用到的时间信息封装为静态成员变量,方便获取.日历类就是方便获取各个时间属性的. Calendar ...

  4. 【AcWing】第 62 场周赛 【2022.07.30】

    AcWing 4500. 三个元素 题目描述 给定一个长度为 \(n\) 的数组 \(r\_1,r\_2,-,r\_n\). 请你找到其中的三个元素 \(r\_a,r\_b,r\_c\),使得 \(r ...

  5. mac 无任何来源选项

    终端执行命令 sudo spctl --master-disable

  6. 无意苦争春,一任群芳妒!M1 Mac book(Apple Silicon)能否支撑全栈工程师的日常?(Python3/虚拟机/Docker/Redis)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_187 就像大航海时代里突然诞生的航空母舰一样,苹果把玩着手心里远超时代的M1芯片,微笑着对Intel说:"不好意思,虽然 ...

  7. 把酒言欢话聊天,基于Vue3.0+Tornado6.1+Redis发布订阅(pubsub)模式打造异步非阻塞(aioredis)实时(websocket)通信聊天系统

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_202 "表达欲"是人类成长史上的强大"源动力",恩格斯早就直截了当地指出,处在蒙昧时代即低 ...

  8. RocketMQ的push消费方式实现的太聪明了

    大家好,我是三友,我又来了~~ 最近仍然畅游在RocketMQ的源码中,这几天刚好翻到了消费者的源码,发现RocketMQ的对于push消费方式的实现简直太聪明了,所以趁着我脑子里还有点印象的时候,赶 ...

  9. 如何自定义一个Collector

    Collectors类提供了很多方便的方法,假如现有的实现不能满足需求,我们如何自定义一个Collector呢?   Collector接口提供了一个of方法,调用该方法就可以实现定制Collecto ...

  10. 【AGC】集成华为AGC崩溃服务实用教程

    ​简介 AppGallery Connect(简称AGC)崩溃服务提供了轻量级崩溃分析服务,集成Crash SDK,可以实现零代码快速集成,您的应用能够在崩溃时自动收集崩溃报告,帮助您了解应用版本质量 ...