模块化
模块化的由来
JS主要在前端的浏览器中使用,js文件被下载缓存到客户端,在浏览器中执行。比如简单的表单本地验证,漂浮一个广告。
2005年之后,随着Google大量使用了AJAX技术之后,可以异步请求服务器端数据,带来了前端交互的巨大变化。前端功能需求越来越多,代码也越来也多。随着js文件的増多,众多文件通过<script>
标签引入到当前页面中,这些JS文件没有约束,定义的变量在这个页面中相互覆盖,造成全局变量污染,2008年V8引攣擎发布,2009年诞生了Nodes,支持服务端JS编程。使用JS编程的项目规模越来越大,这就和大多后端语言一样,需要有效的模块化管理这些文件。由此诞生许多的解决方案。
- CommonJS规范(2009年),使用全局 require函数导入模块,将所有对象约束在模块对象内部,使用exports导出指定的对象。最早这种规范是用在 Nodes后端的,后来又向前端开发移植,这样浏览器端开发也可以使用
- AMD(Asyη chronous Module Definition)异步模块定义,这是由社区提出的一种浏览器端模块化标
准。使用异步方式加载模块,模块的加载不影响它后面语句的执行。所有依赖这个模块的语句,都需要
定义在一个回调函数,回调函数中使用模块的变量和函数,等模块加载完成之后,这个回调函数才会执
行,就可以安全的使用模块的资源了。其实现就是 AMD/Require]s.AMD虽然是异步,但是会预先加载
和执行。目前应用较少。 - CMD( Common Module definition),使用seas,作者是淘宝前端玉伯,兼容并包解决了 Requirers的问题。CMD推崇 as lazy as possible,尽可能的懒加载JS文件。
由于社区的模块化呼声很高,ES6开始提供支持模块的语法,我们可以使用ES6中的语法export、import等语句进行模块的导入导出,但是目前运行环境包括V8引擎都不能很好的支持这种ES6新语法的模块导出方式,我们需要将采用ES6+新语法转换为ES5之前的语法才能运行。这就需要babel转译工具。
babel
将ES6+语法的JS文件转化为ES5语法规范的工具,以达到兼容运行环境的目的。
官网https://babeljs.io/
参考文档https://babel.docschina.org/docs/en/6.26.3/
这里使用6.x版本babel。
使用babel转译一个包
步骤
将我们的原js代码保存到一个src目录中(通常名为src)
1. 在src同级目录命令行执行 npm init 得到 package.json文件
2. 配置npm的镜像源:新建.npmrc文件写入 registry=https://registry.npm.taobao.org
3. 安装babel:命令行 npm install babel-cli --save-dev 得到 node_modules目录
4. 配置babel预设:创建 .babelrc 写入 {‘presets‘:[‘env‘]}
5. 安装配置的预设: npm install babel-preset-env --save-dev
6. 创建快捷命令: package.json文件中更改script为
script {
‘build‘:"babel -src -d lib"
}
7. 执行 npm run build 即可完成
各步骤细节
1.使用初始化命令 npm init
初始化这个目录后,该目录下将会创建一个package.json文件,并且在执行npm init 后将要求你输入对这个package.json的文件的初始配置,例如这个包名和版本号信息,git仓库,不输入则生成默认的内容。有了这个文件,npm可以通过这个package.json文件管理这整个包。在命令行中使用npm命令即可。
2.配置一个国内的镜像源,否则之后所有的包安装都会从国外官网中下载,包括babel包的下载安装,使用国内淘宝镜像将会提高速度。可以有三种
- 配置到项目目录:本项目目录下创建
.npmrc
文件写入,只在该项目生效 - 配置到该用户家目录,本用户生效
- 配置到npm安装目录,全局生效
3.下载并安装babel,执行npm install babel-cli --save-dev
,会将babel-cli以及依赖的所有包下载到本地根目录中
- --save 可以将这个安装的包极其版本号自动添加到package.json文件中去,作为本包的依赖包,
- -dev 是指定该包在开发环境中使用的依赖,通常需要带着这两个属性。
当我们使用save-dev选项后,在package.json文件中将会增加依赖包名,添加后在安装我们自己包时,会自动安装这个依赖的包;
"devDependencies": {
"babel-cli": "^6.26.0",
}
4.安装预设是为将来的转译设置转译规则,babel官方提供了以下转译引擎下载
babel-preset-env 当前环境支持的代码,新 target
babel-preset-es2015 ES2815转码规则
babel-preset-react react转码规则
ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个即可
babel-preset-stage-0
babel-preset-stage-1
babel-preset-stage-2
babel-preset-stage-3
通常安装babel-preset-env转码规则,执行npm install babel-preset-env --save-dev
即可,安装完成后,为babel工具创建babelrc配置文件,写入{‘presets‘:[‘env‘]}内容,转译时将会根据这个配置选择转码规则。也可以配置其他已下载的转码规则使用,详见官网
5.babel包安装完成并配置预设后,就可以使用命令./node_modules/.bin/babel src --out-dir lib
将我们src目录中源码执行转译写入lib文件中,lib文件就是转译的内容。
6.配置npm的快捷执行命令,在package.json文件中更改script, build为自定义的命令,后面为执行的真实命令,-d 是 --out-dir 的简写形式。
script {
‘build‘:"babel -src -d lib"
}
模块的导入导出
使用ES6标准中的导入导出语法在通常的环境和V8引擎的支持很不理想,通常不能正常执行,需要使用babel工具,将这些代码转化为旧版本的书写方式,才能执行。在ES6环境中的导入导出语法如下。
被导出的内容可以是任意的对象,包括变量,函数,类,对象均可。
被导出文件mod.js
export default function echo100 (){ // 函数作为默认导出,在导入文件中使用默认导出方式获得
console.log(100)
}
let a = 20
let b = ‘b‘
class Car{
constructor(name, price){
this.name = name
this.price = price
}
showcar(){console.log(`it‘s ${this.name}, need ¥${this.price}`)}
}
let newcar = new Car(‘polo‘, 130000)
export {newcar, a, b} // 普通导出
普通导出使用 export + {} 指定需要导出变量名即可,使用export default
语句,会将这个对象绑定到该模块对象的default属性上,且不用关心变量名,由导入方指定的变量来接收这个默认对象即可。
导入文件(test.js
),导入上面mod.js
文件中的内容
// import...from... 语法,指定导入文件变量名和路径即可
// 默认导入,Echo变量指向上面的默认导出 echo100对象
import Echo from ‘./mod.js‘
// 普通导入,变量名必须一致,可重命名,类似对象解构
import {a as numx, b as strb, newcar} from ‘./mod.js‘
// 导入整个模块,并给模块对象重命名,mod变量代表整个 mod.js模块
import * as mod from ‘./mod.js‘
// 测试导入的内容
console.log(numx)
console.log(strb)
newcar.showcar()
Echo()
上面是Es6的语法标准,直接运行目前的环境是不支持的,使用babel将上面的进行转译。转译后的文件内容为
转译后的mod.js
‘use strict‘;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
exports.default = echo100;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function echo100() {
console.log(100);
}
var a = 20;
var b = ‘b‘;
var Car = function () {
function Car(name, price) {
_classCallCheck(this, Car);
this.name = name;
this.price = price;
}
_createClass(Car, [{
key: ‘showcar‘,
value: function showcar() {
console.log(‘it\‘s ‘ + this.name + ‘, need \uFFE5‘ + this.price);
}
}]);
return Car;
}();
var newcar = new Car(‘polo‘, 130000);
exports.newcar = newcar;
exports.a = a;
exports.b = b;
转译后的test.js
‘use strict‘;
var _mod = require(‘./mod.js‘);
var mod = _interopRequireWildcard(_mod);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
console.log(_mod.a);
console.log(_mod.b);
_mod.newcar.showcar();
(0, mod.default)();
执行test.js文件,正常输出从mod中导入的内容。从mod包中导入的值不能对原值进行修改,是只读的,例如在 执行numx++
操作,数组等复杂类型元素的替换等,引起原值的修改将会在转译时报语法错误。