JavaScript模块化相关知识

为什么需要模块化

为方便文件管理、增加复用,我们需要细化JS文件,每个文件负责单一职能,称之为模块,明确每个文件的职能,当交互功能较复杂时,引用的文件也越加庞大,此时我们就需要模块化管理。
模块化管理可以避免全局变量污染、函数命名冲突、文件依赖等问题

如何模块化

为了实现JavaScript的模块化开发,需要遵循一定的规范,目前通用的规范分为服务端(CommonJS)和客户端(AMD/CMD)。

CommonJS

CommonJS是针对JavaScript的服务端而言的,NodeJS采用的就是这个规范。根据CommonJS规范,一个单独的文件就是一个模块,每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他的模块读取,除非定义为global对象的属性。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。

//引入其他模块
var example = require('./example.js');
function exportFunc(){
    this.foo = function(){
        console.log('foo');
    }
}
//导出模块
exports.module1 = exportFunc();

NodeJS中使用require引入模块可以引入核心模块、自定义模块和路径形式的文件模块。其中核心模块(如http、fs、path等)是在NodeJS的源代码编译过程中已经编译为二进制代码,其加载过程最快。路径形式的文件模块是指以../标示的文件路径。在分析路径模块时,require()方法会将路径转换为真时路径,并以真实路径为索引,将编译执行后的结果存放到缓存中。由于文件模块给Node指明了确切的文件位置,所以在查找过程中可以节约大量时间,其加载时间鳗鱼核心模块。自定义模块指的是非核心模块,也不是路径形式的标识符。它是一种特殊的文件模块,可能是一个文件或者包的形式。这类模块的查找是最费时的,也是所有方式中最慢的一种。定位过程大概是先在当前文件目录下的node_modules目录,然后在找父目录下的node_modules目录,沿路径向上主机递归,知道根目录下的node_modules 目录
CommonJS规范加载模块是同步的,也就是说只有加载完成才能执行后面的操作。由于NodeJS主要用于服务器端编程,模块文件一般都已经存在与本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以CommonJS比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用AMD规范

AMD(异步模块定义)规范

AMD定义了一套JavaScript模块依赖异步加载标准。
AMD模块的定义

define(id? :String,dependencies?:String[],factory:Function | Object);

id是模块的名字,它是可选的参数
dependencies 指定了所要依赖的模块列表,它是一个数组,也是可选的参数,每个依赖的模块的输出将作为参数依次传入factory中。如果没有指定dependencies ,那么它的默认值就是 ['require','exports','module']
factory 是最后一个参数,它包裹了模块的具体实现,它是一个函数或者对象。如果是函数,那么它的返回值就是模块的输出接口或值

define('myModule',['jquery'],function($){
    $('body').text('hello world');
});
//使用
define(['myModule'],function(myModule){});

AMD模块的加载
AMD采用require()语句加载模块,它要求两个参数

require([module],callback);

第一个参数是数组,每个项都是你需要依赖的模块,这里需要注意,由于AMD是异步加载,每个模块的加载顺序是不固定的,但是执行顺序是固定的,按照依赖声明的先后顺序执行。
目前,require.js实现了AMD规范。

ES6中的模块化

ES6模块的设计思想是尽量静态化,使得在代码运行前就能确定模块的依赖冠以,以及输入输出的变阿玲。CommonJS和AMD模块都只能在运行时确定这些东西。
* 模块常用命令*
ES6的模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

import {es6} from './someModle';
function setName(ele){
    console.log(ele);
}
export setName;

另外,ES6还提供了module命令。该命令可以取代import语句,达到整体输入模块的作用

//circle.js
export function area(radius){
    return Math.PI * radius * radius;
}
export function circum(radius){
    return 2 * Math.PI *radius;
}

//main.js(写法一)
import { area,circum } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circum(14);

//main.js(写法二)
import * as circle from './circle';//as关键字可以重命名输出的模块
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circum(14);

//main.js(写法三)
module circle from './circle';//as关键字可以重命名输出的模块
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circum(14);

为了方便用户,使开发人员使用其他人编写的模块时不用阅读文档就都能加载模块,ES6提供了export default命令,为模块指定默认输出。但是一个模块只能由一个默认输出,export default命令只能使用一次。

//export-default.js
export default function(){
    console.log('foo');
}
//import-default.js
import customName from '.export-default';
customName();//'foo'

模块加载实质
ES6模块加载的机制与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。也就是说,通过CommonJS引入的模块,一旦输出一个值,模块内部的变化就影响不到这个值;而ES6遇到加载命令import时不回去执行模块,智慧生成一个动态的只读引用。等到真的需要用到时,再到模块中取值。

//Common.js实现
var counter = 3;
function incCounter(){
    counter++;
}
module.exports = {
    counter:counter,
    incCounter:incCounter,
};
//main1.js
import{counter,incCounter} from '.lib';
console.log(counter);//3
incCounter();
console.log(counter);//3


//ES6实现
//lib.js
export let counter = 3;
export function incCounter(){
    counter++;
}
//main1.js
import{counter,incCounter} from '.lib';
console.log(counter);//3
incCounter();
console.log(counter);//4
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值