一、模块化(原因和概念)
先介绍一下模块化是个什么东西,解决了什么。
模块化概念和分类这个部分转自别人转的,也不知道原博在哪,但是写得很不错,所以拿来分析总结一下。
在
JavaScript
发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可;如今CPU、浏览器性能得到了极大的提升,很多页面逻辑迁移到了客户端(表单验证等),随着 web2.0 时代的到来,Ajax
技术得到广泛应用,jQuery
等前端库层出不穷,前端代码日益膨胀
这时候JavaScript
作为嵌入式的脚本语言的定位动摇了,JavaScript
却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,JavaScript
极其简单的代码组织规范不足以驾驭如此庞大规模的代码
既然JavaScript
不能处理好如此大规模的代码,我们可以借鉴一下其它语言是怎么处理大规模程序设计的,在Java
中有一个重要带概念——package
,逻辑上相关的代码组织到同一个包内,包内是一个相对独立的王国,不用担心命名冲突什么的,那么外部如果使用呢?直接import
对应的package
即可import java.util.ArrayList;
遗憾的是JavaScript
在设计时定位原因,没有提供类似的功能,开发者需要模拟出类似的功能,来隔离、组织复杂的JavaScript
代码,我们称为模块化。
一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。模块开发需要遵循一定的规范,各行其是就都乱套了
简单来说就是以前前端简单,项目工程也小,现在前端突然发展开来,随之一些需求及项目复杂度也增加,js语言原本的定位不足支持这些工程,就需要向一些成熟的老大哥学习(java等),模块化使代码从一坨变成规整的一块一块。
利于维护(一块出问题了就维护一块)
利于分配工作(每人负责几块)
利于使用(想用哪块的话就拿来哪块)
1、前端模块化的前辈
js模块化有3个最基础的前辈
- 函数封装(代码还是一坨)
- 向其他语言学习,创建对象(面向对象编程,如果把函数比作能做的事,对象就是做事的人,想要做什么事,就找某个能做这个事的人,而不是去成千上万个事中找。还是一坨。)
- 通过
<script>
标签引入自调用函数包(已经变成块了,不过这个块只能连接一层,块之间的依赖只能手动增添,比如a.js依赖于b.js,然后手动添加script标签,然后b.js又需要依赖c/d/e/f/g/h.js等,也要手动写入,而且要注意写入的先后顺序,而且很多都需要导出一个函数或对象到全局作用域才能使用。)
前面的模块化也适应不了日益增长复杂的前端项目。 这时后端(也就是node.js)迈出了真正模块化的第一步——CommonJS标准
2、CommonJS(同步加载,同步执行)
参考原文中这么写——
因为在网页端没有模块化编程只是页面
JavaScript
逻辑复杂,但也可以工作下去,在服务器端却一定要有模块,所以虽然JavaScript
在web端发展这么多年,第一个流行的模块化规范却由服务器端的JavaScript
应用带来,CommonJS
规范是由NodeJS
发扬光大,这标志着JavaScript
模块化编程正式登上舞台。
但是CommonJS
的思想并不能用在浏览器上——
模块系统需要同步读取模块文件内容,并编译执行以得到模块接口。
这在服务器端实现很简单,也很自然,然而, 想在浏览器端实现问题却很多。
浏览器端,加载JavaScript
最佳、最容易的方式是在document
中插入script
标签。但脚本标签天生异步,传统CommonJS模块在浏览器环境中无法正常加载。
解决思路之一是,开发一个服务器端组件,对模块代码作静态分析,将模块与它的依赖列表一起返回给浏览器端。 这很好使,但需要服务器安装额外的组件,并因此要调整一系列底层架构。
就是说CommonJ
S是同步加载的,<script>
标签是异步加载的,所以CommonJS
的同步加载思想不能用在浏览器端上。
那问题来了,原文有这一句话——但脚本标签天生异步,传统CommonJS模块在浏览器环境中无法正常加载。
<script>
标签不是会同步加载和执行并阻塞dom树
的构建吗,怎么在他那边就变成异步加载了。
首先我们要先看清楚加载
这个词的意思,它在浏览器端指的是下载,在服务器端可以指读取硬盘资源。
同步加载是指在构建dom树
时原本就有的一些<script>
标签,在前一个标签内容加载和执行完之前,会阻塞dom树
的构建,也就是不会开始解析下面<script>
标签,也就不会加载此标签的内容了。
但模块化需要的是让每一个模块本身可以自动加载所需依赖。所以肯定得手动用代码去添加<script>
标签来加载和执行其中所带的代码段。
但是手动添加的<script>
标签它就是异步下载的,也就是我想同时想要下载a、b两个依赖,它们会同时下载,而不是和CommonJS
中的那样先下载完a再下载完b,而且如果浏览器端同步加载的话,因为网络下载的延迟性,会阻塞浏览器页面的显示。所以说环境和框架不同使用的标准也要有变化。
于是requireJS
和seaJS
两个模块化工具出现了,随之而来的是requireJS
和seaJS
分别衍生出来的两个标准AMD
和CMD
。
3、AMD(异步加载,异步执行)
AMD为前端JS制定了规范,它使用异步的方式去加载模块,并且所有与模块相关的代码都写在回调函数当中,不影响其他代码的执行。
它主张依赖提前,就是在开始便异步加载所有依赖,哪个依赖先加载完就先执行。不过主逻辑还是同步的等所有依赖加载完后再执行。
相对于使用自调用函数封装并用<script>
标签导入来说,它解决了:
- 异步加载,不会阻塞浏览器构造
dom树
- 自动加载依赖,不再需要手动引入模块间的依赖关系
目前requireJS
和curlJS
为这个标准的主要实现
4、CMD(异步加载,同步执行)
CMD
是淘宝团队开发的SeaJS
在推广过程中的产出,弥补了AMD
的一大缺点——异步执行,由于AMD
是异步加载,并且加载完直接执行,所以执行的顺序与加载速度有关,并且不可控,这就非常不友好,于是CMD提倡的