ES6 模块简介
ES6以前 实现模块化用 RequireJS 或 seaJS(分别是基于AMD 规范的模块化库,和基于 CMD 规范的模块化库)。
ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,及输入和输出的变量。
ES6 的模块化分为导出(export) @与导入(import)两个模块。
ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;
可导入和导出各种类型的变量(如函数,对象,字符串,数字,布尔值,类等)
各模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
每个模块只加载一次(是单例的, 若再去加载同目录下同文件,直接从内存中读取。)
模块的原理
就是一个独立文件
文件内部的所有变量,外部无法获取,须用export输出。
export 与 import
• 导出的函数声明与类声明必须有名称(export default 除外)。
• 不仅能导出声明还能导出引用(例如函数)。
• export 可以出现在模块的任何位置,但必需处于模块顶层。
• import 会自动提升到模块头部,先执行。
/*-----export [test.js]-----*/
let myName = "Tom";
let myAge = 20;
let myfn = function(){
return "My name is" + myName + "! I'm '" + myAge + "years old." }
let myClass = class myClass {
static a = "yeah!";
}
export {
myName, myAge, myfn, myClass
}
/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());
// My name is Tom! I'm 20 years old. console.log(myAge);
// 20 console.log(myName);
// Tom console.log(myClass.a );
// yeah!
建议使用大括号指定所要输出的一组变量写在文档尾部,明确导出的接口。
函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。
export 导出变量
要外部读取模块内的变量,必用export。
例如:用export对外部输出三个变量:
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
建立对应关系
(提供对外的接口)
// 报错:只是一个值,不是接口
export 1;
// 报错
var m = 1;
export m;//此处并不是声明的变量,而是m的值
//正确
export var m = 1;
var m = 1;
export {m};
var n = 1;
export {n as m};
简写
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
export let
// path.js
export default { a:1 };
export let b = 2;
export let c =3;
输出动态值
即通过该接口,可以取到模块内部实时的值。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
上面代码输出变量foo,值为bar,500 毫秒之后变成baz。
与 CommonJS 规范完全不同。
CommonJS 模块输出的是值的缓存,不存在动态更新
export可出现在模块的任何位置,只要处于模块块级顶层就可以。
function foo() {
export default 'bar'
}
foo()
导出函数或类
export function multiply(x, y) {
return x * y;
};
导出格式
// 正确
export function f() {};
// 正确
function f() {}
export {f};
导出重命名
用as。
不同模块导出接口名命名重复,用 as 重新定义变量名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
v2可以用不同的名字输出两次
实例:
Tom 用 as 重新定义导出的接口名称,隐藏模块内部的变量
/*-----export [test.js]-----*/
let myName = "Tom";
export { myName as exportName }
/*-----import [xxx.js]-----*/
import { exportName } from "./test.js";
console.log(exportName);
导入重命名
/*-----export [test1.js]-----*/
let myName = "Tom";
export { myName }
/*-----export [test2.js]-----*/
let myName = "Jerry";
export { myName }
/*-----import [xxx.js]-----*/
import { myName as name1 } from "./test1.js";
import { myName as name2 } from "./test2.js";
console.log(name1);// Tom
console.log(name2);// Jerry
export default
导出默认对象
export default 仅有一个
export default 中的 default 是对应的导出接口变量。
export default 向外暴露的成员,可用任意变量接收。
var a = "My name is Tom!";
export default a;// 仅有一个
export default var c = "error";//default 已经是对应的导出变量,不能跟着变量声明语句
import b from "./xxx.js";// 不需要加{}, 使用任意变量接收
export default 和 export 区别
相同点:
l 均可导出常量、函数、文件、模块等
不同点:
l 一个模块中,export、import可以有多个,export default仅能有一个
l export导出的,在导入时要加{ },export default不用
实例(export)
导出:a.js
export const str = "blablabla~";
export function log(sth) {
return sth;
}
导入:b.js
import { str, log } from 'a'; //也可以分开写两次,导入的时候带花括号
实例(export default)
导出:a.js
const str = "blablabla~";
export default str;
导入:b.js
import str from 'a'; //导入的时候没有花括号
实例:
export default为模块指定默认输出,就不需要知道所要加载模块的变量名
//a.js
let sex = "boy";
export default sex(sex不能加大括号)
其实相当于为sex变量值"boy"起了一个系统默认的变量名default,
自然default只能有一个值,所以一个文件内不能有多个export default。
// b.js
本质上,a.js文件的export default输出一个叫做default的变量,然后系统允许你为它取任意名字。
所以可以为import的模块起任何变量名,且不需要用大括号包含
import any from "./a.js"
import any12 from "./a.js"
console.log(any,any12) // boy,boy
复合使用
export 与 import 可在同一模块使用:
可将导出接口改名,包括 default。
复合使用 export 与 import ,也可以导出全部,
当前模块导出的接口会覆盖继承导出的。
export { foo, bar } from "methods";
// 约等于下面两段语句,不过上面导入导出方式该模块没有导入 foo 与 bar
import { foo, bar } from "methods";
export { foo, bar };
/* ------- 特点 1 --------*/
// 普通改名
export { foo as bar } from "methods";
// 将 foo 转导成 default
export { foo as default } from "methods";
// 将 default 转导成 foo
export { default as foo } from "methods";
/* ------- 特点 2 --------*/
export * from "methods";
import
import...from...的from后面可跟很多路径格式,
若只给出包名(如:vue,axios),会自动到node_modules目录加载;
若给出相对路径及文件前缀,则到指定位置寻找。
可加载各种各样的文件:.js、.vue、.less等。
可省略掉from直接引入。
ES6的import...from...不需要指明文件后缀。
实例1
import Vue from 'vue';
其实最完整的写法是:
import Vue from "../node_modules/vue/dist/vue.js";
因为main.js是在src文件中,所以../向前一级相对目录查找node_modules,再依次寻找后面的文件。
实例2
import axios from 'axios';
完整意思是:
import axios from '..ode_modulesaxiosdistaxios.js';
和引入vue文件是一样的原理,都是从node_modules中加载相应名称的模块。
实例3
import App from './App';
完整写法:
import App from './App.vue';
意思是:
引入写好的.vue文件。(.vue文件是vue框架的单文件组件。)
实例4
import router from './route';
完整写法:
import router from './route.js';
意思是:
引入和main.js同级目录下的route.js文件。
实例5
import './less/index';
意思是:
import './less/index.less';
import 命令
用export命令定义了模块的对外接口以后,其他 JS 文件就可通过import加载这个模块。
// main.js
import {firstName, lastName, year} from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
上面代码的import命令,用于加载profile.js文件,并从中输入变量。
import接受一对大括号,里面指定要从其他模块导入的变量名。
大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
导入重命名
如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
import { lastName as surname } from './profile.js';
import输入的变量都是只读的,因为它的本质是输入接口(不允许在加载模块的脚本里面改写接口)。
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
上面代码中,脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。
如果a是对象,改写a的属性是允许的。
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
上面代码中,a的属性可以成功改写,并且其他模块也可以读到改写后的值。
不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,轻易不要改变它的属性。
import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。
如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
import {myMethod} from 'util';
上面代码中,util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。
import命令具有提升效果,会提升到整个模块的头部,首先执行。
foo();
import { foo } from 'my_module';
上面的代码不会报错,因为import的执行早于foo的调用。
这种行为的本质是,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 'lodash';//执行lodash模块,但是不输入任何值。
如果多次重复执行同一句import语句,那么只会执行一次。
import 'lodash';
import 'lodash';
上面代码加载了两次lodash,但是只会执行一次。
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
上面代码中,虽然foo和bar在两个语句中加载,但是它们对应的是同一个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';
感谢收看本期Q程序员说,最后别忘点赞加关注哈!我接着继续整,有啥不爽留言。