【前端学习】JS的模块化

参考文章
阮一峰的四篇文章:
Javascript模块化编程(一):模块的写法
Javascript模块化编程(二):AMD规范
Javascript模块化编程(三):require.js的用法
Module的语法
1、模块
模块就是实现特定功能的一组方法。一个类是一个模块,一个文件是一个模块。

2、基础模块写法
2.1原始写法
将函数和变量简单组合起来,就构成了一个模块

var a = 0 
var b = 1
function func1(){
	//...
}
function func2(){
	//...	
}

缺点:污染全局变量,模块间没有关系

2.2对象写法
把模块写成一个对象,避免变量名的污染

var module1 = new Object({
  _count : 0,
  m1 : function (){
    //...
  },
  m2 : function (){
    //...
  }
});

缺点:暴露内部成员,比如_count大家都可以去访问、修改

2.3立即执行函数
为了不暴露成员,可以写一个立即执行函数(函数内部作用域外界不能访问,也是闭包原理)

var module1 = (function(){
  var _count = 0;
  var m1 = function(){
    //...
  };
  var m2 = function(){
    //...
  };
  return {
    m1 : m1,
    m2 : m2
  };
})();

2.4放大模式
放大模式可以让模块被继承

var module1 = (function (mod){
  mod.m3 = function () {
    //...
  };
	return mod;
})(module1);

向这个小括号内声明的那个函数,传入一个module1对象,向其添加了一个名为m3的函数

2.5宽放大模式
在浏览器环境中,模块的各个部分通常都是从网上获取的,不知道加载顺序是怎样的,如果用放大模式则会加载出一个不存在的空对象。

var module1 = ( function (mod){
  //...
  return mod;
})(window.module1 || {});

与"放大模式"相比,"宽放大模式"就是"立即执行函数"的参数可以是空对象。

3、规范
3.1CommonJS(主要用于服务端,由sea.js产出)
CommonJS规范是为了让js能进行模块化编程,其实在浏览器端,有没有模块化都无所谓,但是node.js的出现,标志着js进军服务端编程,node.js的模块系统就是参考CommonJS实现的。
主要就是require方法。

var math = require('math');
math.add(2,3); // 5

这是一个引入模块math并使用的例子。
3.2AMD(主要用于客户端)
第二行math.add(2, 3),在第一行require(‘math’)之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。因为服务端的代码都存在硬盘上,客户端的代码必须请求得到,因此第二行代码可能很长时间不能执行,出现了”同步加载“的问题。
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"
函数及参数列表

require([module], callback);

module是模块名,callback是回调,加载完就执行。使用回调完成了异步,因此就不会出现浏览器假死。
示例:

require(['math'], function (math) {
  math.add(2, 3);
});

4、require.js(AMD规范)
4.1简介

<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>

上面的代码有很大的缺点:
首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长。
其次,必须严格保证加载顺序(比如上例的1.js要在2.js的前面),依赖性最大的模块一定要放到最后加载。
require.js的诞生,就是为了解决这两个问题。
(1)实现js文件的异步加载,避免网页失去响应;
(2)管理模块之间的依赖性,便于代码的编写和维护。

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

data-main属性的作用是,指定网页程序的主模块。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。
main有点像java的主函数,那个public static void main(String args[])
主模块可以光写js代码,但一般来讲是为了引入其他模块。

require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
  // some code here
});

假设要引入下面三个库

require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){
  // some code here
});

如果需要对模块进行配置(比如路径),就需要将require.config放在最上面,后面的路径可以是本地路径,也可以是网址

require.config({
  paths: {
    "jquery": "jquery.min",
    "underscore": "underscore.min",
    "backbone": "backbone.min"
  }
});

4.2AMD模块的写法
require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。

define(['myLib'], function(myLib){
  function foo(){
    myLib.doSomething();
  }
  return {
	   foo : foo
	};
});

当require函数加载这个模块的时候,就会优先加载myLib

5、ES6模块化
5.1概述
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找.

// CommonJS模块
let { stat, exists, readfile }= require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

上面代码的实质是整体加载 fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
由于 ES6 模块是编译时加载(静态加载),使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
除了静态加载带来的各种好处,ES6 模块还有以下好处。
不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。目前,通过各种工具库,其实已经做到了这一点。
将来浏览器的新 API 就能用模块格式提供,不再必须做成全局变量或者navigator对象的属性。
不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
5.1 import()
import命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行(import命令叫做“连接” binding 其实更合适)。
因此下面这段代码会报错(语法错误),import、export只能在代码的最顶部

// 报错
if (x === 2) {
  import MyModual from './myModual';
}

这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import命令要取代 Node 的require方法,这就形成了一个障碍。因为require是运行时加载模块,import命令无法取代require的动态加载功能。
es11提供了一个import()方法可以做到条件加载(动态加载)

import(specifier)

specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。import()返回一个 Promise 对象。
5.2 export
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

或者

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };

这两个都是输出了三个变量(当然也可以是类或者函数)
也可以重命名

function v1() { ... }
function v2() { ... }
export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

上面代码使用as关键字,重命名了函数v1和v2的对外接口。
典型的错误写法

// 报错
export 1;
// 报错
var m = 1;
export m;

上面两种写法都会报错,因为没有提供对外的接口。第一种写法直接输出 1,第二种写法通过变量m,还是直接输出 1。1只是一个值,不是接口。正确的写法是下面这样。
正确写法

// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};

函数方面

// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};

export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新。
export必须处于模块作用域的顶层(不可以放到块级作用域、函数作用域)。
5.3import命令
import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。

import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;

上面代码中,脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。但是,如果a是一个对象,改写a的属性是允许的。(但是没必要)

import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作

import命令具有提升效果,会提升到整个模块的头部,首先执行。
由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}

5.4整体输出

export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };

// 接口改名
export { foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';

export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值