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
<script type="module">
import { firstName } from './name.js'
console.log(firstName); // Jone
</script>

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

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

export { firstName,  lastName,  age };
index.html
<script type="module">
import { firstName } from './name.js'
console.log(firstName); // Jone
</script>
  • 使用大括号指定要输出的一组变量。
  • 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

<div id="test"></div>

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 };
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355