JAVAScript:前端模块化开发

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

1、前端模块化概要

1.一、模块概要

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

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

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

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

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

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

  (4)、方便依赖关系管理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方法)
//我是私有函数
//一个属性
复制代码

示例:

  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>
复制代码

结果:

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){}

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

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

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

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

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

});
复制代码

什么是Seajs
Seajs是一个加载器
遵循 CMD 规范模块化开发,依赖的自动加载、配置的简洁清晰。
兼容性
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 的简单配置
seajs.config({
base: "../sea-modules/",
alias: {
"jquery": "jquery/jquery/1.10.1/jquery.js"
}
});

// 加载入口模块
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()载入模块。 ——141023补充

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

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

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

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

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

});
模块是经过define()方法包装的,而后内部痛过require()方法引入须要的依赖文件(模块)。(也能够引入.css文件哦~)
模块最好是面向对象开发的,这样最后能够方便的经过exports.doSomething或module.exports把模块的接口给暴露出来。若是你是写的是jq插件的话就不须要这个功能了,由于你的接口是写在jquery的对象里的。若是你不须要提供接口的话也能够不使用这两个属性哦!

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

小结
其实Seajs的基本使用就这么简单,平常使用足够了,以前看官网的5分钟教程楞是没看懂,等会的时候回头想一想真的是5分钟学会啊,悟性过低- -||

注意事项
模块内的函数依赖必须交代清楚,防止模块在函数依赖加载前先加载出来。并且还加强了模块的独立性。
引入seajs的时候最好给<script>标签加个id,能够快速访问到这个标签(我是在模块合并时用到它的)
还有前面提到的使用seajs.use()在.html页面上写js时若是有多个模块依赖,须要使用暴露出来的接口就要让参数与它一一对应。

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());

    }
复制代码

 

2.一、ES6模块化特色

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

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

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

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

2.一、在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>
复制代码

结果:

2.二、在Node.js中使用Module

2.2.一、方法一:experimental-modules

在 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

2.2.二、方法二: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执行,结果以下:

2.三、Babel

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

2.3.一、配置环境

安装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

2.3.二、转换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了,箭头函数不见了。 

2.3.三、使用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模块化的环境,任选一种能够用于学习。

2.四、模块(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。你甚至能够建立你本身的方式。

2.4.一、导出方式一

使用 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));

结果:

2.4.二、导出方式二

在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);
复制代码

2.4.三、导出方式三

这种方式是直接在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);
复制代码

2.4.四、导出方式四

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

复制代码
//lib.js
export default "string";

//main.js
import defaultString from "./lib";
console.log(defaultString);
复制代码

2.4.五、导出方式五

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

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

//main.js
import defaultFn from "./lib";
console.log(defaultFn());
复制代码

2.4.六、导出方式六

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

复制代码
//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);

2.五、模块加载器(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、NodeJS包管理器

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

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

6.一、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

6.二、包(package)

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

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

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

6.三、模块(module)

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

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

复制代码
一个文件夹package.json文件包含一个main字段。

一个文件夹index.js文件。

一个JavaScript文件。
复制代码

6.四、包和模块的关系

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

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

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

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

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

6.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的配置路径
复制代码
相关文章
相关标签/搜索