ES6
经常使用但被忽略的方法 系列文章,整理做者认为一些平常开发可能会用到的一些方法、使用技巧和一些应用场景,细节深刻请查看相关内容链接,欢迎补充交流。CommonJS
和AMD
模块,都只能在运行时肯定这些东西。 ES6
能够在编译时就完成模块加载,效率要比 CommonJS
模块的加载方式高,这种加载称为“编译时加载”或者静态加载。JavaScript
的语法,好比引入宏(macro
)和类型检验(type system
)这些只能靠静态分析实现的功能。UMD
模块格式。API
就能用模块格式提供,再也不必须作成全局变量或者navigator
对象的属性。Math
对象),将来这些功能能够经过模块提供。ES6
的模块自动采用严格模式,无论你有没有在模块头部加上"use strict"
。with
语句0
表示八进制数,不然报错delete prop
,会报错,只能删除属性delete global[prop]
eval
不会在它的外层做用域引入变量eval
和arguments
不能被从新赋值arguments
不会自动反映函数参数的变化arguments.callee
和arguments.caller
this
指向全局对象fn.caller
和fn.arguments
获取函数调用的堆栈protected
、static
和interface
)ES6
模块之中,顶层的this
指向undefined
,即不该该在顶层代码使用this
。export
命令用于规定模块的对外接口。export
关键字输出该变量。除了输出变量,还能够输出函数或类(class
)。// index.js
export const name = 'detanx';
export const year = 1995;
export function multiply(x, y) {
return x * y;
};
// 写法二
const name = 'detanx';
const year = 1995;
function multiply(x, y) {
return x * y;
};
export { name, year, multiply }
复制代码
export
输出的变量就是原本的名字,可是可使用as
关键字重命名。重命名后,能够用不一样的名字输出屡次。function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
复制代码
export
命令规定的是对外的接口,必须与模块内部的变量创建一一对应关系。// 报错
export 1;
var m = 1;
export m;
// 正确
export var m = 1;
var m = 1;
export {m};
var n = 1;
export {n as m};
复制代码
export
命令能够出如今模块的任何位置,只要处于模块顶层就能够。export *
命令会忽略模块的default
方法。// 总体输出
export * from 'my_module';
复制代码
export
命令定义了模块的对外接口之后,其余 JS
文件就能够经过import
命令加载这个模块。想为输入的变量从新取一个名字,import
命令要使用as
关键字,将输入的变量重命名。import { name, year } from './index.js';
import { name as username } from './profile.js';
复制代码
import
命令输入的变量都是只读的,由于它的本质是输入接口。 也就是说,不容许在加载模块的脚本里面,改写接口。若是a
是一个对象,改写a
的属性是容许的。import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
a.foo = 'hello'; // 合法操做
复制代码
import
后面的from
指定模块文件的位置,能够是相对路径,也能够是绝对路径,.js
后缀能够省略。若是只是模块名,不带有路径,那么必须有配置文件(例如使用webpack
配置路径),告诉 JavaScript
引擎该模块的位置。import {myMethod} from 'util';
复制代码
import
命令具备提高效果,会提高到整个模块的头部,首先执行。foo(); // 不会报错
import { foo } from 'my_module';
复制代码
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';
}
复制代码
import
语句,那么只会执行一次,而不会执行屡次。import 'lodash';
import 'lodash'; // 只会执行一次
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
复制代码
*
)指定一个对象,全部输出值都加载在这个对象上面。import * as user from './index.js';
user.name; // 'detanx'
user.year; // 1995
复制代码
export default
命令,为模块指定默认输出。其余模块加载该模块时,import
命令(import
命令后面,不使用大括号)能够为该匿名函数指定任意名字。// export-default.js
export default function () {
console.log('detanx');
}
// import-default.js
import customName from './export-default';
customName(); // 'detanx'
复制代码
export default
时,对应的import
语句不须要使用大括号;使用export
,对应的import
语句须要使用大括号。 一个模块只能有一个默认输出,所以export default
命令只能使用一次。export default function crc32() { ...}
import crc32 from 'crc32';
export function crc32() { ... };
import { crc32 } from 'crc32';
复制代码
import
语句能够与export
语句写在一块儿。写成一行之后,foo
和bar
实际上并无被导入当前模块,只是至关于对外转发了这两个接口,致使当前模块不能直接使用foo
和bar
。export { foo, bar } from 'my_module';
// 能够简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
复制代码
// 接口更名
export { foo as myFoo } from 'my_module';
// 总体输出
export * from 'my_module';
复制代码
export { default } from 'foo';
复制代码
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
复制代码
export { default as es6 } from './someModule';
ES2020 以前,有一种import语句,没有对应的复合写法。
import * as someIdentifier from "someModule";
复制代码
ES2020
补上了这个写法。export * as ns from "mod";
// 等同于
import * as ns from "mod";
export {ns};
复制代码
constant
的文件,咱们须要什么就加载什么。// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
// use.js
import {A, B} from './constants';
复制代码
import
命令会被 JavaScript
引擎静态分析,先于模块内的其余语句执行(import
命令叫作“链接” binding
其实更合适)。因此咱们只能在最顶层去使用。ES2020
引入import()
函数,支持动态加载模块。import()
返回一个 Promise
对象。const main = document.querySelector('main');
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
复制代码
import()
函数能够用在任何地方,不只仅是模块,非模块的脚本也可使用。它是运行时执行,也就是说,何时运行到这一句,就会加载指定的模块。另外,import()
函数与所加载的模块没有静态链接关系,这点也是与import语句不相同。import()
相似于 Node
的require
方法,区别主要是前者是异步加载,后者是同步加载。import()
加载模块成功之后,这个模块会做为一个对象,看成then
方法的参数。所以,可使用对象解构赋值的语法,获取输出接口。import('./myModule.js')
.then(({export1, export2}) => {
// ...·
});
复制代码
export1
和export2
都是myModule.js
的输出接口,能够解构得到。default
输出接口,能够用参数直接得到。import('./myModule.js')
.then(myModule => {
console.log(myModule.default);
});
复制代码
import('./myModule.js')
.then(({default: theDefault}) => {
console.log(theDefault);
});
复制代码
Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});
复制代码
import()
也能够用在 async
函数之中。async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}
main();
复制代码
JavaScript
脚本,即渲染引擎遇到<script>
标签就会停下来,等到执行完脚本,再继续向下渲染。为了解决<script>
标签打开defer
或async
属性,脚本就会异步加载。defer
与async
的区别是:defer
要等到整个页面在内存中正常渲染结束(DOM
结构彻底生成,以及其余脚本执行完成),才会执行;async
一旦下载完,渲染引擎就会中断渲染,执行这个脚本之后,再继续渲染。一句话,defer
是“渲染完再执行”,async
是“下载完就执行”。另外,若是有多个defer
脚本,会按照它们在页面出现的顺序加载,而多个async
脚本是不能保证加载顺序的。ES6
模块,也使用<script>
标签,可是要加入type="module"
属性。等同于打开了<script>
标签的defer
属性。<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>
复制代码
"use strict"
。import
命令加载其余模块(.js
后缀不可省略,须要提供绝对 URL
或相对 URL
),也可使用export
命令输出对外接口。this
关键字返回undefined
,而不是指向window
。也就是说,在模块顶层使用this
关键字,是无心义的。import utils from 'https://example.com/js/utils.js';
const x = 1;
console.log(x === window.x); //false
console.log(this === undefined); // true
复制代码
this
等于undefined
这个语法点,能够侦测当前代码是否在 ES6
模块之中。const isNotModuleScript = this !== undefined;
复制代码
Node.js
加载 ES6
模块以前,必须了解 ES6
模块与 CommonJS
模块彻底不一样。CommonJS
模块输出的是一个值的拷贝,ES6
模块输出的是值的引用。CommonJS
模块是运行时加载,ES6
模块是编译时输出接口。(由于 CommonJS
加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。)CommonJS
模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。除非写成一个函数,才能获得内部变更后的值。// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
// 写成函数
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
$ node main.js
3
4
复制代码
ES6
模块是动态引用,而且不会缓存值,模块里面的变量绑定其所在的模块。// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
复制代码
ES6
输入的模块变量,只是一个“符号链接”,因此这个变量是只读的,对它进行从新赋值会报错。// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError
复制代码
export
经过接口,输出的是同一个值。不一样的脚本加载这个接口,获得的都是一样的实例。// mod.js
function C() {
this.sum = 0;
this.add = function () {
this.sum += 1;
};
this.show = function () {
console.log(this.sum);
};
}
export let c = new C();
复制代码
mod.js
,输出的是一个C
的实例。不一样的脚本加载这个模块,获得的都是同一个实例。// x.js
import {c} from './mod';
c.add();
// y.js
import {c} from './mod';
c.show();
// main.js
import './x';
import './y';
复制代码
main.js
,输出的是 1
。$ babel-node main.js
1
复制代码
x.js
和y.js
加载的都是C
的同一个实例。Node.js
要求 ES6
模块采用.mjs
后缀文件名。Node.js
遇到.mjs
文件,就认为它是 ES6
模块,默认启用严格模式,没必要在每一个模块文件顶部指定"use strict"
。 若是不但愿将后缀名改为.mjs
,能够在项目的package.json
文件中,指定type
字段为module
。{
"type": "module"
}
复制代码
这时还要使用 CommonJS
模块,那么须要将 CommonJS
脚本的后缀名都改为.cjs
。若是没有type
字段,或者type
字段为commonjs
,则.js
脚本会被解释成 CommonJS
模块。node
总结:.mjs
文件老是以 ES6
模块加载,.cjs
文件老是以 CommonJS
模块加载,.js
文件的加载取决于package.json
里面type
字段的设置。webpack
注意,ES6
模块与 CommonJS
模块尽可能不要混用。require
命令不能加载.mjs
文件,会报错,只有import
命令才能够加载.mjs
文件。反过来,.mjs
文件里面也不能使用require
命令,必须使用import。
es6
Node.js 加载 主要是介绍ES6
模块和 CommonJS
相互之间的支持,有兴趣的能够本身去看看。web
circular dependency
)指的是,a
脚本的执行依赖b
脚本,而b
脚本的执行又依赖a
脚本。“循环加载”表示存在强耦合,若是处理很差,还可能致使递归加载,使得程序没法执行,所以应该避免出现,但很难避免尤为是特别复杂的项目。