模块化开发与规范化标准
一.模块化规范
1.1 Nodejs
中的**CommonJS
**规范:
1.一个文件就是一个模块
2.每个模块都有单独的作用域
3.通过
module.exports导出
成员4.通过
require函数载入
模块5.以
同步
模式加载模块
1.2 浏览器
中的**AMD
**规范(require.js)
1.用
define
定义模块2.用
require
加载模块AMD使用起来相对复杂,模块JS文件请求频繁
define('conor',['Module1', ‘Module2’], function (Module1, Module2) {
function foo () {
Module1.test();
}
return {foo: foo}
});
require(['conor'], function (math) {
math.add(2, 3);
});
1.3 浏览器
中的**CMD
**规范(淘宝推出的seaJs)
1.CMD和AMD是同期出现的,使用也类似. 不过
CMD语法上更贴近CommonJS
2.对于依赖的模块
AMD是提前执行
,CMD是延迟执行
。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。3.
AMD推崇依赖前置
(在定义模块的时候就要声明其依赖的模块),CMD推崇依赖就近(只有在用到某个模块的时候再去require——按需加载)。
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test();
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});
1.4 ES6
中的推出的**ESModule
**规范
目前最主流的前端模块化方案,也是接下来主要学习的
二.ESModule语法特性
基本特性:
1.ESM
默认是严格模式忽略'use strict',
严格模式下this指向undefined`2.ESM的每个模块都有
单独的私有作用域
3.ESM是通过
cors
的方式请求外部JS模块的4.ESM会
延迟执行
脚本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!-- 通过给script添加type="module",就可以用ESModule的标准执行其中的JS代码了 -->
<script type="module">
console.log('ESModule语法特性');
</script>
<!-- ESM默认是严格模式忽略'use strict',严格模式下this指向undefined -->
<script type="module">
var foo = '123'
console.log(foo);
console.log(this);
</script>
<!-- 非严格模式this指向window -->
<script>
console.log(this);
</script>
<!-- ESM的每个模块都有单独的私有作用域 -->
<script type="module">
console.log(foo);//报错
</script>
<!-- ESM是通过cors的方式请求外部JS模块的 -->
<script type="module" src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
<!-- 正常情况下的script代码会阻塞p标签的渲染,而ESM会延迟执行脚本 -->
<script>
alert('123')
</script>
<script type="module">
alert('123')
</script>
<p>需要显示的内容</p>
</body>
</html>
2.1 ES Modules导入导出
用export导出,import导入.共有以下几种方式
2.1.1 export 单个导出
//模块导出文件
export var name = 'foo module'
export function hello () {
console.log('hello')
}
export class Person {}
//模块导入文件
//import {name,hello,Person} from './module' //报错
//import {name,hello,Person} from 'module.js' //报错,以字母开头会被认为是第三方模块
//模块引入必须是完整的./开头的相对路径 or /开头的觉得路径 or 一个cdn链接
import {name,hello,Person} from './module.js'
console.log(name)
console.log(hello)
console.log(Person)
//或者
import * as mod from './module.js'
console.log(mod.name)
console.log(mod.hello)
console.log(mod.Person)
2.1.2 export 统一导出
//模块导出文件
var name = 'foo module'
function hello () {
console.log('hello Boy')
}
class Person {}
// as重命名,如果是default的话导入的时候就必须重命名
// 注意:这里导出的花括符不是对象的字面量,import的引入也不是对象的解构.而是ESM的一种固有的写法.
// 如果真的要导出一个对象的话可以用export default
export{name as default,hello as helloBoy,Person}
//模块导入文件
import {default as fooName,helloBoy,Person} from './module.js'
console.log(fooName)
console.log(helloBoy)
console.log(Person)
2.1.3 默认导出
// 模块导出文件
var name = 'foo module'
var obj = {name}
// 这样写的话,可以用任意名接收这个对象
export default obj
//模块导入文件
//注意:这里导入的nameobj不是被拷贝的值而是一个地址,它会随着导出模块中变量的变化而变化.且这个值无法修改.
import nameobj from './module.js'
console.log(nameobj.name)
2.1.4 只执行模块而不导入
//模块导入文件
import {} from './module.js'
//或者
import './module.js'
2.1.5 运行时按需使用模块
var modulePath = './module.js'
import {name} from modulePath; //报错
if(true){
import {name} from './module.js'; //报错
}
import ('./module.js').then((res)=>{
console.log()
})
2.1.6 默认导出和正常导出一起使用
//模块导出文件
var name = 'foo module'
function hello () {
console.log('hello Boy')
}
class Person {}
export{hello as helloBoy,Person}
export default name
//模块导入文件
import {helloBoy,Person,default as name} from './module.js'
//或者
import name,{helloBoy,Person} from './module.js'
console.log(name)
console.log(Person)
2.1.7 导出导入成员
//模块导入文件
export {helloBoy,Person,default as name} from './module.js'
//这里就访问不到helloBoy,Person,name
三. ES Modules兼容性
将浏览器中不识别的esmodule代码去交给babel转换,然后对那些import下来的文件去进行ajax请求把请求下来的文件再去进行转换从而支持esm .
但是这样子的话在那些支持的环境中就会运行两次代码. 这个问题可以通过添加
nomodule
属性解决 .这种方式效率很低下,只适合在开发环境中.
在生产环境中还是要预先把代码都编译好再部署
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
<script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
<script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script>
<script type="module">
import {foo} from './module.js';
console.log(foo);
</script>
</body>
</html>
四.在node中使用ES Modules
在node中如果要使用ESM,js文件名后缀需要用
.mjs
在node12.10.0版本中可以在package.json中写入一个
type:"module"
字段这样ESM的后缀可以正常的用.js但是CommonJS的话要用.cjs
后缀ESM中可以导入CommonJS模块类但
不能在commonjs中require载入ESM
CommonJS模块类始终导出一个
默认成员
,所以接收的时候不能用{}
node的内置模块兼容了ESM的提取成员方式
运行node --experimental-modules app.mjs
//node中使用ESM
import {foo,hello,Person} from './module.mjs';
console.log(foo);
import fs from 'fs';
fs.writeFileSync('./foo.txt','es module working!');
// 内置模块兼容了ESM的提取成员方式
import {writeFileSync} from 'fs';
writeFileSync('./bar.txt','es module working!');
import _ from 'lodash';
console.log(_.camelCase('SSssSS'))
//不支持,因为第三方模块都是导出默认成员
// import { camelCase } from 'lodash'
// console.log(camelCase('ES Modules'))
// ESM中可以导入CommonJS模块类
// import mod from './commonjs.js'
// console.log(mod)
//不能直接提取
// import {foo} from './commonjs.js'
// console.log(foo) //报错
export const foo = 'es module export value'
//node中使用ESM
// module.exports = {
// foo:'commonjs exports value'
// }
// 不能在node的commonjs中通过require载入ESM,但是在webpack中可以
// const mod = require('./index.mjs')
// console.log(mod)
//CommonJS始终导出一个默认成员,所以接收的时候不能用{}
exports.off="commonjs exports value"
//ESM中使用node
//加载模块函数:require
//模块对象:module
//导出对象别名:exports
//当前文件的绝对路径:__filename
//当前文件所在目录:__dirname
//ESM中没有CommonJS中的以上这些模块全局成员,前三种可以用import和export代替
//__filename和__dirname可以通过 import.meta.url
console.log(import.meta.url)
import {fileURLToPath} from 'url';
const __filename = fileURLToPath(import.meta.url);
console.log(__filename)
//__dirname
import {dirname} from 'path';
const __dirname = dirname(__filename);
console.log(__dirname)
五.在node中使用ES Modules兼容性问题
在node低版本中ESM同样需要转译,这里可以使用babel去处理.