原生ECMAScript模块(ECMAScript modules)概述
来源:互联网 发布:java volatile 原子类 编辑:程序博客网 时间:2024/06/08 07:03
2016年浏览器和Node.js对于ECMAScript 2015 specification的应用取得了难以置信的发展。现在我们面临的状况是支持情况几乎都接近100%:
但是标准也同时介绍了ECMAScript modules (今天也经常被叫作ES或者ES6模块)。它仅仅是会(并且会继续)花最多的时间来实现这一标准,因为还没有浏览器在稳定版本中实现。 最近 Safari 10.1 (Safari Beta), Firefox 54 (Nighly) 以及Edge 15 (next EDGE version)或在之后的标志下 即将实现原生的开箱即用,因此我们很快就可以不需要模块打包来实现。 为了更好地了解我们如何来到这一点,让我们从JS模块历史开始,然后看看当前Native ES模块的特性和实现。
历史
历史上JavaScript没有提供模块系统。有很多技术,我主要说一下最典型的: 1) script
标签里面的长脚本 E.g.:
1
<!--html-->
2
<script type=
"application/javascript"
>
3
// module1 code
4
// module2 code
5
</script>
2) 使用script
标签逻辑地将文件进行划分:
1
/* js */
2
3
// module1.js
4
// module1 code
5
6
// module2.js
7
// module2 code
1
<!--html-->
2
<script type=
"application/javascript"
src=
"PATH/module1.js"
></script>
3
<script type=
"application/javascript"
src=
"PATH/module2.js"
></script>
3) 模块作为一个函数(一个模块就是一个返回自我调用函数或者JavaScript构造函数的函数)
- 应用程序文件/模型作为应用程序的入口点:
01
// polyfill-vendor.js
02
(
function
(){
03
// polyfills-vendor code
04
}());
05
06
// module1.js
07
function
module1(params){
08
// module1 code
09
return
module1;
10
}
11
12
// module3.js
13
function
module3(params){
14
this.a = params.a;
15
}
16
17
module3.prototype.getA =
function
(){
18
return
this.a;
19
};
20
21
// app.js
22
var
APP = {};
23
24
if
(isModule1Needed){
25
APP.module1 = module1({param1:1});
26
}
27
28
APP.module3 =
new
module3({a: 42});
1
<!--html-->
2
<script type=
"application/javascript"
src=
"PATH/polyfill-vendor.js"
></script>
3
<script type=
"application/javascript"
src=
"PATH/module1.js"
></script>
4
<script type=
"application/javascript"
src=
"PATH/module2.js"
></script>
5
<script type=
"application/javascript"
src=
"PATH/app.js"
></script>
之后社区发明了真正的分离技术来继续这种分离。主要的想法是提供一个系统,它将允许你只是包括一个链接到JS文件,如:html和其他一切都在bundler端。
让我们来看看主要的JavaScript模块技术标准:
异步模块定义(AMD)
这个方法与RequireJS库以及工具(如r.js)来构建结果打包。常用的语法是:
01
// polyfill-vendor.js
02
define(
function
() {
03
// polyfills-vendor code
04
});
05
06
// module1.js
07
define(
function
() {
08
// module1 code
09
return
module1;
10
});
11
12
// module2.js
13
define(
function
(params) {
14
var
a = params.a;
15
16
function
getA(){
17
return
a;
18
}
19
20
return
{
21
getA: getA
22
}
23
});
24
25
// app.js
26
define([
'PATH/polyfill-vendor'
] ,
function
() {
27
define([
'PATH/module1'
,
'PATH/module2'
] ,
function
(module1, module2) {
28
var
APP = {};
29
30
if
(isModule1Needed){
31
APP.module1 = module1({param1:1});
32
}
33
34
APP.module2 =
new
module2({a: 42});
35
});
36
});
CommonJS
它是Node.js生态系统中的基础JS打包。Browserify是使用它作为构建的主要工具之一。本标准的显著特点是为每个模块提供一个单独的作用域。 如下所示:
01
// polyfill-vendor.js
02
// polyfills-vendor code
03
04
// module1.js
05
// module1 code
06
module.exports= module1;
07
08
// module2.js
09
module.exports=
function
(params){
10
const
a = params.a;
11
12
return
{
13
getA:
function
(){
14
return
a;
15
}
16
};
17
};
18
19
// app.js
20
require
(
'PATH/polyfill-vendor'
);
21
22
const
module1 =
require
(
'PATH/module1'
);
23
const
module2 =
require
(
'PATH/module2'
);
24
25
const
APP = {};
26
27
if
(isModule1Needed){
28
APP.module1 = module1({param1:1});
29
}
30
31
APP.module2 =
new
module2({a: 42});
ECMAScript模块 (aka ES6/ES2015/Native JavaScript modules)
另外一个标准是 ES2015。它带来了社区需要的语法和功能。
- 单独的模块作用域
- 默认strict模式
- 循环依赖
- 你可以轻松地按照规范拆分代码 有一些加载器/编译器/方法支持系统中的一部分或者全部,例如:
Webpack
SystemJS
JSPM
Babel
UMD
工具
对于今天,我们习惯使用工具来打包JavaScript模块。如果我们谈论到ECMAScript,您可以使用以下方法之一:
Rollup
Traceur Compiler
Babel,特别的ES2015 modules to CommonJS transform 插件
Webpack 2 也带来了Tree Shaking (删除未使用的引入)
通常该工具提供CLI和或或者配置和打包JS文件的能力。它获取入口点并且打包您的文件,通常加入use strict
以及某些为了让代码在所有环境(老版本浏览器,Node.js等等)而对单吗进行转换。 让我们看看简化的Webpack配置,它设置了入口点并且使用Babel来处理JS文件:
01
// webpack.config.js
02
const
path =
require
(
'path'
);
03
04
module.exports = {
05
entry: path.resolve(
'src'
,
'webpack.entry.js'
),
06
output: {
07
path: path.resolve(
'build'
),
08
filename:
'main.js'
,
09
publicPath:
'/'
10
},
11
module: {
12
loaders: {
13
"test"
: /\.js?$/,
14
"exclude"
: /node_modules/,
15
"loader"
:
"babel"
16
}
17
}
18
};
配置的主要意思是: 1. 从webpack.entry.js
文件启动
2. 对于所有的JS文件应用Babel加载器(意味着代码将根据预置/插件+bundled来进行转化)
3. 将结果放入main.js
文件
在这种情况下,通常index.html包含以下内容:
1
<script src=
"build/main.js"
></script>
那么你的app使用的是打包以及转换后的JS代码。上述是使用打包的常见方法,接着让我们看看如何在浏览器中不使用打包来进行模块划分。
如何使JavaScript模块在浏览器中工作
浏览器支持
现如今,每个主流浏览器都在向支持ES6模块发展:
Firefox- 实现,在Firefox 54+中的标志下可用
Chrome- 正常工作
EDGE- 实现,在EDGE 15+标志下可用
Webkit- 实现,默认情况下在Safari 10.1中启用
Node.js- 正在审议中,需要进一步讨论
获取环境来测试
正如我们看到的,目前您可以在Safari 10.1+和EDGE 15 Preview Build中测试本机JS模块。让我们下载并启用其中的功能:
在Firefox中使用ES模块
目前你必须下载Firefox Nightly,这意味着它很快就会到来FF Developer Edition 并且会接着在稳定版本中实现。
启用ES模块:
- 打开
about:config
页面 - 点击“我接受风险!”
- 搜索
dom.moduleScripts.enabled
启用标志 - 双击并且将它的值转化为"true"
就这样,现在你可以测试模块脚本如何在FF中工作。
在启用ES模块的情况下获取Safari技术预览
如果您使用macOS,只需从developer.apple.com下载最新的Safari技术预览(TP)安装并打开它。
从Safari 技术预览版本21+开始,并且会默认启用ES模块。
如果是Safari TP 19或20,请检查ES6模块功能是否已启用,打开“开发”菜单 - >“实验功能” - >“ES6模块”。
另一个选项是下载最新的Webkit Nightly并使用它。
启用ES模块的EDGE 15
你可以从Microsoft下载免费的虚拟机。
只需选择虚拟机(VM)“Win 10预览版(15.XXXXX)上的Microsoft EDGE”以及例如 “虚拟盒”(也免费)作为平台。
安装并运行VM,并打开EDGE浏览器。
打开about:flags
页面,然后选中“启用实验性JavaScript功能”复选框
就这样,现在你有2个环境,你可以使用ECMAScript模块的本地实现
原生和捆绑模块的区别
让我们从本地模块功能开始:
1. 每个模块都有自己的作用域,而不是全局作用域 2. 它们总是处于严格模式,即使没有提供“use strict”指令 3. 模块可以使用import指令导入其他模块 4. 模块可以使用导出来导出绑定
到目前为止,我们没有面对与我们习惯于使用打包的巨大差异。
最大的区别在于为浏览器提供入口点的方式。您必须提供具有特定属性type =“module”
的script
标签,例如:
1
<script type=
"module"
scr=
"PATH/file.js"
></script>
这告诉浏览器你的脚本可能包含其他脚本的导入,他们应该被相应地处理。
这里出现的主要的问题是:
为什么JavaScript解释器无法自行检测文件是否是模块?
这里是其中一个原因
模块默认处于严格模式,经典的 scripts- no:
1. 让我们说解释器解析文件,假设它是一个不是严格模式的经典脚本
2. 然后找到“import \ export”指令
3. 在这种情况下,它应该从头开始在严格模式下再次解析所有代码
另一个原因 - 相同的文件可以没有严格模式的有效,并与它无效。然后有效性取决于它被解释的方式,这导致意想不到的问题。
定义期望的文件加载的类型为许多优化方法创造了条件。例如,在解析文件的其余部分之前并行加载文件导入)。你可以找到一些使用Microsoft Chakra JavaScript引擎对于ES模块的示例。
Node.js将文件标记为模块的方式
Node.js的性质不同于浏览器和使用``的解决方案也不适用。
目前,[合适的解决方案仍在讨论}(https://github.com/nodejs/node/wiki/ES6-Module-Detection-in-Node)。
有一些解决方案被社区否决了:
1. 在每一个文件添加"use module";
2. Meta在package.json中
其他选项仍在考虑中(感谢@bmeck的突出贡献):
1. 决定源代码是否是ES 模块
2. 在ES6模块中使用新的文件拓展名.mjs,如果之前的版本没有办法工作工作的话,这个选择可以作为备选。
1
每个方法都各有利弊,目前,[Node.js如何发展还是未知的](https:
//github.com/nodejs/node-eps/blob/master/002-es6-modules.md))。
简单原生模块例子
好的,首先让我们先创建一个简单的演示(您可以在您设置的环境中运行来进行测试。因此,它将是一个简单的模块,它会引入另外一个模块并且调用其中的一个方法。第一步-使用``来包括这个文件:
1
<!--index.html-->
2
3
4
5
<script type=
"module"
src=
"main.js"
></script>
6
7
8
模块文件
1
// main.js
2
import utils from
"./utils.js"
;
3
4
utils.alert(
5
JavaScript modules work in this browser:
6
https:
//blog.whatwg.org/js-modules
7
);
最后,引入的utils:
1
// utils.js
2
export
default
{
3
alert: (msg)=>{
4
alert(msg);
5
}
6
};
您可能会注意到,当使用import指令的时候,我们会提供.js
文件拓展名。这是和通常的打包行为的另外一个区别-原生模块不会默认添加.js
拓展名。
实际上,这意味着你必须提供确切的URL。主要的要求是资源应该有一个正确的MIME类型(感谢@bradleymeck纠正这个)
第二,让我们来检查模块的作用域(演示):
1
var
x = 1;
2
3
alert(x === window.x);
//false
4
alert(this === undefined);
// true
第三-我们会检查原生模块是在严格模式下。例如,严格模式禁止删除纯名称。因此下面的演示会显示出在模块脚本中抛出错误:
01
// module.js
02
var
x;
03
delete
x;
// !!! syntax error
04
05
alert(
06
THIS ALERT SHOULDN'T be executed,
07
the error is expected
08
as
the module's scripts are in the strict mode by
default
09
);
10
11
// classic.js
12
var
x;
13
delete
x;
// !!! syntax error
14
15
alert(
16
THIS ALERT SHOULD be executed,
17
as
you can
delete
variables outside of the strict mode
18
);
严格模式在模块脚本中是不可避免的。
注意
.js
拓展名不能被省略(应该提供确切的 URL)作用域不是全局的,
this
不指向任何变量原生模块默认处于严格模式下 (不再需要提供‘use strict’ )
内联模块脚本
和经典的脚本一样,你可以内敛代码,而不是在单独的文件中提供它。在之前的演示中,你可以将main.js
直接在放在 ``之中,这会导致相同的行为:
1
<script type=
"module"
>
2
import utils from
"./utils.js"
;
3
4
utils.alert(
5
JavaScript modules work in this browser:
6
https:
//blog.whatwg.org/js-modules
7
);
8
</script>
注意
- 以执行脚本或加载外部文件并作为模块使用执行它
浏览器如何加载并且执行模块
模块都是默认延迟加载的。为了理解这一点,你可以想象它们都具有一个默认的defer属性。
下面是规范中解释这一行为的图片:
这意味着,默认模块脚本不阻塞,并行加载,并在页面完成解析时执行。
因此您可以通过添加async
属性来改变这种行为,因此这个脚本只会在加载的时候执行。
默认行为和经典主要的区别是:经典脚本会被立即读取和计算,直到这两步完成才会开始解析。
为了表示它,下面是一个带有脚本选项的演示,在这第一个将是一个没有 defer
\ async
属性的经典脚本:
1
<script type=
"module"
src=
"./script1.js"
></script>
2
<script src=
"./script2.js"
></script>
3
<script defer=
""
src=
"./script3.js"
></script>
4
<script async=
""
src=
"./script4.js"
></script>
5
<script type=
"module"
async=
""
src=
"./script5.js"
></script>
其它顺序取决于浏览器的实现,脚本的大小,导入脚本的数量等。
写在最后:FOR Freedom 看看外边的世界,以及IT这一行,少不了去Google查资料,最后,安利一个国外网络加速器。一枝红杏加速器,去Google查资料是绝对首选,连接速度快,使用也方便。我买的是99¥一年的,通过这个链接(http://my.yizhihongxing.com/aff.php?aff=2509)注册后输上会员中心得优惠码,平摊下来,每月才7块钱,特实惠。
本文标签: JavaScript ECMAScript模块 ES6模块 CommonJS Safari技术服务器
转自 SUN'S BLOG - 专注互联网知识,分享互联网精神!
原文地址: 《原生ECMAScript模块(ECMAScript modules)概述》
相关阅读:《Aaron Swartz – 互联网天才开挂的人生历程:每时每刻都问自己,现在这世界有什么最重要的事是我能参与去做的?》
相关阅读:《网站环境apache + php + mysql 的XAMPP,如何实现一个服务器上配置多个网站?》相关阅读:《什么是工程师文化?各位工程师是为什么活的?作为一个IT或互联网公司为什么要工程师文化?》
相关阅读: 对程序员有用:2017最新能上Google的hosts文件下载及总结网友遇到的各种hosts问题解决方法及配置详解
相关阅读: 《win10永久激活教程以及如何查看windows系统是不是永久激活?》
相关BLOG:SUN’S BLOG - 专注互联网知识,分享互联网精神!去看看:www.whosmall.com
原文地址:http://whosmall.com/?post=237
- 原生ECMAScript模块(ECMAScript modules)概述
- ECMAScript
- ECMAScript
- ECMAScript
- ECMAScript
- ECMAScript
- ECMAScript
- ECMAScript
- ECMAScript
- ECMAScript
- ECMAScript
- ECMAScript 2017(ES8)特性概述
- ECMAScript 2017(ES8)特性概述
- ECMAScript 函数概述
- JavaScript/ECMAScript语言概述
- ECMAScript 6 学习系列课程 (ES6 Modules的使用)
- ECMAScript 6 模块简介
- UMD和ECMAScript模块
- jvm系列(八):jvm知识点总览-高级Java工程师面试必备
- 欢迎使用CSDN-markdown编辑器
- JAVA 学习笔记
- 值得我们深入研究和学习:从零开始一步一步搭建坚不可摧的Web系统主流架构
- Jqgrid Toolbar
- 原生ECMAScript模块(ECMAScript modules)概述
- nginx如何启用对HTTP2的支持 | nginx如何验证HTTP2是否已启用
- 网站是PHP程序写的,我为什么说要选linux系统的php虚拟主机?
- 中文翻译为"具象状态传输"的RESTful的架构风格和设计思想
- 堆排序:什么是堆?什么是最大堆?二叉堆是什么?堆排序算法是怎么样的?PHP如何实现堆排序?
- 作为合格的 Android 开发者必须了解的Gradle文件及其背后的原理
- 关于 TensorFlow 的 gentlest 的介绍:了解 TensorFlow(TF)进行多个特征的线性回归和逻辑回归
- 如何从Chrome源码看浏览器如何计算CSS?
- 归纳9种CSS样式自动生成在线工具的优缺点