- 发布于
ES6模块
- Authors
- Name
- 田中原
模块代码封装
概念:自动运行在==严格模式== 下并且没有办法退出运行的javascript代码
- requireJs以及commonJS、AMD两种规范 (es6之前)
- es6模块处理
一. requireJs以及commonJS、AMD两种规范
commonJS与AMD区别: 前者用于服务器端,后者用于浏览器端
var pathType = require('path') // 加载指定模块
pathType.add(2, 3)
requireJS优点:
- ==异步加载== (ps:传统异步加载方式)
- 管理模块之间的依赖,便于维护
- 按需、并行、延时
<!-- 非异步加载,需等到执行完脚本才会继续渲染,很墨迹 -->
<script src="js/a.js"></script>
<!-- 延迟加载 -->
<script src="js/b.js" defer></script>
<!--异步加载 -->
<script src="js/c.js" async></script>
<!-- defer与async的区别是:defer是“渲染完再执行”,async是“下载完就执行”。2.执行顺序问题-->
<!-- es6模块加载 也会自动执行defer属性-->
<script src="js/d.js" type="module"></script>
需要注意的地方:1.代码是在模块作用域之内运行,并不是全局作用域运行。模块的顶层变量,外部不可见2.严格模式3.模块顶层this指的是 undefined 例如:
import util from 'util/util.js'
const a = 1
console.log(window.a === a); false
console.log(this === undefined); true
delect a;// 严格模式语法错误,禁止删除变量
4.加载多次将只是执行一次
import { sum } from './example.js' //example.js将只是执行一次
import { mul } from './example.js'
即使使用requireJS也会存在网页失去响应的问题:sad:
解决办法::happy:
- 放在网页底部加载
- 异步加载
require.js在加载的时候会检查data-main属性。data-main属性的作用是,指定网页程序的主模块。由于requireJS默认的文件后缀名是js,所以可以把main.js简写成main。
;<script src="js/require.js" data-main="js/main"></script>
/* main.js */
require(['jquery', 'a', 'b'], function ($, _, Backbone) {
// some code here
})
require.js会先加载jQuery、a和b,然后再运行回调函数。主模块的代码就写在回调函数中。
二. es6模块导入导出
优点:编译时加载,便于处理静态分析
let { a, b, c } = require('fs') // 运行时加载,只有运行时才能得到这个对象
import { a, b, c } from 'fs' // 编译时加载
export(导出)与import(输入)
- ==必须放在其他语句和函数之外使用==
if(flag) {
export flag;
}
export:可以放在任何变量、函数或者类声明前面
export var a = 'red'
export function b(){}
export class c {
constructor(){}
}
const e = 'name'
let b = 'age'
export {e,b}
let f = 'sex'
export {f as ff} // 重命名
h() {}
export {h}
以上都是在接口名与模块的内部变量之间建立了一一对应的关系
var m = 'mm'
export m // 这样写怕是要挨喷了.
d(){} // 不导出就是模块默认私有的
另外,export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
export var bar = 'bar'
setTimeout(() => (bar = 'pie'), 500)
export命令只要处于模块顶层就可以,但是不能处于块级作用域内,那样会导致没法做静态优化了
function foo() {
export default 'bar' // SyntaxError
}
foo()
export default
- 本质上,export default就是输出一个叫做default的变量或方法,正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句,==导出是多次使用default关键字是一个错误==
// exportDefault.js
export default function (){}
// importDefault.js
import aa(可以随便起一个名字,并且不需要大括号了) from './exportDefault'
// 第一组
export default function () { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
// a.js
export let color = 'red'
export default function (num1, num2) {
return num1 + num2
}
//导入
import sum, { color } from './a.js' // 注意: 默认值必须排在非默认值之前
import
import 输入 from 指的是指定模块文件的位置,可以是相对路径也可以是绝对路径 .js后缀可以去掉
import {e,b} from './main.js' // 大括号里面的变量名,必须与被导出·模块(main.js)对外接口的名称相同。
import {e as ee} from './mainjs // 重命名
import 为变量、函数和类创建的是只读属性
// a.js
export let name = '张三'
export function setName(newName) {
name = newName
}
import { name, setName } from './a.js'
console.log(name) // 张三
setName('李四')
console.log(name) // 李四
name = '王五' // 不能直接赋值
模块的整体加载 使用(*)指定一个对象
// index.js
export function a(){}
export function b(){}
// main.js
import {a,b} from './index' // 逐个加载 a()
import * as all from './index // 整体加载,也称为命名空间导入 all.a()
:sad:大家在坚持一下哈:sad:
模块的继承
// circle.js
a() {}
export default a
export let b = ''
// circleplus.js
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
上面代码中的export *,表示再输出circle模块的所有属性和方法。注意,export *命令会忽略circle模块的default方法。然后,上面代码又输出了自定义的e变量和默认方法。
跨模块处理
// constants/db.js
export const db = {
url: 'http://my.couchdbserver.local:5984',
admin_username: 'admin',
admin_password: 'admin password',
}
// constants/user.js
export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator']
// constants/index.js
export { db } from './db'
export { users } from './users'
import con from './constants'
import { db, users } from './index'
ES6模块加载commonJS模块
// a.js
module.exports = {
foo: 'hello',
bar: 'world',
}
//等同于
export default {
foo: 'hello',
bar: 'world',
}
// 获取
import data from './a'
data = { foo: 'hello', bar: 'world' }
// b.js
module.exports = null
// es.js
import foo from './b'
// foo = null;
import * as bar from './b'
// bar = { default:null };
上面代码中,es.js
采用第二种写法时,要通过==bar.default==这样的写法,才能拿到
module.exports`。
// c.js
module.exports = function two() {
return 2
}
// es.js
import foo from './c'
foo() // 2
import * as bar from './c'
bar.default() // 2
bar() // throws, bar is not a function
上面代码中,bar
本身是一个对象,不能当作函数调用,只能通过bar.default
调用。
module.exports = 123
setTimeout((_) => (module.exports = null))
由于 ES6 模块是编译时确定输出接口,CommonJS 模块是运行时确定输出接口
因为fs
是 CommonJS 格式,只有在运行时才能确定readfile
接口,而import
命令要求编译时就确定这个接口
// 不正确
import { readfile } from 'fs';
// 整体输出
import fs from 'fs'
const app = export.default();
import fs from 'fs'
const app = fs();
commonJS模块的循环加载
// a.js
exports.done = false
var b = require('./b.js') // 停住 去执行脚本文件b
console.log('在 a.js 之中,b.done = %j', b.done) // true
exports.done = true
console.log('a.js 执行完毕')
// b.js
exports.done = false
var a = require('./a.js') // 因为a没有执行完成,所以只能拿到done = false(a文件里面的)
console.log('在 b.js 之中,a.done = %j', a.done) // false
exports.done = true
console.log('b.js 执行完毕')
ES6模块的循环加载
// a.mjs
import { bar } from './b'
console.log('a.mjs')
console.log(bar) // 运行b模块
export let foo = 'foo'
// b.mjs
import { foo } from './a' // 已知从a输出了foo
console.log('b.mjs')
console.log(foo) // 并没有被导出
export let bar = 'bar'
es6与commonJS差异
- commonJS模块输出的是一个值的拷贝,
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值
// b.js
var counter = {a:3};
function incCounter() {
counter.a++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./b');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3 之所以还是3,这是因为mod.counter是一个原始类型的值,会被缓存
es6
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// es6.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块
- commonJS模块是运行时加载,es6模块是编译时输出接口