ES6以前,一个Web应用的每一个JS文件所定义的全部内容都由全局做用域共享。当Web应用变得愈来愈复杂,须要更多的JS代码时,此种方式会致使命名冲突、安全等不少问题。javascript
如何解决?html
ES6的设计目标之一就是要解决做用域问题,并让JS应用变得更有调理。 这即是模块的切入点。java
模块( Modules )是使用不一样方式加载的 JS 文件(与 JS 原先的脚本加载方式相对)es6
简单来讲,能够认为一个模块就是一个js文件,该模块中有变量、函数、类。
→模块(Modules)与脚本(Script)的语义有很大的不一样:
一、模块代码自动运行在严格模式下,而且没有任何办法跳出严格模式;
二、在模块的顶级做用域建立的变量,不会自动添加到共享的全局做用域,它们只会在模块顶级做用域的内部存在
三、模块顶级做用域的this值为undefined;
四、模块不容许在代码中使用HTML风格的注释;
五、对于须要让模块外部访问的内容,模块必须导出它们;
六、容许模块从其余模块导入绑定;
web
function add(a, b) { return a + b; } function sub(a, b) { return a - b; } function multi(a, b) { return a * b; } function divide(a, b) { return a / b; }
//先定义,后导出 export { add, sub, multi, divide }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <!--告诉浏览器 要将内联代码当作模块,这是一个直接嵌入到网页内的模块--> <script type="module"> import { add, sub, multi, divide } from './modules/calc.js'; let result = add(10, 20); console.log('result=' + result); </script> </head> <body> </body> </html>
在这里,result变量没有暴露到全局,由于它只在<script>元素定义的这个模块内部存在,所以也没有被添加为window对象的属性。浏览器
输出结果为:安全
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <!--告诉浏览器 要将指定文件中的代码当作模块,而不是当作脚本,这里使用src加载了外部模块文件--> <script type="module" src="./modules/calc.js"></script> </head> <body> </body> </html>
模块相对脚本的独特之处在于:它们能使用 import 来指定必需要加载的其余文件,以保证正确执行。为了支持此功能, <script type="module"> 老是表现得像是已经应用了 defer 属性。ide
defer 属性是加载脚本文件时的可选项,但在加载模块文件时老是自动应用的。当 HTML 解析到拥有 src 属性的 <script type="module"> 标签时,就会当即开始下载模块文件,但并不会执行它,直到整个网页文档所有解析完为止。
模块也会按照它们在 HTML 文件中出现的顺序依次执行,这意味着第一个 <script type="module"> 老是保证在第二个以前执行,即便其中有些模块不是用 src 指定而是包含了内联脚本。函数
例如:
第一步:新建一个模块foo.js在modules目录下,foo.js内容以下:测试
function hello(){ console.log('foo.hello...'); } console.log('foo.js模块执行了');
第二步:新建测试网页foo.html,内容以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <!--外部文件方式加载模块--> <!--first执行--> <script type="module" src="./modules/foo.js"></script> <!--second执行--> <script type="module"> console.log('second模块执行了...'); </script> </head> <body> <h1>Foo</h1> <script type="text/javascript"> //模拟加载数据耗时2秒 setTimeout(() => { console.log('网页模拟加载数据执行了...'); }, 2000); </script> <!--third执行--> <script type="module"> console.log('third模块执行了...'); </script> </body> </html>
观察执行结果:
ES6 为 JS 语言添加了模块,做为打包与封装功能的方式。
模块的行为异于脚本,它们不会用自身顶级做用域的变量、函数或类去修改全局做用域,而模块的 this 值为 undefined 。为了实现这些行为,模块在被加载时使用了一种不一样的方式。
你必须将模块中须要向外提供的任何功能都导出,变量、函数与类均可以,而且每一个模块容许存在一个默认导出。
在导出以后,另外一个模块就能导入该模块所导出的一个或多个名称了。这些导入的名称就像是被 let 所定义的,会被看成块级绑定,而且不允在同一模块内重复声明。
因为模块必须用与脚本不一样的方式运行,浏览器就引入了 <script type="module"> ,以表示资源文件或内联代码须要做为模块来执行。
使用 <script type="module"> 加载的模块文件会默认应用 defer 属性。一旦包含模块的页面文档彻底被解析,模块就会按照它们在文档中的出现顺序依次执行。
参考资料:《Understanding ECMAScript 6》,做者:Nicholas C. Zakas ,在线阅读地址:https://leanpub.com/understandinges6/read#leanpub-auto-what-are-modules