前端入门22-讲讲模块化

声明

本篇内容梳理自如下来源:javascript

感谢各位大佬的分享,解惑了不少。html

正文-模块化

如今回过头来想一想,也许选择以《JavaScript权威指南》一书来做为入门有些很差,由于这本书毕竟是很早以前的,书中所讲的思想、标准也基本都只是 ES5 及那时代的相关技术。前端

这也就致使了,在书中看到的不少例子,虽然以为所用到的思想很奇妙,好比临时命名空间之类的处理,但其实,有些技术到如今已经有了更为强大的技术替代了。java

就像这篇要讲的模块化,目前,以我看到的各资料后,所收获的知识是,大概有四种较为经常使用且热门的模块化技术,也许还有更新的技术,也许还有我不知道的技术,无所谓,慢慢来,这篇的内容已经够我消化了。node

目前四种模块化技术:git

  • CommonJS规范&node.js
  • AMD规范&Require.js
  • CMD规范&Sea.js
  • ES6标准

前面是规范,规范就是相似于 ES5,ES6 只是提出来做为一种标准规范,而不一样规范有具体的实现,好比 nodeJS 实现了 CommonJS 规范。es6

模块化历程

在声明部分中的第2、第三连接里那两篇,以时间线介绍了模块化的相关技术的发展历程,我以为颇有必要一看,对于掌握和理解目前的模块化技术挺有帮助的。github

这里,就稍微摘抄其中一些内容,详细内容仍是须要到原文阅读。web

1.全局变量、全局函数(1999年)npm

这时候的多个 js 脚本文件之间,直接经过全局变量或全局函数的方式进行通讯,这种方式叫作:直接定义依赖。

虽然作的好一些的会对这些 js 文件作一些目录规划,将资源归类整理,但仍没法解决全局命名空间被大量污染,极其容易致使变量冲突问题。

2.对象做为命名空间(2002年)

为了解决遍地的全局变量问题,这时候提出一种命名空间模式的思路,即将本要定义成全局变量、全局函数的这些全都做为一个对象的属性存在,这个对象的角色充当命名空间,不一样模块的 JS 文件中经过访问这个对象的属性来进行通讯。

3.当即执行的函数做为临时命名空间 + 闭包(2003年)

虽然提出利用一个对象来做为命名空间的思路,必定程度解决了大量的全局变量的问题,但仍旧存在不少局限,好比没有模块的隐藏性,因此针对这些问题,这时候又新提出一种思路:利用当即执行的函数来做为临时命名空间,这样就能够避免污染全局命名空间,同时,结合闭包特性,来实现隐藏细节,只对外暴露指定接口。

虽然这种思路,解决了不少问题,但仍旧有一些局限,好比,缺少管理者,什么意思,就是说,在前端里,开发人员得手动将不一样 JS 脚本文件按照它们之间的依赖关系,以被依赖在前面的顺序来手动书写 <script> 加载这些文件。

也就是不一样 <script> 的先后顺序实际上表示着它们之间的依赖关系,一旦文件过多,将会很难维护,这是上述方案都存在的问题。

4.动态建立 <script> 工具(2009年)

针对上述问题,也就衍生出了一些加载 js 文件的工具,先看个例子:

$LAB.script("greeting.js").wait()
    .script("x.js")
    .script("y.js").wait()
    .script("run.js");

LAB.js 这类加载工具的原理其实是动态的建立 <script>,达到做为不一样 JS 脚本文件的管理者做用,来决定 JS 文件的加载和执行顺序。

虽然我没用过这种工具,但我以为它的局限仍是有不少,其实就是将开发人员原本须要手动书写在 HTML 文档里的 <script> 代码换成写在 JS 文件中,不一样 JS 文件之间的依赖关系仍旧须要按照先后顺序手动维护。

5.CommonJS规范&node.js(2009年)

中间跳过了一些过程,好比 YUI 的沙箱模式等,由于不熟,想了解更详细的能够去原文阅读。

当 CommonJS 规范出来时,模块化算是进入了真正的革命,由于在此以前的探索,都是基于语言层面的优化,也就是利用函数特性、对象特性等来在运行期间模拟出模块的做用,而从这个时候起,模块化的探索就大量的使用了预编译。

CommonJS 模块化规范有以下特色:

  • 全部代码都运行在模块做用域,不会污染全局做用域。
  • 模块能够屡次加载,可是只会在第一次加载时运行一次,而后运行结果就被缓存了,之后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

不一样的模块间的依赖关系经过 require 来控制,而每一个模块须要对外暴露的接口经过 exports 来决定。

因为 CommonJS 规范自己就只是为了服务端的 node.js 而考虑的,node.js 实现了 CommonJS 规范,因此运行在 node.js 环境中的 js 代码可使用 requireexports 这两个命令,但在前端里,浏览器的 js 执行引擎并不认识 require 这些命令,因此须要进行一次转换工做,后续介绍。

再来看看 require 命令的工做原理:

require 命令是 CommonJS 规范之中,用来加载其余模块的命令。它其实不是一个全局命令,而是指向当前模块的 module.require 命令,然后者又调用 Node 的内部命令 Module._load

Module._load = function(request, parent, isMain) {
  // 1. 检查 Module._cache,是否缓存之中有指定模块
  // 2. 若是缓存之中没有,就建立一个新的Module实例
  // 3. 将它保存到缓存
  // 4. 使用 module.load() 加载指定的模块文件,
  //    读取文件内容以后,使用 module.compile() 执行文件代码
  // 5. 若是加载/解析过程报错,就从缓存删除该模块
  // 6. 返回该模块的 module.exports
};

Module.prototype._compile = function(content, filename) {
  // 1. 生成一个require函数,指向module.require
  // 2. 加载其余辅助方法到require
  // 3. 将文件内容放到一个函数之中,该函数可调用 require
  // 4. 执行该函数
};

因此,其实 CommonJS 的模块化规范之因此能够实现控制做用域、模块依赖、模块通讯,其实本质上就是将模块内的代码都放在一个函数内来执行,这过程当中会建立一个对象 Module,而后将模块的相关信息包括对外的接口信息都添加到对象 Module 中,供其余模块使用。

6.AMD规范&Require.js(2009年)

CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操做。因为 Node.js 主要用于服务器编程,模块文件通常都已经存在于本地硬盘,因此加载起来比较快,不用考虑非同步加载的方式,因此CommonJS 规范比较适用。

可是,若是是浏览器环境,这种同步加载文件的模式就会致使浏览器陷入卡死状态,由于网络缘由是不可控的。因此,针对浏览器环境的模块化,新提出了一种规范:AMD(Asynchronous Modules Definition)异步模块定义。

也就是说,对于 Node.js,对于服务端而言,模块化规范就是按照 CommonJS 规范便可。

但对于浏览器,对于前端而言,CommonJS 不适用,须要看看 AMD 规范。

AMD 规范中定义:

  • 定义一个模块时经过 define 命令,经过 return 声明模块对外暴露的接口
  • 依赖其余模块时,经过 require 命令

而规范终归只是规范,使用时仍是须要有规范的具体实现,针对 AMD 规范,具体的实现是 Require.js,在前端里,如何基于 Require.js 来使用 AMD 规范的模块化技术,后续介绍。

7.CMD规范&Sea.js(2013年)

CMD(Common Module Definition)也是专门针对浏览器、针对前端而提出的一种模块化规范。它跟 AMD 规范都是用途都是同样,用途解决前端里的模块化技术。

但两种规范各有各的优缺点,各有各的适用场景和局限性吧,我还没使用过这两种规范,因此无从比较,但网上关于这两种规范比较的文章却是很多。

CMD 规范中定义:

  • 使用 define 命令定义一个模块,使用 exports 来暴露模块对外的接口
  • 使用 require 来同步加载依赖的模块,但也可以使用 require.async 来异步加载依赖的模块

总之,虽然两种规范都是用于解决前端里的模块化技术,但实现的本质上仍是有些不一样,后续介绍。

对于 CMD 规范的具体实现是 Sea.js,前端里若是想使用 CMD 规范的模块化技术,须要借助 Sea.js。

8.ES6标准(2015年)

2015 年发布的 ES6 标准规范中,新增了 Module 特性,也就是官方在 ES6 中,在语言自己层面上,添加了模块的机制支持,让 JavaScript 语言自己终于能够支持模块特性了。

在 ES6 以前的各类方案,包括 CommonJS,AMD,CMD,本质上其实都是利用函数具备的本地变量的特性进行封装从而模拟出模块机制。也就是说,这些方案都是须要在运行期间,才会去动态的建立出某个模块,并暴露模块的相关接口。这种方案其实也存在一些局限性。

而 ES6 新增了模块的机制后,在代码的解析阶段,就可以肯定模块以及模块对外的接口,而不用等到运行期。这种本质上的区别,在借助开发工具写代码阶段的影响很明显就是,终于可让开发工具智能的提示依赖的模块都对外提供了哪些接口。

但 ES6 因为是新增的特性,在支持方面目前好像还不是很理想,并非全部环境都支持 ES6 的模块机制好像,因此会看到某些大佬的文章里会介绍一些诸如:Babel、Browserify。

Babel 用于将 ES6 转换为 ES5 代码,好让不支持 ES6 特性的环境能够运行 ES5 代码。

Browserify 用于将编译打包 js,处理 require 这些浏览器并不认识的命令。

上面只是简单介绍了下模块化的发展历程,并且中间略过一些阶段,想看详细的能够去原文阅读。下面就具体介绍下,目前四个比较稳定的模块技术:

CommonJS规范

因为 CommonJS 是针对服务端设计的模块化规范,对于 Node.js 来讲,一个 JS 文件就是一个模块,因此并不须要相似 AMD 或 CMD 中的 define 来声明一个模块。这是个人理解。

exports

既然在 Node.js 中,每一个 JS 文件就是一个模块,那么这个模块文件内的变量都是对外隐藏的,外部无权访问,只能访问 exports 对外暴露的接口,如:

//module.js
var name = "dasu";
var wx = "dasuAndroidTv";
var getBlog = function() {
    return "http://www.cnblogs.com/dasusu/";
}
//以上变量和函数,外部都无权访问,外部只能访问到下面经过 exports 暴露的接口
module.exports.name = name;
exports.getWx = () => wx;

模块内,能够经过对 module.exports 或 exports 添加属性来达到对外暴露指定接口的目的,固然,从程序上来讲,你能够直接改变 module.exports 的指向,让它指向一个新对象而不是在本来对象上添加属性,这个就相似于对构造函数 prototype 属性的修改。

但建议使用方式仍是尽量在 exports 对象上添加属性。

若是有想探究它的原理的话,能够尝试利用 Browserify 来转换这段模块代码,看看最后生成的是什么:

function(require,module,exports){
    //module.js
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    //以上变量和函数,外部都无权访问,外部只能访问到下面经过 exports 暴露的接口
    module.exports.name = name;
    exports.getWx = () => wx;
}

虽然,对于 Node.js 来讲,它其实对待每一个 JS 文件,本质上,会将文件内的代码都放于一个函数内,若是该模块首次被其余模块引用了,那么就会去执行这个函数,也就是执行模块内的代码,因为函数自己有三个参数,其中有两个分别是:module 和 exports,这也是内部为何能够直接经过 module.exports 或 exports 来操做的缘由。

Module 对象是 Node.js 会为每一个模块文件建立的一个对象,模块之间的通讯,其实就是经过访问每一个 Module 对象的属性来实现。

因此,说白了,CommonJS 模块化技术的本质,其实就是利用了函数的局部做用域的特性来实现模块做用域,而后结合一个对象做为命名空间的方式,来保存模块内部须要对外暴露的信息方式。最后,经过 require 命令来组织各模块之间的依赖关系,解决之前方案没有管理者角色的局限,解决谁先加载谁后加载的问题。

require

每一个 JS 文件其实都被当作一个模块处理,也就是文件内的代码都会被放入到一个函数内,那这个函数何时执行呢?也就是说,模块何时应该被加载呢?

这就是由 require 命令决定,当某个模块内使用了 require 命令去加载其余模块,那么这时候,被加载的模块若是是首次被调用,它是没有对应的 Module 对象的,因此会去调用它的函数,执行模块内代码,这个过程是同步的,这期间会完善这个模块的 Module 对象信息。以后,其余模块若是也引用了这个模块,由于模块内代码已经被执行过了,已经存在对应的 Module 对象,因此此时就不会再重复去加载这个模块了,直接返回 Module 对象。

以上,基本也就是模块间依赖的加载处理过程。而 require 命令用法很简单:

//main.js
var module1 = require("./module");

module1.name;  //输出=> dasu
module1.getWx(); //输出 => dasuAndroidTv
//module1.getBlog(); //没有权限访问

其实,Node.js 对 main.js 的处理也是认为它是个模块,因此文件内的代码也都放入一个函数内,还记得函数的第一个参数就是 require 么,这也就是为何模块内能够直接使用 require() 的缘由,require 其实本质上是一个函数,具体的实现是 Node.js 的一个内置方法:Module._load(),主要工做上一节有介绍过了。

说得稍微严谨点,Node.js 其实才是做为各模块之间的管理者,由它来管控着哪一个模块先加载,哪一个后加载,维护着各模块对外暴露的信息。

到这里再来理解,有些文章中对 Module 对象的介绍:

  • module.id 模块的识别符,一般是带有绝对路径的模块文件名。
  • module.filename 模块的文件名,带有绝对路径。
  • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
  • module.parent 返回一个对象,表示调用该模块的模块。
  • module.children 返回一个数组,表示该模块要用到的其余模块。
  • module.exports 表示模块对外输出的值。

这时,对于 Module 对象内的各属性用途,理解应该会比较容易点了。

最后说一点,CommonJS 只是一种模块化的规范,而 Node.js 才是这个规范的具体实现者,但 Node.js 一般用于服务端的运行环境,对于前端而言,对于浏览器而言,由于不存在 Node.js 这东西,因此 require 或 exports 这类在前端里是没法运行的,但能够借助 Browerify 来进行代码转换。

AMD规范

AMD规范和规范实现者:Require.js

前端里没有 Node.js 的存在,即便有相似的存在,但因为 CommonJS 的模块化规范中,各模块的加载行为是同步的,也就是被依赖的模块必须执行结束,当前模块才能继续处理,这对于前端而言,模块的加载就多了一个下载的过程,而网络是不可靠的,因此 CommonJS 并不适用于前端的场景。

因此,针对前端,提出了另外一种模块化规范:AMD,即异步模块加载,经过增长回调处理的方式,来移除被依赖模块和当前模块的先后关联,两个模块可同时下载,执行。当前模块内,须要依赖于其余模块信息的代码放置于回调函数内,这样就能够先行处理当前模块内其余代码。

define

前端里是经过 <script> 来加载 JS 文件代码的,不能像 Node.js 那样从源头上处理 JS 文件,因此它多了一个 define 来定义模块,如:

//module.js
define(function(){
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    return {
        name: name,
        getWx: function() {
            return wx;
        }
    }
})

若是定义的模块又依赖了其余模块时,此时 define 须要接收两个参数,如:

//两个参数,第一个参数是数组,数组里是当前模块依赖的全部模块,第二个参数是函数,函数须要参数,参数个数跟数组个数一直,也跟数组里依赖的模块一一对应,该模块内部就是经过参数来访问依赖的模块。
define(['module2'], function(module2){
    //module2.xxx  使用模块 module2 提供的接口
    
    //本模块的内部逻辑
    
    return {
        //对外暴露的接口
    }
})

define 有两个参数,第一个参数是数组,数组里是当前模块依赖的全部模块,第二个参数是函数,函数须要参数,参数个数跟数组个数一直,也跟数组里依赖的模块一一对应,该模块内部就是经过参数来访问依赖的模块。

require

若是其余地方有须要使用到某个模块提供的功能时,此时就须要经过 require 来声明依赖关系,但声明依赖前,须要先经过 requirejs.config 来配置各个模块的路径信息,方便 Require.js 可以得到正确路径自动去下载。

requirejs.config({
    paths: {
        module1: './module'
    }
})

var module1 = require(['module1'], function(module1){
    console.log(module1.name);    //访问模块内的 name 接口
    console.log(module1.getWx()); //访问模块内的 getWx 接口
});

//其余不依赖于模块的代码

这种方式的话,require 的第一个数组参数内的值就能够模块的别称,而第二个参数是个函数,一样,函数的参数就是加载后的模块,该 JS 文件内须要依赖到模块信息的代码均可以放到回调函数中,经过回调函数的参数来访问依赖的模块信息。

使用步骤

1.下载 Require.js,并放到项目中

Require.js:https://requirejs.org/docs/download.html#requirejs

2.新建做为模块的文件,如 module.js,并经过 define 定义模块

//module.js
define(function(){
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    return {
        name: name,
        getWx: function() {
            return wx;
        }
    }
})

3.在其余 js 文件内先配置全部的模块来源信息

//main.js
requirejs.config({
    paths: {
        module1: './module'
    }
})

4.配置完模块信息后,经过 require 声明须要依赖的模块

//main.js
var module1 = require(['module1'], function(module1){
    console.log(module1.name);    //访问模块内的 name 接口
    console.log(module1.getWx()); //访问模块内的 getWx 接口
});

//其余不依赖于模块的代码

5.最后也最重要的一步,在 html 中声明 require.js 和 入口 js 如 main.js 的加载关系

<script src="js/lib/require.js" data-main="js/src/main.js"></script>

固然,这只是基础用法的步骤,其中第 3 步的模块初始化步骤也可经过其余方式,如直接利用 require 的不一样参数类型来实现等等,但大致上须要这几个过程,尤为是最后一步,也是最重要一步,由于 AMD 在前端的具体实现都依靠于 Require.js,因此必须等到 Require.js 下载并执行结束后会开始处理其余 js。

以上例子的项目结构如图:

小结

最后小结一下,AMD 规范的具体实现 Require.js 其实从使用上来看,已经比较容易明白它的原理是什么了。

本质上,也仍是利用了函数的特性,做为模块存在的那些代码自己已经经过 define 规范被定义在函数内了,因此模块内的代码天然对外是隐藏的,外部能访问到的只有函数内 return 的接口,那么这里其实也就利用了闭包的特性。

因此,模块化的实现,无非就是让函数做为临时命名空间结合闭包或者对象做为命名空间方式, 这种方式即便没有 CommonJS 规范,没有 AMD 规范,本身写代码很能够容易的实现。那么为何会有这些规范技术的出现呢?

无非就是为了引入一个管理者的角色,没有管理者的角色,模块之间的依赖关系,哪一个文件先加载,哪一个后加载,<script> 的书写顺序都只能依靠人工来维护、管理。

而这些模块化规范,其实目的就在于解决这些问题,CommonJS 是由 Node.js 做为管理者角色,来维护、控制各模块的依赖关系,文件的加载顺序。而 AMD 则是由 Require.js 来做为管理者角色,开发者不用在 HTML 里写那么多 <script>,并且也不必去关心这些文件谁写前谁写后,Require.js 会自动根据 require 来维护这些依赖关系,自动根据 requirejs.config 的配置信息去决定先加载哪一个文件后加载哪一个文件。

CMD规范

CMD 规范,相似于 AMD,一样也是用于解决前端里的模块化技术问题。而有了 AMD 为何还会有 CMD,我我的的理解是,AMD 的适用场景并无覆盖整个前端里的需求,或者说 AMD 自己也有一些缺点,致使了新的 CMD 规范的出现。

好比说,从使用方式上,AMD 就有不少跟 CommonJS 规范不一致的地方,对于从 CommonJS 转过来的这部分人来讲,可能就会很不习惯;再好比说,AMD 考虑的场景可能太多,又要适配 jQurey,又要适配 Node 等等;

总之,CMD 规范总有它出现和存在的理由,下面就大概来看看 CMD 的用法:

define&exports

相似于 AMD,CMD 规范中,也是经过 define 定义一个模块:

//module.js
define(function (require, exports, module) {
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    exports.name = name;
    exports.getWx = () => wx;
})

跟 AMD 不同的地方是,CMD 中 define 只接收一个参数,参数类型是一个函数,函数的参数也很重要,有三个,按顺序分别是 require,exports,module,做用就是 CommonJS 规范中三个命令的用途。

若是当前模块须要依赖其余模块,那么在内部,使用 require 命令便可,因此,函数的三个参数很重要。

当前模块若是不依赖其余模块,也没有对外提供任何接口,那么,函数能够没有参数,由于有了内部也没有使用。

而若是当前模块须要依赖其余模块,那么就须要使用到 require,因此函数第一个参数就是必须的;若是当前模块须要对外暴露接口,那么后两个参数也是须要的;

总之,建议使用 define 定义模块时,将函数三个参数都带上,用不用再说,规范一点总没错。

require

在有须要使用某个模块提供的功能时,经过 require 来声明依赖关系:

//main.js
define(function (require, exports, module) {
    console.log("=====main.js=======");

    var module1 = require("./module");//同步加载模块
    console.log(module1.name);

    require.async("./module2", function (module2) {//异步加载模块
        console.log(module2.wx);
    })
})

require 默认是以同步方式加载模块,若是须要异步加载,须要使用 require.async

使用步骤

1.下载 Sea.js,并放到项目中

Sea.js:https://github.com/seajs/seajs/releases

2.新建做为模块的文件,如 module.js,并经过 define 定义模块

//module.js
define(function (require, exports, module) {
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    exports.name = name;
    exports.getWx = () => wx;
})

3.其余须要依赖到该模块的地方经过 require 声明

//main.js
define(function (require, exports, module) {
    console.log("=====main.js=======");

    var module1 = require("./module");//同步加载模块
    console.log(module1.name);

    require.async("./module2", function (module2) {//异步加载模块
        console.log(module2.wx);
    })
})

4.最后也最重要的一步,在 html 中先加载 sea.js 并指定主模块的 js

<script src="js/lib/require.js"></script>
<script>
     seajs.use("./js/src/main.js");
</script>

使用步骤跟 AMD 很相似,首先是须要依赖于 Sea.js,因此必须先下载它。

而后定义模块、依赖模块、使用模块的方式就跟 CommonJS 很相似,这几个操做跟 AMD 会有些不一样,也许这点也正是 CMD 存在的缘由之一。

最后一步也是最重要一步,须要在 HTML 文档中加载 sea.js 文档,并指定入口的 js,注意作的事虽然跟 AMD 同样,但实现方式不同。

小结

其实,CMD 跟 CommonJS 很相似,甚至在模块化方面的工做,能够很通俗的将 sea.js 理解成 node.js 所作的事,只是有些 node.js 能完成但却没法经过 sea.js 来负责的工做须要开发人员手动处理,好比定义一个模块、经过 <script> 加载 sea.js 和指定主入口的 js 的工做。

CommonJS, AMD, CMD 三者间区别

下面分别从适用场景、使用步骤、使用方式、特性等几个方面来对比这些不一样的规范:

适用场景

CommonJS 用于服务端,基于 Node.js 的运行环境;

AMD 和 CMD 用于前端,基于浏览器的运行环境;

使用方式

CommonJS 经过 require 来依赖其余模块,经过 exports 来为当前模块暴露接口;

AMD 经过 define 来定义模块,经过 requirejs.config 来配置模块路径信息,经过 require 来依赖其余模块,经过 retrurn 来暴露模块接口;

CMD 经过 define 来定义模块,经过 require 来依赖其余模块,经过 exports 来为当前模块暴露接口;

使用步骤

CommonJS 适用的 Node.js 运行环境,无需其余步骤,正常使用模块技术便可;

AMD 适用的前端浏览器的运行环境没有 Require.js,因此项目中须要先加载 Require.js,而后再执行主入口的 js 代码,须要在 HTML 中使用相似以下命令:

<script src="js/lib/require.js" data-main="js/src/main.js"></script>

CMD 适用的前端浏览器的运行环境也没有 Sea.js,因此项目中也须要先加载 Sea.js,而后再执行主入口的 js 代码,须要在 HTML 中使用相似以下命令:

<script src="js/lib/sea.js"></script>
<script>
    seajs.use("./js/src/main.js");
</script>

特性

AMD:依赖前置、提早执行,如:

require(['module1','module2'], function(m1, m2){
    //...
})
define(['module1','module2'], function(m1, m2){
    //...
})

须要先将全部的依赖的加载完毕后,才会去处理回调中的代码,这叫依赖前置、提早执行;

CMD:依赖就近、延迟执行,如:

define(function(require, exports, module){
    //...
    var module1 = require("./module1");
    //...
    require("./module2", function(m2){
        //...
    });
})

等代码执行到 require 这行代码时才去加载对应的模块

ES6标准

ES6 中新增的模块特性,在上一篇中已经稍微介绍了点,这里也不具体展开介绍了,须要的话开头的声明部分有给出连接,自行查阅。

这里就简单说下,在前端浏览器中使用 ES6 模块特性的步骤:

1.定义模块,经过指定 <script type="module"> 方式

2.依赖其余模块使用 import,模块对外暴露接口时使用 export;

须要注意的一点是,当 JS 文件内出现 import 或者 export 时,这份 JS 文件必须声明为模块文件,即在 HTML 文档中经过指定 <script> 标签的 type 为 module,这样 import 或 export 才可以正常运行。

也就是说,使用其余模块的功能时,当前的 JS 文件也必须是模块。

另外,有一点,ES6 的模块新特性,全部做为模块的文件都须要开发人员手动去 HTML 文档中声明并加载,这是与其余方案不一样的一点,ES6 中 import 只负责导入指定模块的接口而已,声明模块和加载模块都须要借助 <script> 实现。

这里不详细讲 ES6 的模块特性,但想来说讲,一些转换工做的配置,由于:

  • 有些浏览器不支持 ES6 的语法,写完 ES6 的代码后,须要经过 Babel 将 ES6 转化为 ES5。
  • 生成了 ES5 以后,里面仍然有 require 语法,而浏览器并不认识 require 这个关键字。此时,能够用 Browserify 编译打包 js,进行再次转换。

而我是选择使用 WebStrom 做为开发工具的,因此就来说讲如何配置

WebStrom 的 Babel 配置

教程部分摘抄自:ES6的介绍和环境配置

1.新建项目
2.经过 npm 初始化项目

在安装 Babel 以前,须要先用 npm 初始化咱们的项目。打开终端或者经过 cmd 打开命令行工具,进入项目目录,输入以下命令: npm init -y,命令执行结束后,会在项目根目录下生成 package.json 文件

3.(首次)全局安装 Babel-cli

在终端里执行以下命令:

npm install -g babel-cli

4.本地安装 babel-preset-es2015 和 babel-cli

npm install --save-dev babel-preset-es2015 babel-cli

本地是指在项目的根目录下打开终端执行以上命令,执行结束,项目根目录的 package.json 文件中会多了 devDependencies 选项

5.新建 .babelrc 文件

在根目录下新建文件 .babelrc,输入以下内容:

{
    "presets":[
        "es2015"
    ],
    "plugins":[]
}

6.执行命令转换

babel js/src/main.js -o js/dist/main.js

-o 前是原文件,后面是转换后的目标文件

这是个人项目结构:

.json 文件和 node_modules 文件夹都是操做完上述步骤后会自动生成的,最后执行完命令后,会在 dist 目录下生成目标文件。

7.(可选)若是嫌每次执行的命令太过复杂,可利用 npm 脚本

babel js/src/main.js -o js/dist/main.js 这行代码复制到 package.json 里的 scripts 字段中:

之后每次都点一下 build 左边的三角形按钮运行一下脚本就能够了,省去了手动输命令的时间。

8.(可选)若是还嫌每次手动点击按钮运行脚本麻烦,可配置监听文件改动自动进行转换

打开 WebStrom 的 Setting -> Tools -> File Watchers,而后点击 + 按钮,选择 Babel 选项,而后进行配置:

9.最后,之后每次新的项目,除了第 3 步不用了以外,其他步骤仍旧须要进行。

WebStrom 的 Browserify 配置

步骤跟上述很相似,区别仅在于一个下载 babel,这里下载的是 browserify,以及转换的命令不一样而已:

1.新建项目
2.经过 npm 初始化项目

打开终端,进入到项目的根目录,执行 npm init -y,执行结束后会在根目录生成 package.json 文件

3.(首次)全局安装 browserify

在终端里执行以下命令:

npm install browserify -g

4.执行命令转换

browserify js/src/main.js -o js/dist/main.js --debug

-o 前是原文件,后面是转换后的目标文件

5.(可选)若是嫌每次执行的命令太过复杂,可利用 npm 脚本

browserify js/src/main.js -o js/dist/main.js --debug 这行代码复制到 package.json 里的 scripts 字段中:

之后每次都点一下 build 左边的三角形按钮运行一下脚本就能够了,省去了手动输命令的时间。

6.(可选)若是还嫌每次手动点击按钮运行脚本麻烦,可配置监听文件改动自动进行转换

打开 WebStrom 的 Setting -> Tools -> File Watchers,而后点击 + 按钮,选择 <custom> 选项,而后进行配置:

7.最后,之后每次新的项目,除了第 3 步不用了以外,其他步骤仍旧须要进行。


你们好,我是 dasu,欢迎关注个人公众号(dasuAndroidTv),公众号中有个人联系方式,欢迎有事没事来唠嗑一下,若是你以为本篇内容有帮助到你,能够转载但记得要关注,要标明原文哦,谢谢支持~
dasuAndroidTv2.png

相关文章
相关标签/搜索