yu648813710 / JS-module

关于前端JS模块化管理的学习

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

JS-module

模块化开发的目的:

前端代码量日益臃肿,如果采用之前的简单规范进行开发,会导致前端项目无法代码混乱,变量名冲突等,所以就产生了模块化开发的概念,一个模块对应它相应的功能。

模块:

JS没有类似于JAVA里包的概念,就是把逻辑相关的代码放进一个包中,是独立的不会与外部的命名冲突,但是JS没有这样的功能。ES6之后有了类的概念就有了

模块化的发展:

  • 函数分装:

    函数一个功能就是实现特定逻辑的一组语句打包,而且JavaScript的作用域就是基于函数的,所以把函数作为模块化的第一步是很自然的事情,在一个文件里写几个相关函数 就是最开始的模块化了 。

    缺点:污染了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间没什么关系。

  • 对象

    为了解决上面问题 ,对象的写法应运而生,可以把所有的模块成员封装在一个对象中,比如对象 myModule.fn1 ,引入相应的文件就可以这样去写。

    优点:避免了全局变量的污染,只要保证对象模块名的唯一性就行。 缺点:继承了对象的特性,外部可以直接修改方法,不太安全,比如 直接 myModule.fn1 =1;这时对象就变为1了。

  • 立即执行函数

    • 立即执行函数的解释 :

      利用运算符和()直接执行函数如下图:

      从图中可以看出,除了使用()运算符之外,!,+,-,=等运算符都能起到立即执行的作用。这些运算符的作用就是将匿名函数或函数声明转换为函数表达式,不单单是匿名的可以执行 ,声明的函数表达式也可以立即执行。

      优点:

      立即执行函数直接匿名化了一个新的函数作用域,相当于创建了一个【私有】的命名空间,不会污染全局变量,可以利用传参传入对象,安全性也极高。JQ就是这种方式封装的代码

    立即执行函数的写法是现在模块化的基础,目前JS模块化规范主要有两个 一个是commonjs和AMD以及CMD

script标签:

最原始的 JavaScript 文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中,不同模块的接口调用都是一个作用域中,一些复杂的框架,会使用命名空间的概念来组织这些模块的接口

  • 缺点:
    • 全局作用域下容易造成变量冲突
    • 文件只能按照 < script > 的书写顺序进行加载
    • 在大型项目中各种资源难以管理

commonjs规范:

CommonJS是服务器端模块的规范,由Node推广使用。由于服务端编程的复杂性,如果没有模块很难与操作系统及其他应用程序互动。使用方法如下。

  • 规范:

    • 一个文件一个模块,每个模块是单独的作用域,无法被其他模块读取,除非定义为global全局对象
    • 输出模块的方法是module.exports对象
    • 加载模块使用require方法,该方法读取一个文件并执行,返回文件内部的module.exports对象
  • 缺点:

    • 同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的,服务器可以用的原因是磁盘加载,所以等待时长很短
    • 不能非阻塞的并行加载多个模块

AMD规范:

推广requireJS中出来的规范,它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

  • 解决问题:

    • 多个js文件可能有依赖关系,被依赖的文件需要早期加载到浏览器
    • JS加载时页面会停止渲染,所以加载文件越多,页面响应时间越长。
  • 优点:

    • 适合在浏览器环境中异步加载模块
    • 可以并行加载多个模块
  • 缺点:

    • 提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅
    • 不符合通用的模块化思维方式,是一种妥协的实现

CMD规范:

CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,在 CMD 规范中,一个模块就是一个文件,AMD是依赖关系前置,在定义模块的时候就要声明其依赖的模块; CMD是按需加载依赖就近,只有在用到某个模块的时候再去require

  • 优点:
    • 依赖就近,延迟执行
    • 可以很容易在 Node.js 中运行
  • 缺点:
    • 依赖 SPM 打包,模块的加载逻辑偏重
    • API文档不齐全,学习成本高

决定使用的模块规范AMD

解决问题:如果加载require本身出现卡顿呢?使用如下方法

<script src="js/require.js" defer async="true"></script>

  • 把requireJS放到body后加载,并加入 defer 属性(defer 属性规定是否对脚本执行进行延迟,直到页面加载为止,只有IE支持)

  • async="async" (加入了 html5 出的 async 异步属性,async 属性规定一旦脚本可用,则会异步执行。IE 不支持)

    • async 属性仅适用于外部脚本(只有在使用 src 属性时)。 页面内的JS不适用
    • 如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
    • 如果不使用 async 且 defer="defer":脚本将在页面完成解析时执行
    • 如果不使用 async 且 defer="defer":脚本将在页面完成解析时执行
    • 如果既不使用 async 也不使用 defer:在浏览器继续解析页面之前,立即读取并执行脚本

data-main 的使用

 <script src="js/require.js" data-main="js/main"></script>
  • data-main属性的作用:

    指定网页程序的主模块。在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。 个人理解就是把一个入口的JS 通过require 引入进去。当然这个入口的JS写了大部分页面要用的业务逻辑

主模块的写法,也就是入口文件的写法

如果我们的代码不依赖任何其他模块,那么可以直接写入javascript代码。

    console.log("使用dta-main成功!");

但是真正常见的情况是,主模块依赖于其他模块,这时就要使用AMD规范定义的的require()函数。

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

    // 写代码的地方

  });

require()函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是['moduleA', 'moduleB', 'moduleC'],即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。

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

  • 实例
require(['moduleA'],function(moduleA){
    console.log(A);
})

模块的加载

在require函数的第一个参数写的依赖模块,在与入口文件main.js为同一文件夹的时候可以不用写声明加载,直接写在第一个参数里,直接调用,保证参数名与文件名相同就可。

不过使用require.config()方法,我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。

require.config({

    paths: {

      "A": "moduleA",//在同一路径
      "B": "lib/moduleB",//在lib的文件夹下的
      "C": "lib/moduleC"

    }

  });

另一种则是直接改变基目录(baseUrl)。

require.config({
        baseUrl: "js/lib",//直接配置默认路径
    paths: {
      "B": "moduleB",//在lib的文件夹下的
      "C": "moduleC"
    }

  });

如果某个模块在另一台主机上,也可以直接指定它的网址

require.config({
    paths: {
        "jquery":"https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
    }

  });

require.js要求,每个模块是一个单独的js文件。这样的话,如果加载多个模块,就会发出多次HTTP请求,会影响网页的加载速度。因此,require.js提供了一个优化工具,当模块部署完毕以后,可以用这个工具将多个模块合并在一个文件中,减少HTTP请求数。

AMD模块的写法

  • require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。

    具体来说,就是模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

假定现在有一个module.js文件,它定义了一个module模块。那么,module.js就要这样写:

define(function (){

    var A = "我是模块A!快来点我!";//定义

    return {//出口

            A: A
    };

  });

如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

  define(['moduleB'], function(moduleB){//在入口文件怎么定义的moduleB,那么这块就引入什么名称

          var C=moduleB.C();

    return {
            C : C
    };

  });

当require()函数加载上面这个模块的时候,就会先加载moduleB文件。

加载非规范的模块

理论上,require.js加载的模块,必须是按照AMD规范、用define()函数定义的模块。但是实际上,虽然已经有一部分流行的函数库(比如jQuery)符合AMD规范,更多的库并不符合。

所以 要用require.config()方法,定义它们的一些特征。

  require.config({
    shim: {//配置不是AMD规范的JS,不过路径也要照常配置
        "D": {
            exports: 'D',//出口D模块的暴露入口
        },
        "E": {
            deps: ['D'],//E模块依赖于D模块所以需要提前声明,声明的名称是 路径配置名称 
            exports: 'E',
        },
    },
})

require.config()接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义(1)exports值(输出的变量名),表明这个模块外部调用时的名称;(2)deps数组,表明该模块的依赖性。

About

关于前端JS模块化管理的学习


Languages

Language:JavaScript 80.0%Language:HTML 10.4%Language:CSS 9.6%