初涉模块化

初涉模块化_第1张图片

早期的JavaScript发展初期只是为了少量的页面交互逻辑,且功能(逻辑)简单,代码量少,甚至于早期的Web是没有前端这个说法的,后端顺便一写JS。
随着时间发展,进入Web2.0时代,CPU等硬件性能的提升也使得浏览器性能得到了提升,很多页面交互逻辑迁移到了客户端(浏览器),加上新技术不断涌现(Ajax),JQuery等前端库层出不穷,代码量日益膨胀。

这时候JS作为动态语言的定位就显得捉襟见肘,没有类的概念,没有模块,简单的代码组织不足以驾驭如此大规模的代码。

模块

从最简单的开始:

function step1() {

};
function step2() {

};
step1();
step2();

这是上古时期的JS书写方式,亦或者初学JavaScript的新手书写JS的方法,当然也属于面向过程式的。

当逻辑交互增多,一个JS文件显然不够了,需要引入多个JS文件,并且这种书写的JS问题也很明显。

  1. 函数都是在global下定义,别人可以随意的修改操控这些全局函数,污染了全局变量
  2. 如果有人要是在另一个文件的人也定义了一个step函数会如何?会发生命名冲突

为了解决如上问题,对象的写法应运而生,可以把所有的模块成员封装在一个对象中。

var Moudle = {
    value1: 1,
    value2: 2,
    method1: function() {
        /*do something*/
    },
    method2: function() {
        /*do somethinlg*/
    }
}

调用时,只要保证模块名唯一即可。
当然,这并没有从根本上解决这个问题,外部依然可以随意修改内部成员。

Moudle.value1 = 10;

这样会产生安全问题。


后来又有人使用立即执行的函数表达式,也叫IIFE模式。

var Moudle = (function() {
    var a = 1;
    var b = 2;
    function method1() {
        console.log(a);
    };
    function method2() {
        console.log(b);
    };

    return {
        method1: method1,
        method2: method2
    }
})();

Moudle.method1();       // 1
Moudle.method2();       // 2

这样在模块外部无法修改我们没有暴露出来的变量、函数。
上述做法就是我们模块化的基础


AMD/CMD/CommonJS是什么?

CommonJS / Node.js

2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。这标志"Javascript模块化编程"正式诞生。
为什么JS可以在服务器端运行就标志着模块化编程到来呢?因为,在浏览器下,没有模块也不是很致命的问题,毕竟网页程序的复杂度有限。但对于服务端就不一样了,如果没有模块与操作系统底层或者其他应用程序互动,根本无法编程。

Node.js是CommonJS规范的实现

node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。

  1. 根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块获取,除非定义为global对象的属性。
  2. 模块输出:模块只有一个出口,module.exports对象,我们需要把模块希望输出的内容放入对象。
  3. 加载模块:加载模块使用require()方法,该方法获取一个文件并执行,返回文件内部的module.exports对象。

模块定义a.js

var myModule = {
    a: 10,
    sayA: function() {
        return this.a;
    }
}
module.exports = myModule;

加载模块

var myModule = require("./a.js");
console.log(myModule.sayA());    // 10

CommonJS定义的模块分为:{模块引用(require)} {模块定义(exports)} {模块标识(module)}
require()用来引入外部模块;exports对象用于导出当前模块的方法或变量,唯一的导出口;module对象就代表模块本身。


AMD / require.js
背景

在CommonJS规范的Node.js诞生后,服务端的模块化概念已经形成,很自然的,大家就想要客户端(浏览器)模块。而且最好两者都兼容,一个模块不用改,在两端都可以运行。
但是有一个缺陷,使得CommonJS规范不适用于浏览器环境。

var myModule = require("./a.js");
console.log(myModule.sayA());    // 10

第二行的sayA()方法,在第一行的a模块之后运行,必须得等到a.js加载完成后才能使用方法,如果无法加载或者加载没有完成那就会造成阻塞,直到a.js加载完成后续的代码才会执行。即:它们是同步的

同步加载对于服务器端不是难事,因为资源都存放在服务器端,即用即取,完全可以同步加载,等待的时间就是服务器的硬盘读取时间,这个时间肯定比浏览器快的多。
受到网速限制,如果等待很长时间都未能加载,页面就会“假死”,这对用户来说是相当不友好的。

因此,基于这样的特殊背景,浏览器端的模块,不能采用同步加载,只能采用异步加载,这就是AMD规范的诞生。

require.js是AMD规范的实现

AMD介绍

AMD(Asynchronous Module Definition),中文名异步模块定义,是一个在浏览器端模块化开发的规范。

由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎的RequireJS,实际上AMD是requireJS在推广过程中对模块定义的规范化的产出。

requireJS解决了什么问题呢?

  • 实现JS文件的异步加载,避免网页失去相应。
  • 管理模块之间的依赖性,便于代码的编写和维护。
  
  
  
  
  
  

这样的代码,很多人都应该写过,繁多复杂,并且,还要体现依赖性,如照这样写,说明1.js必定被2,3,4,5,6所依赖。如果依赖关系在复杂一点,可读性,维护将变得很差。

还有一个缺点,就是script标签加载时的阻塞问题。

使用require.js,首先需要引入它。
requireJS

    

当然,加载这个文件也可能使网页失去响应,要么放在body下方;要么写上async属性表明这个文件需要异步加载,避免网页失去响应,IE不支持这个属性,所以写上defer。

初涉模块化_第2张图片

加载require.js之后,下一步就是加载自己的代码了,假定我们的代码文件存放在app下,那么写成这样就好了。

    

data-main属性的作用是,指定网页程序的主模块,如上代码,就是app目录下的a.js。这个文件会第一个被require.js加载。由于require.js默认的文件后缀名的js,所以可以把main.js简写成main。

主模块写法

a.js,把它称为“主模块”,意思是整个网页的入口代码,类似C语言里的main()函数,所有代码都从这儿开始运行。

怎么写a.js?

如果你的a.js不依赖任何模块,那么直接写就行了。

alert("加载成功!");

但很显然存在模块,并且主模块a.js依赖于其他模块,比如你的tab模块、轮播图模块等等。
这时就要用到AMD规范定义的require()函数。

注意,这个不是CommonJS里的require函数了。

  require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){

    // some code here

  });

require()函数接受两个参数。

  • 第一个参数:表示所依赖的模块,例子中就是['moduleA','moduleB','moduleC'],即主模块依赖这三个模块;
  • 第二个参数:是一个回调函数,只有前面的所依赖的模块加载完成,回调函数才会被调用,加载的模块会以参数的形式传入函数,从而在回调函数中使用这些模块。

require()异步加载moduleA,moduleB和moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

注意: 默认情况下,require.js会假设这三个模块与主模块目录相同,关于目录指定,可以查看官方文档

假设现在主模块依赖moduleA、moduleB、moduleC这三个模块,那么主模块可以这样写:


初涉模块化_第3张图片
require(["moduleA","moduleB","moduleC"],function(moduleA,moduleB,moduleC) {
    var sum = moduleA.str + moduleA.str + moduleC.str;
    console.log(sum);       // " module A module A module C"
});

在CommonJS中,模块的出口完全靠module.exports,在requireJS中,我们又如何定义模块呢?

在之前的例子中,A,B,C三个模块存放在app目录下,require.js假定这三个模块与主模块(a.js)在同一个目录中,然后就可以自动加载他们了。

RequireJS以一个相对于baseUrl的地址来加载所有的代码。 页面顶层

你可能感兴趣的:(初涉模块化)