export等等那些故事(二)ES6 Module

ES6 Module

CommonJS模块就是对象,输入的时候必须查找对象属性。

let { name, age, job } = require('./person')

等同于
const person = require('./person')
const name = person.name
const age = person.age
const job = person.job

上面就是整体加载person模块,生成一个对象person,然后再从这个对象上面读取三个方法。这种加载被称为运行时加载

ES6模块不是对象,而是通过export命令显示的指定输出的代码,再通过import命令输入。

严格模式

ES6的模块自动采用严格模式,即在模块头部加上"use strict"

  • 变量 在使用之前必须声明,不然会报错
"use strict"
a = 5;
alert(a); // referenceerror a is not defined
  • 函数的参数不能有同名属性,不然会报错
"use strict"
function test(a, a) {
  alert(a + a)
}
test(7, 7) // Duplicate parameter name not allowed in this context
  • 不能使用with语句
    with语句(为逐级的对象访问对象命名空间式的速写方式),也就是在指定的代码区域,直接通过节点的名称调用对象。
    width通常被当做重复隐痛同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身
var obj = {
    a: 1,
    b: 2,
    c: 3
};

// 如果要改变obj中每个属性的值,一般这样做
obj.a = 2;
obj.b = 3;
obj.c = 4;

//width语句这样做
with (obj) {
    a = 3;
    b = 4;
    c = 5;
}

每个变量首先被认为是一个局部变量,如果局部变量与Obj对象的某个属性同名,则这个局部变量会指向obj对象属性。
with的弊端

  1. 导致数据泄露
function change(obj) {
    with (obj) {
        name1 = "grow";
    }
}

var a1 = {
    name1: "jack"
};

var a2 = {
    name2: "jone"
}

change(a1);
console.log(a1); // { name1: "grow" }

change(a2);
console.log(a2); // { name1: "jone" }
console.log(name1); // grow

因为a2中的作用域,没有标识符name1,在非严格模式下,会自动在全局作用域创建一个全局变量,在严格模式下,会出现错误

  1. 性能下降
    with会在运行时候修改或者创建新的作用域。
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
export命令

模块的功能主要由两个命令构成export和import
export命令用于规定模块的对外接口
import命令用于输入其他模块提供的功能

  • 一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果想让外部能够读取到内部的某个变量,就必须使用export关键字输出该变量。
name.js
export var firstName = "Jone"
index.html

如果要导出多个变量,可以对export做优化

name.js
var firstName = 'Jone';
var lastName = 'ma';
var age = 17;

export { firstName,  lastName,  age };
index.html

  • 使用大括号指定要输出的一组变量。
  • export命令除了输出变量,还可以输出函数或者类(class)
  • export输出的变量就是本来的名字,但是可以使用as关键字重命名。
function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

以上代码使用了as关键字,重命名了函数v1和v2的对外接口,重命名后,v2可以用不同的名字输出两次。(因为v2被重命名了两次)。

  • export命令规定的是对外的接口,必须与模块内部的变量建立一一对应的关系。
export 1; // 报错

const m = 1
export m // 报错
  • 没有提供对外的接口。第一种写法直接输出1,第二种写法通过变量m, 还是直接输出1。1只是一个值,不是接口,
正确的写法
export var m = 1

const m = 7
export { m }

const m = 77
export { m as n }

其他的脚本可以通过这个接口,取到值(在接口名与模块内部的变量之间,建立了一一对应的关系)

  • functionclass的输出,也必须这样写
// 错误写法
function sayName(name) {
  return `hello ${name}`
}
export sayName // 报错  Unexpected token 'export'

// 正确写法
function sayName(name) {
  return `hello ${name}`
}
export { sayName }

// 正确写法2
export function sayName(name) {
  return `hello ${name}`
}
  • export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
export var name = "Jack"
setTimeout(() => name = 'Jone', 500);

// 也就是在调用的文件里面 1秒之后再次调用

import { name } from "./name.js";
    console.log(name)
    setTimeout(() => {
        console.log(name)
    }, 1000)

与CommonJS规范完全不同,CommonJS模块输出的是值的缓存,不存在动态更新。

import命令

使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块。

name.js

const firstName = "Jone"
const lastName = "mi"

export { firstName, lastName }
index.html

import { firstName, lastName } from "./name.js"; const nameDom = document.getElementById("test") function setName(element) { element.textContent = `${firstName} - ${lastName}` } setName(test) 结果:Jone mi 显示在dom
  • import命令接收一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块name.js对外接口的名称相同。
如果将name.js中的lastName重命名为last,但是在index.html中依旧引入lastName变量 则会报错。
简略代码解释:
name.js:

export { firstName, lastName as last }

index.html:

(错误)
import { firstName, lastName } from './name.js'
console.log(lastName)
 //Uncaught SyntaxError: The requested module './name.js' does not provide an export named 'lastName'

(正确)
import { firstName, last } from './name.js'

或者在import导入的时候使用as重命名
import { firstName, lastName as last } from './name.js'
  • import命令输入的变量都是只读的,因为他的本质是输入接口,也就是说,不允许在加载模块的脚本里面,改写接口。
import { a } from './xxx.js'

a = {}; // Syntax Error : 'a' is read-only;
  • 脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。但是如果a是一个对象,改写a的属性是允许的。
name.js

var firstName = {
  a: "Jone",
  b: "Jack"
}
export { firstName }
index.html

import { firstName } from './name.js'
firstName.a = "haha"
console.log(JSON.stringify(firstName)) // {"a":"haha","b":"Jack"}
  • 其他模块可以读到改写后的值,不过,这种写法很难查错,建议凡是输入的变量,都当做完全只读,不要轻易改变它的属性。
  • import后面的from指定模块文件的位置,可以使相对路径,也可以是绝对路径,.js后缀可以省略,如果只是模块名,不带有路径,那么必须有配置文件
  • import命令具有提升效果,会提升到整个 模块 的头部。(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';
}

用到了表达式、变量和if语句 在静态分析阶段,这些语法都是没法得到值的
  • import语句会执行所加载的模块。
import 'loadsh'

仅仅执行loadsh模块,不输入任何值。
import 'loadsh'
import 'loadsh'
// 如果重复执行同一句import语句,那么只会执行一次,而不会执行多次。
import { foo } from 'my_module';
import { bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module';
// import语句是 Singleton 模式。
  • 通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。
require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
import React from 'React';
模块的整体加载

使用 * 星号指定一个对象,所有输出的值都加载在这个对象上面。

circle.js

export function area(radius) {
    return Math.PI * radius * radius;
}

export function circumference(radius) {
    return 2 * Math.PI * radius;
}
index.html

import * as circle from "./circle.js";
console.log(circle.area(7)) // 153.93804002589985
console.log(circle.circumference(7)) // 43.982297150257104

模块整体所在的那个对象,应该是可以静态分析的。所以不允许运行时改变

import * as circle from './circle';

// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};
export default 命令
  1. 使用export default输出模块的时候,在被导入的模块可以使用任意名称指向export default输出的模块的方法,这时候不需要知道原模块输出的函数名。(import命令可以为该匿名函数指定任意名字)
// export-default.js
export default function () {
  console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
  1. 这个时候import命令后面,不使用大括号。
  2. export default命令用在非匿名函数前
// export-default.js
export default function foo() {
  console.log('foo');
}

// 或者写成

function foo() {
  console.log('foo');
}

export default foo;

foo函数的函数名是foo,在模块外部是无效的。在使用import命令加载的时候,视同匿名函数加载。

export 和 export default 导出方法的对比
对比1:
export function sayName() {
}
import { sayName} from './name.js'
对比2
export default function sayName() {
}
import sayName from './name.js'
  1. export default命令用于指定模块的默认输出,也就是一个模块只有一个默认输出,也就是export default命令只能使用一次
  2. export default就是输出一个叫做default的变量或者方法,然后允许为它取任意名字
// modules.js
function add(x, y) {
  return x * y;
}
export {add as default};
// 等同于
// export default add;

// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';
  1. 因为export default就是输出一个变量名为default的变量,所以它后面不能再跟变量了
// 正确
export var a = 1;

// 正确
var a = 1;
export default a;

// 错误
export default var a = 1;
  1. export default本质就是将值赋给default变量,可以直接将一个值写在export default后。
export default "jone" // 正确 赋值给default变量
export "Jone" // 错误 不能直接输出值
  1. 如果想在一条import语句中,同时输入默认方法和其他接口,可以写成下面这样。
import _, { each, forEach } from 'lodash';

被导入模块的输出方法

export default function (obj) {
  // ···
}

export function each(obj, iterator, context) {
  // ···
}

export { each as forEach };

你可能感兴趣的:(export等等那些故事(二)ES6 Module)