javascript 之模块化篇

什么是模块化?

模块化就是把系统分离成独立功能的方法,这样咱们须要什么功能,就加载什么功能。javascript

优势:
可维护性:根据定义,每一个模块都是独立的,良好设计的模块会尽可能与外部的代码撇清关系,以便于独立对其进行改进和维护。
可复用性:能够重复利用,而不用常常复制本身以前写过的代码html

原始JS开发问题

一、污染全局变量
//a.js 文件:前端

var test1='aaaaaa';
//b.js 文件
var test1='bbbbbb';
 <script>
    console.log('test1='+test1);//bbbbbb;
 
</script>
console test1 输出'bbbbbb';悲剧啊

二、命名冲突java

//a.js 文件:
function fun(){
    console.log('this is b');
}
 //b.js 文件
 
function fun(){
    console.log('this is b');
}
//main.js 文件
<script src="a.js"></script>
<script src="b.js"></script>
<script>
    fun();//this is b;
</script>
小张在a.js定义了fun(),小李在b.js又定义了fun(),a,b被小王引入到main.js,执行fun(),输出this is b;

三、依赖关系
b.js依赖a.js,标签的书写顺序必须是:node

<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>

这样在多人开发的时候很难协调啊,使人头疼的问题。jquery

解决冲突的方式

一、使用java式的命名空间
二、变量前加“_”
三、对象写法git

var module1={
    test1:'aaaaaa',
    fun:function(){
        console.log(this.test1);
    }
}
变量和函数封装在对象里面,使用时,调用对象的属性便可:
module1.fun();//aaaaaa
可是这样的写法会暴露全部模块成员,内部状态能够被外部改写,
module1.test1='cccccc';

四、匿名闭包函数es6

var  module1=(function(){
    var test1='aaaaaa';
    var fun=function(){
        console.log('this is a');
    }
    return{
        fun:fun
    }
}());

匿名函数有本身的做用域,这样外部代码没法读取 module1 function 里面的变量了,从而也不会修改变量或者是覆盖同名变量了,可是仍是有缺陷的,module1这个的变量仍是暴露到全局了,而去随着模块的增多,全局变量会愈来愈多。
五、全局引入
像jquery库使用的全局引入。和匿名闭包函数类似,只是传入全局变量的方法不一样
(function(window){github

var test1='aaaaaa';
window.testFun=function(){//经过给window添加属性而暴漏到全局
    console.log(test1);
}

}(window));web

经过匿名函数包装代码,所依赖的外部变量传给这个函数,在函数内部可使用这些依赖,而后在函数的最后把模块自身暴漏给window。

3,4,5解决方法都是经过定一个全局变量来把全部的代码包含在一个函数内,由此来建立私有的命名空间和闭包做用域。

本文着重介绍几种广受欢迎的解决方案:CommonJS,AMD,CMD,ES模块化。

CommonJs

根据CommonJs规范,每一个文件就是一个模块,有本身的做用域。在一个文件里面定义的变量、函数、类,都是私有的,对其余文件不可见。

commonJS中模块能够加载屡次,可是只会在第一次加载的时候运行一次,而后运行结构被缓存,再次加载就是读取缓存的结果。

CommonJS规范加载模块是同步的,也就是说,加载完成才能够执行后面的操做,Node.js主要用于服务器编程,模块通常都是存在本地硬盘中,加载比较快,因此Node.js采用CommonJS规范。

CommonJS规范分为三部分:module(模块标识),require(模块引用), exports(模块定义),
module变量在每一个模块内部,就表明当前模块;
exports属性是对外的接口,用于导出当前模块的方法或变量;
require()用来加载外部模块,读取并执行js文件,返回该模块的exports对象;

一、commonJs模块定义

module.exports定义模块:

//math.js
let add=(x,y)=>{
    return x+y;
}
let sub=(x,y)=>{
    return x-y;
}

module.exports={
    add:add,
    sub:sub
};

exports 定义模块:

let add=(x,y)=>{
    return x+y;
}
let sub=(x,y)=>{
    return x-y;
}
exports.add=add;
exports.sub=sub;

注意:不能够直接对exports赋值,exports=add;

exports和module.exports有什么区别呢?
在每一个模块中Node都提供了一个Module 对象,表明当前模块。

//console.log(Module);
Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/Users/zss/node-Demo/my-app/testNOde/b.js',
  loaded: false,
  children: [],
  paths: 
   [ '/Users/zss/node-Demo/my-app/testNOde/node_modules',
     '/Users/zss/node-Demo/my-app/node_modules',
     '/Users/zss/node-Demo/node_modules',
     '/Users/zss/node_modules',
     '/Users/node_modules',
     '/node_modules' 
     ] 
   }

module.exports属性表示当前模块对外输出的接口,其余文件加载该模块,实际上就是读取module.exports变量。
为了方便,Node为每一个模块提供一个exports变量,指向module.exports。咱们把它们都打印出来看看究竟,

//test.js
console.log(module.exports);
console.log(exports);
console.log(module.exports===exports);

exports.test = ()=>{
    console.log('exports 1');
};
module.exports.test1 = ()=>{
    console.log('module.exports 1');
};
console.log(module.exports);
console.log(exports);

//输出:
{}
{}
true
{ test: [Function], test1: [Function] }
{ test: [Function], test1: [Function] }

从上例能够看出:
1.每一个模块文件一建立,有个var exports = module.exports = {};使exports和module.exports都指向一个空对象。
**2.module是全局内置对象,exports是被var建立的局部对象,module.exports和exports所指向的内存地址相同
全部的exports收集到的属性和方法,都赋值给了Module.exports,最终返回给模块调用的是module.exports而不是exports。**

再举个例子:

//test.js
exports.test = ()=>{
    console.log('exports 1');
};
module.exports={
    test:function(){
        console.log('module.exports 1');
    },
    testmodule:()=>{
        console.log('module.exports 2')
    }
}
console.log(module.exports);
console.log(exports);

 
 //输出
{ test: [Function: test], testmodule: [Function: testmodule] }
{ test: [Function] }

//在index.js文件中调用test2.js
let a=require('./test2');
a.test();
a.testmodule();
//输出:
module.exports 1
module.exports 2

全部的exports收集到的属性和方法,都赋值给了Module.exports,当直接把函数和属性传给module.exports时,module.exports与exports不想等了,在调用时候,exports的属性和方法会被忽略,因此最终返回给模块调用的是module.exports而不是exports。

二、模块分类

NodeJs的模块分为两类:
一类是原生模块,例如http,fs,path 等等。node在加载原生模块的时候,不须要传入路径,NodeJs将原生模块的代码编译到了二进制执行文件中,加载速度快。
一类是文件模块,动态加载模块,
可是NodeJs对原生模块和文件模块都进行了缓存,第二次require时,就是执行的内存中的文件。

三、commonJs模块加载规则

index.js调用math模块:

let math=require('./math');
let test=math.add(3,3);
console.log(test);

执行index.js 输出:6;

当咱们执行node index.js的时候,第一语句就是“require('./math');” 加载 math文件。加载math文件这个动做是由原生模块module的runMain()实现的。

有没有注意到上面写的是加载math文件,并无明确指出是js文件。
NodeJS加载文件模块基本流程:
一、根据名称按照‘.js’,‘.node‘,’.json‘的顺讯依次查找,若是是.node或者.json的文件最好加上扩展名,加载速度快。
二、查找到math.js,读取js内容,将使用function进行包装,这样能够避免污染全局环境,该函数的参数包括require、module、exports等等参数,以mathi.js为例:

(function(exports,require,module,__filename,__dirname){
        let add=(x,y)=>{
            return x+y;
        }
        let sub=(x,y)=>{
            return x-y;
        }

        module.exports={
            add:add,
            sub:sub
        };

 })

require 方法中的文件查找规则很复杂底,在网上copy了一个图:

clipboard.png

更详细的加载规则能够参考:http://www.infoq.com/cn/artic...

四、commonJs模块的加载机制:

//lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
//index.js
var mod=require('./lib');
consoe.log(mod.counter);
mod.incCounter();
consoe.log(mod.counter);

输出:3
     3

commonJS中模块加载之后,它的内部变化不会影响其内部变量,由于它们会被缓存,因此它输出的是值的拷贝。

CommonJS规范比较适用服务器端,若是是浏览器就须要异步加载模块了,因此就有了AMD,CMD解决方案。

AMD(requireJS)

 AMD是"Asynchronous Module Definition"的简写,也就是异步模块定义。它采用异步方式加载模块。经过define方法去定义模块,require方法去加载模块。

AMD模块定义:

define(function(){
    let add=(x,y)=>{
        return x+y;
    }
    let sub=(x,y)=>{
        return x-y;
    }
    
    return {
        add:add,
        sub:sub
    };
});

若是这个模块还须要依赖其余模块,那么define函数的第一个参数,必须是一个数组,指明该模块的依赖。

define([tools],function(){
    //…………………………
})

AMD模块的加载:

require([module], callback);

第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功以后的回调函数。例如加载math.js。

require([math],function(){
    //……………………
})

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

CMD(SeaJS)

玉伯提出的CMD规范,并开发了前端模块化开发框架SeaJS,不过在2015年后SeaJS中止了在github上维护,CMD与AMD用法很类似,可是我我的更喜欢使用SeaJS,虽然在2016年后也被我抛弃啦。

SeaJs使用:

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

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

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

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

});

有关于SeaJS与 RequireJS 的异同,能够参考:
https://github.com/seajs/seaj...
https://www.douban.com/note/2...

ES6 模块化

在es6 以前没有模块化的,为了解决问题,提出了commonJS,AMD,CMD,如今ES6模块化汲取了CommonJS 和 AMD 的优势,简洁的语法,异步加载
它彻底能够成为浏览器和服务器通用的模块化解决方案。

ES6中模块的定义

ES6 新增了两个关键字 export 和 import,export 用于把 模块里的内容 暴露 出来, import 用于引入模块提供的功能。

export命令输出变量:

//lib.js
let bar=function(){
    console.log('this is bar funciton');
};

let foo=function(){
    console.log('this is foo function');
};

export {bar,foo}

上面的代码还有另外一种写法:

export let bar=function(){
    console.log('this is bar funciton');
};

export let foo=function(){
    console.log('this is foo function');
};

export 不止能够导出函数,还能够导出对象,类,字符串等等

const test='aaa';
const obj={
    str:'hello!'
}
export {test,obj};

注:使用export在尾部输出变量时,必定要加大括号,

ES6中模块的加载

import 加载模块:

//加载 lib.js文件
 import {bar,foo,test,obj} from './lib'
 
 foo();//this is foo function

注:import 命令具备提高效果,会提高到整个模块的头部,首先执行

上面的是逐一指定要加载的方法,咱们还可使用 * 能够总体加载模块:

import * as lib from './lib'
lib.foo();

上面的加载模块的方式须要知道变量名和函数名,不然是没法加载的,咱们可使用export default 命令,为模块指定默认输出。

//lib.js
let foo=function(){
    console.log('this is foo');
} 
export default foo;

其余文件加载时,能够为该匿名函数指定任意名字。

import  lib from 'lib';

注:export default 命令适用于指定默认模块的输出,一个模块只能有一个默认输出,因此export default 只能使用一次。

ES6 模块运行机制

ES6模块是动态引用,若是使用import从一个模块加载变量(即import foo from 'foo'),变量不会被缓存,而是成为一个指向被加载模块的引用。等脚本执行时,根据只读引用,到被加载的那个模块中去取值。
举一个NodeJS模块化的例子:

//lib.js
export let counter = 3;
exoprt function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
//index.js
import {counter,incCounter} from './lib';
consoe.log(mod.counter);
mod.incCounter();
consoe.log(mod.counter);

输出:3
     4
调用 incCounter()方法后,lib 模块里的counter变量值改变了。

参考:
http://www.cnblogs.com/TomXu/...
http://blog.csdn.net/tyro_jav...
http://javascript.ruanyifeng....
http://www.ruanyifeng.com/blo...
https://zhuanlan.zhihu.com/p/...
https://segmentfault.com/a/11...
http://web.jobbole.com/83761/
http://es6.ruanyifeng.com/#do...
http://www.cnblogs.com/lishux...

相关文章
相关标签/搜索