发布于

ES6模块

Authors
  • avatar
    Name
    田中原
    Twitter

模块代码封装

概念:自动运行在==严格模式== 下并且没有办法退出运行的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模块是编译时输出接口