30分钟学会前端模块化开发

早期的javascript版本没有块级做用域、没有类、没有包、也没有模块,这样会带来一些问题,如复用、依赖、冲突、代码组织混乱等,随着前端的膨胀,模块化显得很是迫切。javascript

前端模块化规范以下:css

1、前端模块化概要

1.一、模块概要

JavaScript在早期的设计中就没有模块、包、类的概念,开发者须要模拟出相似的功能,来隔离、组织复杂的JavaScript代码,咱们称为模块化。html

模块就是一个实现特定功能的文件,有了模块咱们就能够更方便的使用别人的代码,要用什么功能就加载什么模块前端

模块化开发的四点好处:vue

  (1)、 避免变量污染,命名冲突java

  (2)、提升代码复用率node

  (3)、提升了可维护性react

  (4)、方便依赖关系管理jquery

为了不缺乏模块带来的问题,咱们能够看看程序员应对的历程:webpack

1.二、函数封装

咱们在讲函数的时候提到,函数一个功能就是实现特定逻辑的一组语句打包,并且JavaScript的做用域就是基于函数的,因此把函数做为模块化的第一步是很天然的事情,在一个文件里面编写几个相关函数就是最开始的模块了

//函数1
function fn1(){
  //statement
}
//函数2
function fn2(){
  //statement

这样在须要的之后夹在函数所在文件,调用函数就能够了

缺点:

污染了全局变量,没法保证不与其余模块发生变量名冲突,并且模块成员之间没什么关系

1.三、对象封装

为了解决上面问题,对象的写法应运而生,能够把全部的模块成员封装在一个对象中

var myModule = {
var1: 1,

var2: 2,

fn1: function(){

},

fn2: function(){

}
}

这样咱们在但愿调用模块的时候引用对应文件,而后

myModule.fn2();

这样避免了变量污染,只要保证模块名惟一便可,同时同一模块内的成员也有了关系

缺陷:外部能够随意修改内部成员,这样就会产生意外的安全问题

myModel.var1 = 100;

1.四、当即执行函数表达式(IIFE)

能够经过当即执行函数表达式(IIFE),来达到隐藏细节的目的

var myModule = (function(){
var var1 = 1;
var var2 = 2;

function fn1(){

}

function fn2(){

}

return {
fn1: fn1,
fn2: fn2
};
})();

这样在模块外部没法修改咱们没有暴露出来的变量、函数

缺点:功能相对较弱,封装过程增长了工做量、仍会致使命名空间污染可能、闭包是有成本的。

JavaScript最初的做用仅仅是验证表单,后来会添加一些动画,可是这些js代码不少在一个文件中就能够完成了,因此,咱们只须要在html文件中添加一个script标签。

后来,随着前端复杂度提升,为了可以提升项目代码的可读性、可扩展性等,咱们的js文件逐渐多了起来,再也不是一个js文件就能够解决的了,而是把每个js文件当作一个模块。那么,这时的js引入方式是怎样的呢?大概是下面这样:

  <script src="jquery.js"></script>
  <script src="jquery.artDialog.js"></script>
  <script src="main.js"></script>
  <script src="app1.js"></script>
  <script src="app2.js"></script>
  <script src="app3.js"></script>

即简单的将全部的js文件通通放在一块儿。可是这些文件的顺序还不能出错,好比jquery须要先引入,才能引入jquery插件,才能在其余的文件中使用jquery。

优势:

相比于使用一个js文件,这种多个js文件实现最简单的模块化的思想是进步的。 

缺点:

污染全局做用域。 由于每个模块都是暴露在全局的,简单的使用,会致使全局变量命名冲突,固然,咱们也可使用命名空间的方式来解决。

对于大型项目,各类js不少,开发人员必须手动解决模块和代码库的依赖关系,后期维护成本较高。

依赖关系不明显,不利于维护。 好比main.js须要使用jquery,可是,从上面的文件中,咱们是看不出来的,若是jquery忘记了,那么就会报错。

1.五、模块化规范

常见的的JavaScript模块规范有:CommonJS、AMD、CMD、UMD、原生模块化

1.5.一、CommonJS

CommonJs 是服务器端模块的规范,Node.js采用了这个规范。

根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。

例如:

// foobar.js

//私有变量
var test = 123;

//公有方法
function foobar () {

this.foo = function () {
// do someing ...
}
this.bar = function () {
//do someing ...
}
}

//exports对象上的方法和变量是公有的
var foobar = new foobar();
exports.foobar = foobar;
//require方法默认读取js文件,因此能够省略js后缀
var test = require('./foobar').foobar;

test.bar();

CommonJS 加载模块是同步的,因此只有加载完成才能执行后面的操做。像Node.js主要用于服务器的编程,加载的模块文件通常都已经存在本地硬盘,因此加载起来比较快,不用考虑异步加载的方式,因此CommonJS规范比较适用。但若是是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。因此就有了 AMD CMD 解决方案。

1.5.二、AMD((Asynchromous Module Definition) 异步模块定义

AMD 是 RequireJS 在推广过程当中对模块定义的规范化产出

AMD异步加载模块。它的模块支持对象 函数 构造器 字符串 JSON等各类类型的模块。

适用AMD规范适用define方法定义模块。

//经过数组引入依赖 ,回调函数经过形参传入依赖
define(['someModule1', ‘someModule2’], function (someModule1, someModule2) {

function foo () {
/// someing
someModule1.test();
}

return {foo: foo}
});

AMD规范容许输出模块兼容CommonJS规范,这时define方法以下:

define(function (require, exports, module) {

var reqModule = require("./someModule");
requModule.test();

exports.asplode = function () {
//someing
}
});

1.5.三、CMD(Common Module Definition)通用模块定义

CMD是SeaJS 在推广过程当中对模块定义的规范化产出

CMD和AMD的区别有如下几点:

1.对于依赖的模块AMD是提早执行,CMD是延迟执行。不过RequireJS从2.0开始,也改为能够延迟执行(根据写法不一样,处理方式不经过)。

2.CMD推崇依赖就近,AMD推崇依赖前置。

//AMD
define(['./a','./b'], function (a, b) {

//依赖一开始就写好
a.test();
b.test();
});

//CMD
define(function (requie, exports, module) {

//依赖能够就近书写
var a = require('./a');
a.test();

...
//软依赖
if (status) {

var b = requie('./b');
b.test();
}
});

虽然 AMD也支持CMD写法,但依赖前置是官方文档的默认模块定义写法。

3.AMD的api默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的 require,提供 seajs.use()来实现模块系统的加载启动。CMD里每一个API都简单纯粹。

SeaJS 和 RequireJS的主要区别 在此有解释

1.5.四、UMD

UMD是AMD和CommonJS的综合产物。

AMD 浏览器第一的原则发展 异步加载模块。

CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。

这迫令人们又想出另外一个更通用的模式UMD (Universal Module Definition)。但愿解决跨平台的解决方案。

UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。

在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

(function (window, factory) {
if (typeof exports === 'object') {

module.exports = factory();
} else if (typeof define === 'function' && define.amd) {

define(factory);
} else {

window.eventUtil = factory();
}
})(this, function () {
//module ...
});

1.5.五、原生JS模块化(Native JS)

上述的模块都不是原生 JavaScript 模块。它们只不过是咱们用模块模式(module pattern)、CommonJS 或 AMD 模仿的模块系统。

JavaScript标准制定者在 TC39(该标准定义了 ECMAScript 的语法与语义)已经为 ECMAScript 6(ES6)引入内置的模块系统了。

ES6 为导入(importing)导出(exporting)模块带来了不少可能性。下面是很好的资源:

http://jsmodules.io/

http://exploringjs.com/

相对于 CommonJS 或 AMD,ES6 模块如何设法提供一箭双鵰的实现方案:简洁紧凑的声明式语法和异步加载,另外能更好地支持循环依赖。

1.5.六、小结

AMD(异步模块定义) 是 RequireJS 在推广过程当中对模块定义的规范化产出,CMD(通用模块定义)是SeaJS 在推广过程当中被普遍认知。RequireJs出自dojo加载器的做者James Burke,SeaJs出自国内前端大师玉伯。

二者的区别以下:

RequireJS 和 SeaJS 都是很不错的模块加载器,二者区别以下:

1. 二者定位有差别。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。SeaJS 则专一于 Web 浏览器端,同时经过 Node 扩展的方式能够很方便跑在 Node 服务器端

2. 二者遵循的标准有差别。RequireJS 遵循的是 AMD(异步模块定义)规范,SeaJS 遵循的是 CMD (通用模块定义)规范。规范的不一样,致使了二者 API 的不一样。SeaJS 更简洁优雅,更贴近 CommonJS Modules/1.1 和 Node Modules 规范。

3. 二者社区理念有差别。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。SeaJS 不强推,而采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。

4. 二者代码质量有差别。RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug。

5. 二者对调试等的支持有差别。SeaJS 经过插件,能够实现 Fiddler 中自动映射的功能,还能够实现自动 combo 等功能,很是方便便捷。RequireJS 无这方面的支持。

6. 二者的插件机制有差别。RequireJS 采起的是在源码中预留接口的形式,源码中留有为插件而写的代码。SeaJS 采起的插件机制则与 Node 的方式一致:开放自身,让插件开发者可直接访问或修改,从而很是灵活,能够实现各类类型的插件。

2、CommonJS

CommonJS就是一个JavaScript模块化的规范,该规范最初是用在服务器端NodeJS中,前端的webpack也是对CommonJS原生支持的。

根据这个规范,每个文件就是一个模块,其内部定义的变量是属于这个模块的,不会对外暴露,也就是说不会污染全局变量。

CommonJS的核心思想就是经过 require 方法来同步加载所要依赖的其余模块,而后经过 exports 或者 module.exports 来导出须要暴露的接口。

CommonJS API编写应用程序,而后这些应用能够运行在不一样的JavaScript解释器和不一样的主机环境中。

2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。这标志"Javascript模块化编程"正式诞生。由于老实说,在浏览器环境下,之前没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;可是在服务器端,必定要有模块,与操做系统和其余应用程序互动,不然根本无法编程。NodeJS是CommonJS规范的实现,webpack 也是以CommonJS的形式来书写。

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

Nodejs的模块是基于CommonJS规范实现的,经过转换也能够运行在浏览器端。

特色:

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

2.一、NodeJS中使用CommonJS模块管理

一、模块定义

根据commonJS规范,一个单独的文件是一个模块,每个模块都是一个单独的做用域,也就是说,在该模块内部定义的变量,没法被其余模块读取,除非为global对象的属性。

模块只有一个出口,module.exports对象,咱们须要把模块但愿输出的内容放入该对象。

mathLib.js模块定义

var message="Hello CommonJS!";

module.exports.message=message;
module.exports.add=(m,n)=>console.log(m+n);

二、模块依赖

加载模块用require方法,该方法读取一个文件而且执行,返回文件内部的module.exports对象。

myApp.js 模块依赖

var math=require('./mathLib');
console.log(math.message);
math.add(333,888);

三、测试运行

安装好node.JS

打开控制台,可使用cmd命令,也能够直接在开发工具中访问

运行

2.二、在浏览器中使用CommonJS 模块管理

因为浏览器不支持 CommonJS 格式。要想让浏览器用上这些模块,必须转换格式。浏览器不兼容CommonJS的根本缘由,在于缺乏四个Node.js环境的变量(module、exports、require、global)。只要可以提供这四个变量,浏览器就能加载 CommonJS 模块。
var math = require('math');
math.add(2, 3);

第二行math.add(2, 3),在第一行require('math')以后运行,所以必须等math.js加载完成。也就是说,若是加载时间很长,整个应用就会停在那里等。这对服务器端不是一个问题,由于全部的模块都存放在本地硬盘,能够同步加载完成,等待时间就是硬盘的读取时间。可是,对于浏览器,这倒是一个大问题,由于模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态

而browserify这样的一个工具,能够把nodejs的模块编译成浏览器可用的模块,解决上面提到的问题。本文将详细介绍Browserify实现Browserify是目前最经常使用的CommonJS格式转换的工具

请看一个例子,b.js模块加载a.js模块

// a.js
var a = 100;
module.exports.a = a;

// b.js
var result = require('./a');
console.log(result.a);

index.html直接引用b.js会报错,提示require没有被定义

//index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="myApp_01.js"></script>
</body>
</html>

这时,就要使用Browserify了

【安装】

使用下列命令安装browserify

npm install -g browserify

【转换】

使用下面的命令,就能将b.js转为浏览器可用的格式bb.js

$ browserify myApp.js > myApp_01.js

转换结果:

查看myapp_01.js,browserify将mathLib.js和myApp.js这两个文件打包为MyApp01.js,使其在浏览器端能够运行

(function () {
    function r(e, n, t) {
        function o(i, f) {
            if (!n[i]) {
                if (!e[i]) {
                    var c = "function" == typeof require && require;
                    if (!f && c) return c(i, !0);
                    if (u) return u(i, !0);
                    var a = new Error("Cannot find module '" + i + "'");
                    throw a.code = "MODULE_NOT_FOUND", a
                }
                var p = n[i] = {exports: {}};
                e[i][0].call(p.exports, function (r) {
                    var n = e[i][1][r];
                    return o(n || r)
                }, p, p.exports, r, e, n, t)
            }
            return n[i].exports
        }

        for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) o(t[i]);
        return o
    }

    return r
})()({
    1: [function (require, module, exports) {
        var message = "Hello CommonJS!";

        module.exports.message = message;
        module.exports.add = (m, n) => console.log(m + n);


    }, {}], 2: [function (require, module, exports) {
        var math = require('./mathLib');
        console.log(math.message);
        math.add(333, 888);
    }, {"./mathLib": 1}]
}, {}, [2]);

index.html引用bb.js,控制台显示100

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src="bb.js"></script> 
</body>
</html> 
 
运行结果:
 
 
虽然 Browserify 很强大,但不能在浏览器里操做,有时就很不方便。
 
纯浏览器的 CommonJS 模块加载器 require1k ( https://github.com/Stuk/require1k)。彻底不须要命令行,直接放进浏览器便可。

优势:

CommonJS规范在服务器端率先完成了JavaScript的模块化,解决了依赖、全局变量污染的问题,这也是js运行在服务器端的必要条件。

缺点:

此文主要是浏览器端js的模块化, 因为 CommonJS 是同步加载模块的,在服务器端,文件都是保存在硬盘上,因此同步加载没有问题,可是对于浏览器端,须要将文件从服务器端请求过来,那么同步加载就不适用了,因此,CommonJS是不太适用于浏览器端。

3、AMD

3.一、概要

CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操做。AMD规范则是非同步加载模块,容许指定回调函数。因为Node.js主要用于服务器编程,模块文件通常都已经存在于本地硬盘,因此加载起来比较快,不用考虑非同步加载的方式,因此CommonJS规范比较适用。可是,若是是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,所以浏览器端通常采用AMD规范。而AMD规范的实现,就是大名鼎鼎的require.js了。

3.一、require.js

Asynchronous Module Definition,中文名是异步模块定义。它是一个在浏览器端模块化开发的规范,因为不是js原生支持,使用AMD规范进行页面开发须要用到对应的函数库,也就是大名鼎鼎的RequireJS,实际上AMD是RequireJS在推广过程当中对模块定义的规范化的产出。Asynchronous Module Definition,中文名是异步模块。它是一个在浏览器端模块化开发的规范,因为不是js原生支持,使用AMD规范进行页面开发须要用到对应的函数库,也就是大名鼎鼎的RequireJS,实际上AMD是RequireJS在推广过程当中对模块定义的规范化的产出。

官网:http://www.requirejs.cn

requireJS主要解决两个问题:

1 多个js文件可能有依赖关系,被依赖的文件须要早于依赖它的文件加载到浏览器。

2 js加载的时候浏览器会中止页面渲染,加载文件愈多,页面失去响应的时间愈长。

//定义模块
define(['dependency'],function(){ var name = 'foo'; function printName(){ console.log(name); } return { printName:printName } }) //加载模块 require(['myModule'],function(my){ my.printName(); })

语法:

AMD标准中,定义了下面两个API:

1.require([module], callback)

2. define(id, [depends], callback)

即经过define来定义一个模块,而后使用require来加载一个模块。 而且,require还支持CommonJS的模块导出方式。

requireJS定义了一个函数define,它是全局变量,用来定义模块。

define(id,dependencies,factory)

——id 可选参数,用来定义模块的标识,若是没有提供该参数,脚本文件名(去掉拓展名)

——dependencies 是一个当前模块用来的模块名称数组

——factory 工厂方法,模块初始化要执行的函数或对象,若是为函数,它应该只被执行一次,若是是对象,此对象应该为模块的输出值。

在页面上使用require函数加载模块;

require([dependencies], function(){});

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

——第一个参数是一个数组,表示所依赖的模块;

——第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可使用这些模块

定义alert模块:

define(function () {
var alertName = function (str) {
alert("I am " + str);
}
var alertAge = function (num) {
alert("I am " + num + " years old");
}
return {
alertName: alertName,
alertAge: alertAge
};
});

引入模块:

require(['alert'], function (alert) {
alert.alertName('zhangsan');
alert.alertAge(21);
});

可是,在使用require.js的时候,咱们必需要提早加载全部的依赖,而后才可使用,而不是须要使用时再加载。

优势:

适合在浏览器环境中异步加载模块。能够并行加载多个模块。

缺点:

提升了开发成本,而且不能按需加载,而是必须提早加载全部的依赖。

3.三、使用技巧

请记住使用requirejs的口诀:两函数一配置一属性

3.3.一、data-main属性

requirejs须要一个根来做为搜索依赖的开始,data-main用来指定这个根。

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

这里就指定了根是app.js,只有直接或者间接与app.js有依赖关系的模块才会被插入到html中。

3.3.二、require.config() 配置

经过这个函数能够对requirejs进行灵活的配置,其参数为一个配置对象,配置项及含义以下:

baseUrl——用于加载模块的根路径。

paths——用于映射不存在根路径下面的模块路径。

shims——配置在脚本/模块外面并无使用RequireJS的函数依赖而且初始化函数。假设underscore并无使用 RequireJS定义,可是你仍是想经过RequireJS来使用它,那么你就须要在配置中把它定义为一个shim。

deps——加载依赖关系数组

require.config({
//默认状况下从这个文件开始拉去取资源
    baseUrl:'scripts/app',
//若是你的依赖模块以pb头,会从scripts/pb加载模块。
    paths:{
        pb:'../pb'
    },
// load backbone as a shim,所谓就是将没有采用requirejs方式定义
//模块的东西转变为requirejs模块
    shim:{
        'backbone':{
            deps:['underscore'],
            exports:'Backbone'
        }
    }
});

3.3.三、define()函数

该函数用于定义模块。形式以下。

//logger.js
define(["a"], function(a) {
    'use strict';
    function info() {
        console.log("我是私有函数");
    }
    return {
        name:"一个属性",
        test:function(a){
            console.log(a+"你好!");
            a.f();
            info();
        }
    }
});

define函数就受两个参数。

* 第一个是一个字符串数组,表示你定义的模块依赖的模块,这里依赖模块a;

* 第二个参数是一个函数,参数是注入前面依赖的模块,顺序同第一参数顺序。在函数中可作逻辑处理,经过return一个对象暴露模块的属性和方法,不在return中的能够认为是私有方法和私有属性。

3.3.四、require()函数

该函数用于调用定义好的模块,能够是用define函数定义的,也能够是一个shim。形式以下:

//app.js
require(['logger'], function (logger) {
    logger.test("tom");
    console.log(logger.name);
});
//输出结果:
//tom你好!
//不肯定(取决于a模块的f方法)
//我是私有函数
//一个属性

示例:

//index.html
<!DOCTYPE html>
<html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    </head>
    <body>
    <script src="js/require.js" data-main="app.js"></script>
    </body>
    </html>

<script>
//app.js
require.config({
//By default load any module IDs from js/
    baseUrl: 'js',
//except, if the module ID starts with "pb"
    paths: {
        pb: '../pb'
    },
    shim: {
        'world': {
            deps:['animalWorld'],
            // use the global 'Backbone' as the module name.
            exports: 'world'
        }
    }
});
require(['cat','dog','world'], function (cat,dog,world) {
    world.world();
    cat.say();
    dog.say();
});

//animal.js
define([], function() {
    'use strict';
    function _showName(name){
        console.log(name);
    }
    return {
        say(words){
            console.log(words);
        },
        showName(name){ //练习私有方法
            _showName(name);
        }
    }
});

//cat.js
define([
    'pb/animal'
], function(animal) {
    'use strict';
    return {
        say(){
            animal.say("喵喵");
            animal.showName("");
        }
    }
});

//dog.js
define([
    'pb/animal'
], function(animal) {
    'use strict';
    return {
        say(){
            animal.say("汪汪");
            animal.showName("");
        }
    }
});

//animalWorld.js
window.animal = function() {
    console.log("这里是动物世界!");
}
world.js

define([], function() {
    'use strict';
    return {
        world(){
            animal();
        }
    }
});
</script>
View Code

3.四、简单示例

目录结构:

模块定义:

 mathModule.js

define(function () {
   return{
       message:"Hello AMD!",
       add:function (n1,n2) {
           return n1+n2;
       }
   }
});

模块依赖:

 app.js

require(['mathModule'],function (mathModule) {
    console.log(mathModule.message);
    console.log(mathModule.add(100,200));
});

测试运行:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="../js/require2.1.11.js" data-main="app.js"></script>
</body>
</html>

结果:

3.五、加载 JavaScript 文件

RequireJS的目标是鼓励代码的模块化,它使用了不一样于传统<script>标签的脚本加载步骤。能够用它来加速、优化代码,但其主要目的仍是为了代码的模块化。它鼓励在使用脚本时以module ID替代URL地址。

RequireJS以一个相对于baseUrl的地址来加载全部的代码。 页面顶层<script>标签含有一个特殊的属性data-main,require.js使用它来启动脚本加载过程,而baseUrl通常设置到与该属性相一致的目录。下列示例中展现了baseUrl的设置:

<!--This sets the baseUrl to the "scripts" directory, and
loads a script that will have a module ID of 'main'-->
<script data-main="scripts/main.js" src="scripts/require.js"></script>

baseUrl亦可经过RequireJS config手动设置。若是没有显式指定config及data-main,则默认的baseUrl为包含RequireJS的那个HTML页面的所属目录。

RequireJS默认假定全部的依赖资源都是js脚本,所以无需在module ID上再加".js"后缀,RequireJS在进行module ID到path的解析时会自动补上后缀。你能够经过paths config设置一组脚本,这些有助于咱们在使用脚本时码更少的字。

有时候你想避开"baseUrl + paths"的解析过程,而是直接指定加载某一个目录下的脚本。此时能够这样作:若是一个module ID符合下述规则之一,其ID解析会避开常规的"baseUrl + paths"配置,而是直接将其加载为一个相对于当前HTML文档的脚本:

以 ".js" 结束.
以 "/" 开始.
包含 URL 协议, 如 "http:" or "https:".

通常来讲,最好仍是使用baseUrl及"paths" config去设置module ID。它会给你带来额外的灵活性,如便于脚本的重命名、重定位等。 同时,为了不凌乱的配置,最好不要使用多级嵌套的目录层次来组织代码,而是要么将全部的脚本都放置到baseUrl中,要么分置为项目库/第三方库的一个扁平结构,以下:

www/
index.html
js/
app/
sub.js
lib/
jquery.js
canvas.js
app.js

index.html:

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

app.js:

requirejs.config({
//By default load any module IDs from js/lib
    baseUrl: 'js/lib',
//except, if the module ID starts with "app",
//load it from the js/app directory. paths
//config is relative to the baseUrl, and
//never includes a ".js" extension since
//the paths config could be for a directory.
    paths: {
        app: '../app'
    }
});

// Start the main app logic.
requirejs(['jquery', 'canvas', 'app/sub'],
    function ($, canvas, sub) {
//jQuery, canvas and the app/sub module are all
//loaded and can be used here now.
    });

注意在示例中,三方库如jQuery没有将版本号包含在他们的文件名中。咱们建议将版本信息放置在单独的文件中来进行跟踪。使用诸如volo这类的工具,能够将package.json打上版本信息,并在磁盘上保持文件名为"jquery.js"。这有助于你保持配置的最小化,避免为每一个库版本设置一条path。例如,将"jquery"配置为"jquery-1.7.2"。

理想情况下,每一个加载的脚本都是经过define()来定义的一个模块;但有些"浏览器全局变量注入"型的传统/遗留库并无使用define()来定义它们的依赖关系,你必须为此使用shim config来指明它们的依赖关系。 若是你没有指明依赖关系,加载可能报错。这是由于基于速度的缘由,RequireJS会异步地以无序的形式加载这些库。

3.5.一、路径处理

假定当前路径以下:

moduleA:

define(function () {
    return {
        show: m => console.info(m)
    }
});

moduleB:

define(['moduleA'], function (a) {
    return {
        add: (m, n) => a.show(m + n)
    }
});

app.js:

require(['module/moduleB'], function (b) {
    b.add(100, 500);
});

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="js/require2.1.11.js" data-main="js/app.js"></script>
</body>
</html>

直接运行时,报错:

解决方法一:

能够看出运行时报错,缘由是baseUrl的值与app.js所在位置有关,当前应该是:baseUrl='js/',那么全部模块在依赖时都默认以js/开始,因此moduleB依赖ModuleA时也以js/开始,能够经过指定当前目录解决该问题,修改moduleB以下:

define(['./moduleA'], function (a) {
    return {
        add: (m, n) => a.show(m + n)
    }
});

./表示当前目录,../上一级,/表示从域名开始不含虚拟目录

运行结果:

解决方法二:

手动配置baseUrl,修改后以下:

app.js

//配置
requirejs.config({
    //模块依赖的基础路径,默认模块的加载位置
    baseUrl:'js/module/'
});

//模块引用
require(['moduleB'], function (b) {
    b.add(100, 500);
});

moduleB.js

define(['moduleA'], function (a) {
    return {
        add: (m, n) => a.show(m + n)
    }
});

运行结果:

3.5.二、依赖第三方的库(AMD依赖jQuery)

jQuery 1.7 开始支持将 jQuery 注册为一个AMD异步模块。有不少兼容的脚本加载器(包括 RequireJS 和 curl)均可以用一个异步模块格式来加载模块,这也就表示不须要太多 hack 就能让一切运行起来。能够看看jQuery 1.7 中的源码:

if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
  define( "jquery", [], function () { return jQuery; } );
}

其工做的原理是,所使用的脚本加载器经过指定一个属性,即 define.amd.jQuery 为 true,来标明本身能够支持多个 jQuery 版本。若是有兴趣了解特定的实现细节的话,咱们能够将 jQuery 注册为一个具名模块,由于可能会有这样的风险,即它可能被与其它使用了 AMD 的 define() 方法的文件拼合在一块儿,而没有使用一个合适的、理解匿名 AMD 模块定义的拼合脚本。

高版本的jQuery (1.11.1) 去掉了define.amd.jQuery判断:

if ( typeof define === "function" && define.amd ) {
    define( "jquery", [], function() {
        return jQuery;
    });
}

示例代码:

//配置
requirejs.config({
    //模块依赖的基础路径,默认模块的加载位置
    baseUrl:'js/',
    //路径
    paths:{
        //若是在路径前写上m/,则表示为:js/module/
        m:"module/",
        jquery:'common/jquery/jquery-1.12.4'
    }
});

//模块引用
require(['m/moduleB','m/moduleC','jquery'], function (b,c,$) {
    let m=500,n=100;
    b.add(m, n);
    c.sub(m,n);
    $("body").css({"background":"yellow"});
});

运行结果:

3.六、data-main 入口点

require.js 在加载的时候会检察data-main 属性:

<!--when require.js loads it will inject another script tag
(with async attribute) for scripts/main.js-->
<script data-main="scripts/main" src="scripts/require.js"></script>

你能够在data-main指向的脚本中设置模板加载 选项,而后加载第一个应用模块。.注意:你在main.js中所设置的脚本是异步加载的。因此若是你在页面中配置了其它JS加载,则不能保证它们所依赖的JS已经加载成功。

例如:

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

 main.js

// contents of main.js:
require.config({
    paths: {
        foo: 'libs/foo-1.1.3'
    }
});
// contents of other.js:

// This code might be called before the require.config() in main.js
// has executed. When that happens, require.js will attempt to
// load 'scripts/foo.js' instead of 'scripts/libs/foo-1.1.3.js'
require( ['foo'], function( foo ) {

});

3.七、定义模块

模块不一样于传统的脚本文件,它良好地定义了一个做用域来避免全局名称空间污染。它能够显式地列出其依赖关系,并以函数(定义此模块的那个函数)参数的形式将这些依赖进行注入,而无需引用全局变量。RequireJS的模块是模块模式的一个扩展,其好处是无需全局地引用其余模块。

RequireJS的模块语法容许它尽快地加载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的。同时由于无需建立全局变量,甚至能够作到在同一个页面上同时加载同一模块的不一样版本。

(若是你熟悉ConmmonJS,可参看CommonJS的注释信息以了解RequireJS模块到CommonJS模块的映射关系)。

一个磁盘文件应该只定义 1 个模块。多个模块可使用内置优化工具将其组织打包。

3.7.一、简单的值对

若是一个模块仅含值对,没有任何依赖,则在define()中定义这些值对就行了:

//Inside file my/shirt.js:
define({
    color: "black",
    size: "unisize"
});

3.7.二、函数式定义

若是一个模块没有任何依赖,但须要一个作setup工做的函数,则在define()中定义该函数,并将其传给define():

//my/shirt.js now does setup work
//before returning its module definition.
define(function () {
//Do setup work here

    return {
        color: "black",
        size: "unisize"
    }
});

3.7.三、存在依赖的函数式定义

若是模块存在依赖:则第一个参数是依赖的名称数组;第二个参数是函数,在模块的全部依赖加载完毕后,该函数会被调用来定义该模块,所以该模块应该返回一个定义了本模块的object。依赖关系会以参数的形式注入到该函数上,参数列表与依赖名称列表一一对应。

//my/shirt.js now has some dependencies, a cart and inventory
//module in the same directory as shirt.js
define(["./cart", "./inventory"], function(cart, inventory) {
//return an object to define the "my/shirt" module.
        return {
            color: "blue",
            size: "large",
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);

本示例建立了一个my/shirt模块,它依赖于my/cart及my/inventory。磁盘上各文件分布以下:

my/cart.js
my/inventory.js
my/shirt.js

模块函数以参数"cart"及"inventory"使用这两个以"./cart"及"./inventory"名称指定的模块。在这两个模块加载完毕以前,模块函数不会被调用。

严重不鼓励模块定义全局变量。遵循此处的定义模式,可使得同一模块的不一样版本并存于同一个页面上(参见 高级用法 )。另外,函参的顺序应与依赖顺序保存一致。

返回的object定义了"my/shirt"模块。这种定义模式下,"my/shirt"不做为一个全局变量而存在。

3.7.四、将模块定义为一个函数

对模块的返回值类型并无强制为必定是个object,任何函数的返回值都是容许的。此处是一个返回了函数的模块定义:

//A module definition inside foo/title.js. It uses
//my/cart and my/inventory modules from before,
//but since foo/title.js is in a different directory than
//the "my" modules, it uses the "my" in the module dependency
//name to find them. The "my" part of the name can be mapped
//to any directory, but by default, it is assumed to be a
//sibling to the "foo" directory.
define(["my/cart", "my/inventory"],
    function(cart, inventory) {
//return a function to define "foo/title".
//It gets or sets the window title.
        return function(title) {
            return title ? (window.title = title) :
                inventory.storeName + ' ' + cart.name;
        }
    }
);

3.7.五、简单包装CommonJS来定义模块

若是你现有一些以CommonJS模块格式编写的代码,而这些代码难于使用上述依赖名称数组参数的形式来重构,你能够考虑直接将这些依赖对应到一些本地变量中进行使用。你可使用一个CommonJS的简单包装来实现:

define(function(require, exports, module) {
        var a = require('a'),
            b = require('b');

//Return the module value
        return function () {};
    }
);

该包装方法依靠Function.prototype.toString()将函数内容赋予一个有意义的字串值,但在一些设备如PS3及一些老的Opera手机浏览器中不起做用。考虑在这些设备上使用优化器将依赖导出为数组形式。

更多的信息可参看CommonJS Notes页面,以及"Why AMD"页面的"Sugar"段落。

3.7.六、定义一个命名模块

你可能会看到一些define()中包含了一个模块名称做为首个参数:

//Explicitly defines the "foo/title" module:
define("foo/title",
    ["my/cart", "my/inventory"],
    function(cart, inventory) {
//Define foo/title object in here.
    }
);

这些常由优化工具生成。你也能够本身显式指定模块名称,但这使模块更不具有移植性——就是说若你将文件移动到其余目录下,你就得重命名。通常最好避免对模块硬编码,而是交给优化工具去生成。优化工具须要生成模块名以将多个模块打成一个包,加快到浏览器的载人速度。

示例:

moduleD.js:

define("md",[],function(){
        return {hi: () => console.info("Hello ModuleD!")};
});

app.js:

//配置
requirejs.config({
    //模块依赖的基础路径,默认模块的加载位置
    baseUrl:'js/',
    //路径
    paths:{
        //若是在路径前写上m/,则表示为:js/module/
        m:"module/",
        jquery:'common/jquery/jquery-1.12.4',
        md:'module/moduleD'
    }
});

//模块引用
require(['m/moduleB','m/moduleC','jquery','md'], function (b,c,$,d) {
    let m=500,n=100;
    b.add(m, n);
    c.sub(m,n);
    d.hi();
    $("body").css({"background":"yellow"});
});

使用命名模块时须要为命名模块指定路径,名称需惟一。

运行结果:

3.7.七、依赖非AMD模块

配置中shim参数为那些没有使用define()来声明依赖关系、设置模块的"浏览器全局变量注入"型脚本作依赖和导出配置。

添加一个非amd模块moduleE.js:

var moduleE = {
    hello: () => {
        console.info("Hello ModuleE!");
    }
};

引用ModuleE:

//配置
requirejs.config({
    //模块依赖的基础路径,默认模块的加载位置
    baseUrl:'js/',
    //路径
    paths:{
        //若是在路径前写上m/,则表示为:js/module/
        m:"module/",
        jquery:'common/jquery/jquery-1.12.4',
        md:'module/moduleD',
        moduleE:'module/moduleE'
    },
    //处理非标准的amd模块
    shim:{
        //这个键名为要载入的目标文件的文件名,不能随便命名不然拿不到改文件对外提供的接口的
        'moduleE':{
            exports:'moduleE'
        }
    }
});

//模块引用
require(['m/moduleB','m/moduleC','jquery','md','moduleE'], function (b,c,$,d,e) {
    let m=500,n=100;
    b.add(m, n);
    c.sub(m,n);
    d.hi();
    $("body").css({"background":"yellow"});
    e.hello();
});

运行结果:

3.7.八、注意事项

一个文件一个模块: 每一个Javascript文件应该只定义一个模块,这是模块名-至-文件名查找机制的天然要求。多个模块会被优化工具组织优化,但你在使用优化工具时应将多个模块放置到一个文件中。

define()中的相对模块名: 为了能够在define()内部使用诸如require("./relative/name")的调用以正确解析相对名称,记得将"require"自己做为一个依赖注入到模块中:

define(["require", "./relative/name"], function(require) {
    var mod = require("./relative/name");
});

或者更好地,使用下述为转换CommonJS模块所设的更短的语法:

define(function(require) {
    var mod = require("./relative/name");
});

相对路径在一些场景下格外有用,例如:为了以便于将代码共享给其余人或项目,你在某个目录下建立了一些模块。你能够访问模块的相邻模块,无需知道该目录的名称。

生成相对于模块的URL地址: 你可能须要生成一个相对于模块的URL地址。你能够将"require"做为一个依赖注入进来,而后调用require.toUrl()以生成该URL:

define(["require"], function(require) {
    var cssUrl = require.toUrl("./style.css");
});

控制台调试:若是你须要处理一个已经过require(["module/name"], function(){})调用加载了的模块,可使用模块名做为字符串参数的require()调用来获取它:

require("module/name").callSomeFunction()

注意这种形式仅在"module/name"已经由其异步形式的require(["module/name"])加载了后才有效。只能在define内部使用形如"./module/name"的相对路径。

4、CMD

CMD规范是阿里的玉伯提出来的,实现js库为sea.js。 它和requirejs很是相似,即一个js文件就是一个模块,可是CMD的加载方式更加优秀,是经过按需加载的方式,而不是必须在模块开始就加载全部的依赖。以下:

define(function(require, exports, module) {
    var $ = require('jquery');
    var Spinning = require('./spinning');
    exports.doSomething = ...
    module.exports = ...
})

优势:

一样实现了浏览器端的模块化加载。

能够按需加载,依赖就近。

缺点:

依赖SPM打包,模块的加载逻辑偏重。

其实,这时咱们就能够看出AMD和CMD的区别了,前者是对于依赖的模块提早执行,然后者是延迟执行。 前者推崇依赖前置,然后者推崇依赖就近,即只在须要用到某个模块的时候再require。 以下:

// AMD
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 
    a.doSomething()
// 此处略去 100 行 
    b.doSomething()
...
});
// CMD
define(function(require, exports, module) {
    var a = require('./a')
    a.doSomething()
// 此处略去 100 行 
    var b = require('./b')
// 依赖能够就近书写 
    b.doSomething()
// ... 
});

 模块定义语法

define(id, deps, factory)

由于CMD推崇一个文件一个模块,因此常常就用文件名做为模块id;
CMD推崇依赖就近,因此通常不在define的参数中写依赖,而是在factory中写。

factory有三个参数:

function(require, exports, module){}

1、require

require 是 factory 函数的第一个参数,require 是一个方法,接受 模块标识 做为惟一参数,用来获取其余模块提供的接口;

2、exports

exports 是一个对象,用来向外提供模块接口;

3、module

module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。

// 定义模块 myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
});

// 加载模块
seajs.use(['myModule.js'], function(my){

});

4.一、Seajs

Seajs是一个加载器、遵循 CMD 规范模块化开发,依赖的自动加载、配置的简洁清晰。

SeaJS是一个遵循CMD规范的JavaScript模块加载框架,能够实现JavaScript的模块化开发及加载机制。

与jQuery等JavaScript框架不一样,SeaJS不会扩展封装语言特性,而只是实现JavaScript的模块化及按模块加载。SeaJS的主要目的是令JavaScript开发模块化并能够轻松愉悦进行加载,将前端工程师从繁重的JavaScript文件及对象依赖处理中解放出来,能够专一于代码自己的逻辑。SeaJS能够与jQuery这类框架完美集成。使用SeaJS能够提升JavaScript代码的可读性和清晰度,解决目前JavaScript编程中广泛存在的依赖关系混乱和代码纠缠等问题,方便代码的编写和维护。

github:https://github.com/seajs/seajs

官网:http://www6.seajs.org

特色:

Sea.js 追求简单、天然的代码书写和组织方式,具备如下核心特性:

(一)、简单友好的模块定义规范:Sea.js 遵循 CMD 规范,能够像 Node.js 通常书写模块代码。

(二)、天然直观的代码组织方式:依赖的自动加载、配置的简洁清晰,可让咱们更多地享受编码的乐趣。

Sea.js 还提供经常使用插件,很是有助于开发调试和性能优化,并具备丰富的可扩展接口。

兼容:

Chrome 3+
Firefox 2+
Safari 3.2+
Opera 10+
IE 5.5+

基本应用:

导入Seajs库

去官网下载最新的seajs文件,http://seajs.org/docs/#downloads

在页尾引入seajs:

<script src="/site/script/sea.js"></script>

而后在它下面写模块的配置和入口。

// 加载入口模块
seajs.use("../static/hello/src/main");

配置和入口

这里解释下配置和入口的意思。

配置

一般在配置上修改seajs的路径和别名。

seajs的路径是相对于前面引入的seajs文件的。假如是这样的目录结构:

examples/
|-- index.html
|
`--about
| |-- news.html
|
`-- script
|-- seajs.js
|-- jquery.js
`-- main.js

咱们平时若是咱们在index.html上引用main.js路径应该是这样写的script/main.js,从news.html引用main.js就要这样写,../script/main.js。

而在seajs是相对于seajs文件的,一概直接使用main.js就OK了,是否是很方便呢?

既然这么方便那在什么状况须要配置呢?通常状况是用不到的。可是假如你的路径特别深 或者要作路径映射的时候它的做用就来了。下面介绍下经常使用的几个配置。

seajs.config({
// Sea.js 的基础路径(修改这个就不是路径就不是相对于seajs文件了)
    base: 'http://example.com/path/to/base/',
// 别名配置(用变量表示文件,解决路径层级过深和实现路径映射)
    alias: {
        'es5-safe': 'gallery/es5-safe/0.9.3/es5-safe',
        'json': 'gallery/json/1.0.2/json',
        'jquery': 'jquery/jquery/1.10.1/jquery'
    },
// 路径配置(用变量表示路径,解决路径层级过深的问题)
    paths: {
        'gallery': 'https://a.alipayobjects.com/gallery'
    }
});

入口

入口即加载,须要加载什么文件(模块加载器)就在这里引入。sea.js 在下载完成后,会自动加载入口模块。

seajs.use("abc/main"); //导入seajs.js同级的abc文件夹下的main.js模块的(后缀名可略去不写)

seajs.use()还有另一种用法。

有时候咱们写一个简单的单页并不想为它单独写一个js文件,选择在直接把js代码写在页面上,seajs经过seajs.use()实现了这个。接收两个参数第一个是文件依赖(单个用字符串数组均可以,多个需用数组表示),第二个是回调函数。

加载单个依赖

//加载模块 main,并在加载完成时,执行指定回调
seajs.use('./main', function(main) {
  main.init();
});

加载多个依赖

//并发加载模块 a 和模块 b,并在都加载完成时,执行指定回调
seajs.use(['./a', './b'], function(a, b) {
    a.init();
    b.init();
});

这里回掉函数中的a和b参数是与前面的模块暴露出来的接口一一对应的。有时候也许只须要使用b的接口,可是也要把a参数写上。什么是暴露接口下面会解释。

经过seajs.use()只能在第一个参数中引入模块,不能在回调函数中使用require()载入模块。 

模块开发

这里才是重点,其实也很简单就是一个书写规范(CMD)而已。

// 全部模块都经过 define 来定义
define(function(require, exports, module) {

// 经过 require 引入依赖
var $ = require('jquery');
var Spinning = require('./spinning');

// 经过 exports 对外提供接口
exports.doSomething = ...

// 或者经过 module.exports 提供整个接口
module.exports = ...

});

模块是经过define()方法包装的,而后内部痛过require()方法引入须要的依赖文件(模块)。

模块最好是面向对象开发的,这样最后能够方便的经过exports.doSomething或module.exports把模块的接口给暴露出来。若是你是写的是jq插件的话就不须要这个功能了,由于你的接口是写在jquery的对象里的。若是你不须要提供接口的话也能够不使用这两个属性哦!

事实上define方法还有另外几个参数,通常状况咱们用不到。具体看官方API。

注意事项

模块内的函数依赖必须交代清楚,防止模块在函数依赖加载前先加载出来。并且还加强了模块的独立性。

引入seajs的时候最好给<script>标签加个id,能够快速访问到这个标签(我是在模块合并时用到它的)

还有前面提到的使用seajs.use()在.html页面上写js时若是有多个模块依赖,须要使用暴露出来的接口就要让参数与它一一对应。

4.二、seajs示例

目录结构以下:

moduleH.js

define(function (require,exports,module) {
    var obj={
        msg:"Hello SeaJS!",
        show:()=>console.info(obj.msg)
    };
    exports.moduleH=obj;
});

main.js

seajs.config({
    //Sea.js 的基础路径(修改这个就不是路径就不是相对于seajs文件了)
    base: './js/',
    //别名配置(用变量表示文件,解决路径层级过深和实现路径映射)
    alias: {
        'jquery': 'common/jquery/jquery-1.12.4'
    },
    //路径配置(用变量表示路径,解决路径层级过深的问题)
    paths: {
        'm': 'module/'
    }
});

seajs.use(["m/moduleH.js",'jquery'], function (mh,$) {
    mh.moduleH.show();
    $("body").append("<h2>Hello CMD!</h2>");
})

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="js/sea.js"></script>
<script src="js/main.js"></script>
</body>
</html>

运行结果:

4.三、官方文档

若是您要更加深刻学习CMD与seajs能够参考下面的文档、https://seajs.github.io/seajs/docs/

4.3.一、入门

4.3.二、基础

4.3.三、插件

4.3.四、进阶

4.3.五、探讨

5、原生模块化(ECMAScript模块化)

ES6以前使用RequireJS或者seaJS实现模块化, requireJS是基于AMD规范的模块化库, 而像seaJS是基于CMD规范的模块化库, 二者都是为了为了推广前端模块化的工具。

如今ES6自带了模块化, 也是JS第一次支持module, 在好久之后 ,咱们能够直接做用import和export在浏览器中导入和导出各个模块了, 一个js文件表明一个js模块;

现代浏览器对模块(module)支持程度不一样, 目前都是使用babelJS, 或者Traceur把ES6代码转化为兼容ES5版本的js代码;

以前的几种模块化方案都是前端社区本身实现的,只是获得了你们的承认和普遍使用,而ES6的模块化方案是真正的规范。 在ES6中,咱们可使用 import 关键字引入模块,经过 export 关键字导出模块,功能较之于前几个方案更为强大,也是咱们所推崇的,可是因为ES6目前没法在浏览器中执行,因此,咱们只能经过babel将不被支持的import编译为当前受到普遍支持的 require。

虽然目前import和require的区别不大,可是仍是推荐使用使用es6,由于将来es6一定是主流,对于代码的迁移成本仍是很是容易的。 如:

import store from '../store/index'
import {mapState, mapMutations, mapActions} from 'vuex'
import axios from '../assets/js/request'
import util from '../utils/js/util.js'

export default {
    created () {
        this.getClassify();

        this.RESET_VALUE();
        console.log('created' ,new Date().getTime());

    }

5.一、ES6模块化特色

一、每个模块只加载一次, 每个JS只执行一次, 若是下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说就是一个对象;

二、每个模块内声明的变量都是局部变量, 不会污染全局做用域;

三、模块内部的变量或者函数能够经过export导出;

四、一个模块能够导入别的模块

5.二、在Chrome浏览器使用Module

Chrome 61就提供了对ES2015 import语句的支持,实现模块加载

查看版本的办法是:在chrome浏览器中输入chrome://version/

谷歌浏览器(Canary 60) – 须要在chrome:flags里开启”实验性网络平台功能(Experimental Web Platform)”

示例:lib.js

/**
 *定义模块
 */
//导出
export let msg="求和:";
export function sum(n){
    let total=0;
    for(var i=1;i<=n;i++){
        total+=i;
    }
    return total;
}

html:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Module模块</title>
    </head>
    <body>
        <script type="module">
            //导入
            import {sum,msg} from './lib.js';
            let result=sum(100);
            console.log(msg+""+result);
        </script>
    </body>
</html>

结果:

5.三、在Node.js中使用Module

5.3.一、方法一

在 Node.js 模块系统中,每一个文件都被视为独立的模块。

例子,假设有一个名为 foo.js 的文件:

const circle = require('./circle.js');
console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`);

在第一行中,foo.js 加载了同一目录下的 circle.js 模块。

circle.js 文件的内容为:

const { PI } = Math;

exports.area = (r) => PI * r ** 2;

exports.circumference = (r) => 2 * PI * r;

circle.js 模块导出了 area() 和 circumference() 两个函数。 经过在特殊的 exports 对象上指定额外的属性,函数和对象能够被添加到模块的根部。

模块内的本地变量是私有的,由于模块被 Node.js 包装在一个函数中(详见模块包装器)。 在这个例子中,变量 PI 是 circle.js 私有的。

module.exports属性能够被赋予一个新的值(例如函数或对象)。

以下,bar.js 会用到 square 模块,square 模块导出了 Square 类:

const Square = require('./square.js');
const mySquare = new Square(2);
console.log(`mySquare 的面积是 ${mySquare.area()}`);

square 模块定义在 square.js 中:

// 赋值给 `exports` 不会修改模块,必须使用 `module.exports`
module.exports = class Square {
constructor(width) {
this.width = width;
}

area() {
return this.width ** 2;
}
};

模块系统在 `require('module')` 模块中实现。

访问主模块

当 Node.js 直接运行一个文件时,require.main 会被设为它的 module。 这意味着能够经过 require.main === module 来判断一个文件是否被直接运行:

对于 foo.js 文件,若是经过 node foo.js 运行则为 true,但若是经过 require('./foo') 运行则为 false。

由于 module 提供了一个 filename 属性(一般等同于 __filename),因此能够经过检查 require.main.filename 来获取当前应用程序的入口点。

参考API:http://nodejs.cn/api/modules.html

5.3.二、方法二:experimental-modules

升级node 8.5 使用 experimental-modules参数,且要求全部文件名后缀都要修改成mjs
node --experimental-modules index.mjs
定义模块lib.mjs:

/**
 *定义模块
 */
//导出
export let msg="求和:";
export function sum(n){
    let total=0;
    for(var i=1;i<=n;i++){
        total+=i;
    }
    return total;
}

定义main.mjs文件

/**
 * 使用模块
 */
//导入
import { sum, msg } from './lib.mjs';
let result = sum(100);
console.log(msg + "" + result);

在命令行下转换到当前目录,使用node加参数experimental-modules执行,结果以下:

5.四、Babel

Babel是一个普遍使用的转码器,能够将ES6代码转为ES5代码,从而在现有环境执行。

5.4.一、配置环境

安装babel命令行工具:

npm install --global babel-cli

安装成功后可使用babel -V查看版本,可使用babel -help 查看帮助

建立项目,在当前项目中依赖babel-core

假定当前项目的目录为:E:\Desktop-temp\xww\FastResponse\Mobile\Hybird\vue2_01\vue07_03_babel

使用npm init能够初始化当前项目为node项目

npm install babel-core --save

依赖插件babel-preset-es2015

若是想使用es6语法,必须安装一个插件

npm install babel-preset-es2015

而后在文件夹下面建立一个叫.babelrc的文件,并写入以下代码:

{
"presets": ["es2015"]
}

windows不支持直接命令为.babelrc,能够在DOS下使用@echo结合>实现:

.babelrc文件以rc结尾的文件一般表明运行时自动加载的文件,配置等等的,相似bashrc,zshrc。一样babelrc在这里也是有一样的做用的,并且在babel6中,这个文件必不可少。
在babel6中,预设了6种,分别是:es201五、stage-0、stage-一、stage-二、stage-三、react

5.4.二、转换ES6为ES5

当环境准备好了,就能够编写一个es6风格的文件如:es6.js,内容以下:

let add=(x,y)=>x+y;
const n1=100,n2=200;
var result=add(n1,n2);
console.log(result);

在当前目录执行命令:

babel es6.js -o es5.js

转换后的结果es5.js:

"use strict";

var add = function add(x, y) {
  return x + y;
};
var n1 = 100,
    n2 = 200;
var result = add(n1, n2);
console.log(result);

从转换后的结果能够看出es6已变成es5了,箭头函数不见了。 

5.4.三、使用babel-node运行ES6模块化代码

babel-cli工具自带一个babel-node命令,提供一个支持ES6的REPL环境。它支持Node的REPL(交互式解释器环境)环境的全部功能,并且能够直接运行ES6代码。

在当前目录下建立lib.js文件:

/**
 *定义模块
 */
//导出
export let msg="求和:";
export function sum(n){
    let total=0;
    for(var i=1;i<=n;i++){
        total+=i;
    }
    return total;
}

建立main.js文件调用定义好的模块:

/**
 * 使用模块
 */
//导入
import { sum, msg } from './lib.js';
let result = sum(100);
console.log(msg + "" + result);

在命令行执行:babel-node main.js 结果以下:

到这里共讲解了3种能够运行ES6模块化的环境,任选一种能够用于学习。

5.五、模块(Modules)

ES6从语言层面对模块进行了支持。编写方式借鉴了流行的JavaScript模块加载器(AMD, CommonJS)。由宿主环境的默认加载器定义模块运行时的行为,采起隐式异步模式——在模块能够被获取和加载前不会有代码执行。

定义模块:

// lib/math.js

export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;

导入模块:

//所有导入  
import people from './example'  
  
//有一种特殊状况,即容许你将整个模块看成单一对象进行导入  
//该模块的全部导出都会做为对象的属性存在  
import * as example from "./example.js"  
console.log(example.name)  
console.log(example.age)  
console.log(example.getName())  
  
//导入部分  
import {name, age} from './example'  
  
//导出默认, 有且只有一个默认  
export default App  
  
// 部分导出  
export class App extend Component {};  

*表示全部,as取别名

// app.js

import * as math from "lib/math";
console.log("2π = " + math.sum(math.pi, math.pi));

// otherApp.js

导入部份内容

import {sum, pi} from "lib/math";
console.log("2π = " + sum(pi, pi));

还有的功能包括:export default and export *:

// lib/mathplusplus.js

export * from "lib/math";
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}

// app.js

import exp, {pi, e} from "lib/mathplusplus";
console.log("e^π = " + exp(pi));

导入的时候有没有大括号的区别:

  • 1.当用export default people导出时,就用 import people 导入(不带大括号)
  • 2.一个文件里,有且只能有一个export default。但能够有多个export。
  • 3.当用export name 时,就用import { name }导入(记得带上大括号)
  • 4.当一个文件里,既有一个export default people, 又有多个export name 或者 export age时,导入就用 import people, { name, age }
  • 5.当一个文件里出现n多个 export 导出不少模块,导入时除了一个一个导入,也能够用import * as example

模块的格式:

Babel能够将ES2015的模块转换为一下几种格式:Common.js,AMD,System,以及UMD。你甚至能够建立你本身的方式。

5.5.一、导出方式一

使用 export{接口} 导出接口, 大括号中的接口名字为上面定义的变量, import和export是对应的;

//lib.js 文件
let bar = "stringBar";
let foo = "stringFoo";
let fn0 = function() {
    console.log("fn0");
};
let fn1 = function() {
    console.log("fn1");
};
export{ bar , foo, fn0, fn1}

//main.js文件
import {bar,foo, fn0, fn1} from "./lib";
console.log(bar+"_"+foo);
fn0();
fn1();

示例:

先配置babel的运行环境,建立util.js文件:

let PI=3.14;
function getArea(r){
    return PI*r*r;
}

//集中导出对象
export {PI,getArea}

导入模块main.js:

import {PI,getArea} from './util'

console.log("R=5时面积为:"+getArea(5));

结果:

5.5.二、导出方式二

在export接口的时候, 咱们可使用 XX as YY, 把导出的接口名字改了, 好比: closureFn as sayingFn, 把这些接口名字改为不看文档就知道干什么的

//lib.js文件
let fn0 = function() {
    console.log("fn0");
};
let obj0 = {}
export { fn0 as foo, obj0 as bar};

//main.js文件
import {foo, bar} from "./lib";
foo();
console.log(bar);

5.5.三、导出方式三

这种方式是直接在export的地方定义导出的函数,或者变量:

//lib.js文件
export let foo = ()=> {console.log("fnFoo") ;return "foo"},bar = "stringBar";

//main.js文件
import {foo, bar} from "./lib";
console.log(foo());
console.log(bar);

5.5.四、导出方式四

这种导出的方式不须要知道变量的名字, 至关因而匿名的, 直接把开发的接口给export;
若是一个js模块文件就只有一个功能, 那么就可使用export default导出;

//lib.js
export default "string";

//main.js
import defaultString from "./lib";
console.log(defaultString);

5.5.五、导出方式五

export也能默认导出函数, 在import的时候, 名字随便写, 由于每个模块的默认接口就一个

//lib.js
let fn = () => "string";
export {fn as default};

//main.js
import defaultFn from "./lib";
console.log(defaultFn());

5.5.六、导出方式六

使用通配符* ,从新导出其余模块的接口

//lib.js
export * from "./other";
//若是只想导出部分接口, 只要把接口名字列出来
//export {foo,fnFoo} from "./other";

//other.js
export let foo = "stringFoo", fnFoo = function() {console.log("fnFoo")};

//main.js
import {foo, fnFoo} from "./lib";
console.log(foo);
console.log(fnFoo());

在import的时候可使用通配符*导入外部的模块:

import * as obj from "./lib";
console.log(obj);

5.六、模块加载器(Module Loaders)

这并非ES2015的一部分:这部分ECMAScript 2015规范是由实现定义(implementation-defined)的。最终的标准将在WHATWG的Loader 规范中肯定,目前这项工做正在进行中,下面的内容来自于以前的ES2015草稿。

模块加载器支持如下功能:

  • 动态加载(Dynamic loading)
  • 状态一致性(State isolation)
  • 全局空间一致性(Global namespace isolation)
  • 编译钩子(Compilation hooks)
  • 嵌套虚拟化(Nested virtualization)

你能够对默认的加载器进行配置,构建出新的加载器,能够被加载于独立或受限的执行环境。

// 动态加载 – ‘System’ 是默认的加载器
System.import("lib/math").then(function(m) {
alert("2π = " + m.sum(m.pi, m.pi));
});

// 建立执行沙箱 – new Loaders
var loader = new Loader({
global: fixup(window) // replace ‘console.log’
});
loader.eval("console.log(\"hello world!\");");

// 直接操做模块的缓存
System.get("jquery");
System.set("jquery", Module({$: $})); // WARNING: not yet finalized

须要额外的polyfill
因为Babel默认使用common.js的模块,你须要一个polyfill来使用加载器API。

使用模块加载器
为了使用此功能,你须要告诉Babel使用system模块格式化工具。

6、UMD(通用的模块定义)

UMD(Universal Module Definition)通用的模块定义、UMD等于CommonJS加上AMD。UMD的工做其实就是作了一个判断:

  • - 先判断当前环境对NodeJs支持的模块是否存在,存在就用Node.js模块模式(exports)。
  • - 若是不支持,就判断是否支持AMD(define),存在就使用AMD方式加载。

CommonJs和AMD风格同样流行,彷佛缺乏一个统一的规范。因此人们产生了这样的需求,但愿有支持两种风格的“通用”模式,因而通用模块规范(UMD)诞生了。

不得不认可,这个模式略难看,可是它兼容了AMD和CommonJS,同时还支持老式的“全局”变量规范,同时兼容先后端,模块定义写法以下:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
        define(['b'], factory);
    } else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
        module.exports = factory(require('b'));
    } else {
// Browser globals (root is window)
        root.returnExports = factory(root.b);
    }
}(this, function (b) {
//use b in some fashion.

// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
    return {};
}));

写法2:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
// AMD
        define(['jquery', 'underscore'], factory);
    } else if (typeof exports === 'object') {
// Node, CommonJS之类的
        module.exports = factory(require('jquery'), require('underscore'));
    } else {
// 浏览器全局变量(root 即 window)
        root.returnExports = factory(root.jQuery, root._);
    }
}(this, function ($, _) {
// 方法
    function a() {
    }; // 私有方法,由于它没被返回 (见下面)

    function b() {
    }; // 公共方法,由于被返回了
    function c() {
    };
//公共方法,由于被返回了
// 暴露公共方法
    return {
        b: b,
        c: c
    }
}));

写法3(Vue):

/*!
 * Vue.js v2.5.17
 * (c) 2014-2018 Evan You
 * Released under the MIT License.
 */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global.Vue = factory());
}(this, (function () {
  
return {};

})));

6.一、UMD示例

6.1.一、定义模块Utils.js

(function (global, factory) {
    if (typeof define === 'function' && (define.amd || define.cmd)) {
        // AMD规范. 注册一个匿名模块,兼容AMD与CMD
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        //CommonJS规范,NodeJS运行环境
        module.exports = factory();
    } else {
        //浏览器全局对象注册
        global.UMD = factory();
    }
}(this, function () {
    var msg = "UMD!";
    //返回要导出的对象
    return {
        show: function () {
            console.log("Hello " + msg);
        }
    };
}));

6.1.二、在CommonJS规范下运行

useUtils.js

var utils=require('./Utils.js');
utils.show();

运行结果:

6.1.三、在AMD规范下运行

app.js

//配置
requirejs.config({
    //模块依赖的基础路径,默认模块的加载位置
    baseUrl:'js/',
    //路径
    paths:{
        //若是在路径前写上m/,则表示为:js/module/
        m:"module/",
        jquery:'common/jquery/jquery-1.12.4',
        md:'module/moduleD',
        moduleE:'module/moduleE',
        mf:'module/moduleF',
        moduleG:'module/moduleG'
    },
    //处理非标准的amd模块
    shim:{
        //这个键名为要载入的目标文件的文件名,不能随便命名不然拿不到改文件对外提供的接口的
        'moduleE':{
            exports:'moduleE'
        },
        'moduleG':{
            exports:'moduleG'
        }
    }
});

//模块引用
require(['m/moduleB','m/moduleC','jquery','md','moduleE','mf','moduleG','m/Tools','m/Utils'], function (b,c,$,d,e,f,g,umd,mu) {
    mu.show();
});

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>AMD - requirejs</title>
</head>
<body>
<script src="js/require2.1.11.js" data-main="js/app.js"></script>
</body>
</html>

运行结果:

6.1.四、在CMD规范下运行

app.js

seajs.config({
    // Sea.js 的基础路径(修改这个就不是路径就不是相对于seajs文件了)
    base: './javascript/module/',
    //别名配置(用变量表示文件,解决路径层级过深和实现路径映射)
    alias:{
        'jquery':'common/jquery/jquery-1.12.4'
    },
    // 路径配置(用变量表示路径,解决路径层级过深的问题)
    paths: {
        'xy': 'xDirectory/yDirectory/'
    }
});

//引用模块,依赖模块
seajs.use(['moduleA','jquery','xy/moduleC','xy/moduleD','Utils'],function (ma,$,mc,md,mu) {
    mu.show();
});

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="js/sea.js"></script>
<script src="js/main.js"></script>
</body>
</html>

运行结果:

6.1.五、原生浏览器环境运行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>UMD</title>
</head>
<body>
<script src="javascript/module/Utils.js"></script>
<script>
    UMD.show();
</script>
</body>
</html>

运行结果:

从上面的示例运行结果能够看出采用UMD定义的模块能够兼容CommonJS、AMD、CMD与浏览器原生环境,同时兼容先后台。写法并不是固定能够根据须要变化。

7、NodeJS包管理器

npm 为你和你的团队打开了链接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即,代码模块)。来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您可以轻松跟踪依赖项和版本。

仓库:https://www.npmjs.com/

7.一、npm概要

npm全称为Node Package Manager,是一个基于Node.js的包管理器,也是整个Node.js社区最流行、支持的第三方模块最多的包管理器。

npm的初衷:JavaScript开发人员更容易分享和重用代码。

npm的使用场景:

  • 容许用户获取第三方包并使用。
  • 容许用户将本身编写的包或命令行程序进行发布分享。

npm版本查询:npm -v 

npm安装:

  一、安装nodejs

    因为新版的nodejs已经集成了npm,因此可直接经过输入npm -v来测试是否成功安装。

  二、使用npm命令来升级npm: npm install npm -g

7.二、包(package)

包是描述一个文件或一个目录。一个包的配置一般由如下构成:

  • 一个文件夹包含一个package.json配置文件。
  • 包含(含有package.json文件的文件夹)的Gzip压缩文件。
  • 解析gzip的url
  • 为注册表添加<name>@<version>的url 信息

注意的是即便你历来没有在注册中心发布你的公共包,你可能仍然能够获得不少全部这些package

7.三、模块(module)

模板是经过配置文件中的一个dom节点进行包含一个或多个包。一般通常由包和配置文件以及相关模块程序构成完成一个或多个业务功能操做。

一个模块能够在node . js 程序中装满任何的require()任何。 如下是全部事物加载模块的例子 :

一个文件夹package.json文件包含一个main字段。

一个文件夹index.js文件。

一个JavaScript文件。

7.四、包和模块的关系

通常来讲在js程序中使用require加载它们的模块在节点中进行配置npm包,一个模块不必定是一个包。

例如,一些cli包, js程序节点中只包含一个可执行的 命令行界面,不提供main字段。 那么这些包不是模块。

几乎全部npm包(至少,那些节点计划)包含许多模块在他们(由于每一个文件加载require()是一个模块)。

几乎全部的npm包都关联着多个模块,由于每一个文件都使用require()加载一个模块。

从module加载文件中的上下文node节点。如:var req = require('request')。咱们可能会说,“request模块赋值给req这个变量”。

7.5.npm的生态系统

package.json文件定义的是包。

node_modules文件夹是存储模块的地方。便于js查找模块。

例如:
若是建立一个node_modules/foo.js文件,经过var f=require('foo.js')进行加载模块。由于它没有package.json文件因此foo.js不是一个包。
若是没有建立index.js包或者package.json文件"main"字段,即便是在安装node_modules,由于它没有require()因此它不是一个模块。

经常使用命令:

npm install [-g] 本地或全局安装模块
npm uninstall [-g] 本地或全局卸载模块
npm update 更新模块
npm ls 查看安装的模块
npm list 列出已安装模块
npm show  显示模块详情
npm info 查看模块的详细信息
npm search 搜索模块
npm publish 发布模块
npm unpublish 删除已发布的模块
npm -v 或 npm version显示版本信息
npm view npm versions 列出npm 的全部有效版本
npm install -g npm@2.14.14 /npm update -g npm@2.14.14  安装指定的npm版本
npm init 引导建立一个package.json文件,包括名称、版本、做者这些信息等
npm outdated  #检查模块是否已通过时
npm root  [-g] 查看包的安装路径,输出 node_modules的路径,
npm help 查看某条命令的详细帮助 例如输入npm help install
npm config 管理npm的配置路径

更多介绍:

npm中文网:https://www.npmjs.com.cn/

点击查看《Node.js开发Web后台服务》

8、视频

 https://www.bilibili.com/video/av37008594/

9、示例

https://git.dev.tencent.com/zhangguo5/ModuleDemo.git

https://git.dev.tencent.com/zhangguo5/Module01.git

10、做业

一、复现全部的上课示例

二、请定义一个模块(UMD),该模块实现计算斐波拉契数列功能,要求在以下面种环境下都测试经过:
  2.一、CommonJS,NodeJS
  2.二、AMD
  2.三、CMD
  2.四、浏览器 (MyUmd.feb(6),输出:8)

//斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:一、一、二、三、五、八、1三、2一、3四、……在数学上,斐波纳契数列以以下被以递推的方法定义:F(1)=1,F(2)=1, F(3)=2,F(n)=F(n-1)+F(n-2)(n>=4,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。
View Code  

三、将javascript自定义插件的大做业实现UMD功能。