ES6之模块入门(一)

  • A+
所属分类:Web前端
摘要

    ES6之前,一个Web应用的每个JS文件所定义的所有内容都由全局作用域共享。当Web应用变得越来越复杂,需要更多的JS代码时,此种方式会导致命名冲突、安全等很多问题。

    ES6之前,一个Web应用的每个JS文件所定义的所有内容都由全局作用域共享。当Web应用变得越来越复杂,需要更多的JS代码时,此种方式会导致命名冲突、安全等很多问题。

    如何解决?

    ES6的设计目标之一就是要解决作用域问题,并让JS应用变得更有调理。 这便是模块的切入点。

 

  1. 什么是模块(What are Modules)?

    Modules are JavaScript files that are loaded in a different mode (as opposed to scripts, which are loaded in the original way JavaScript worked).

    模块( Modules )是使用不同方式加载的 JS 文件(与 JS 原先的脚本加载方式相对)

    简单来说,可以认为一个模块就是一个js文件,该模块中有变量、函数、类。

    →模块(Modules)与脚本(Script)的语义有很大的不同:

      1、模块代码自动运行在严格模式下,并且没有任何办法跳出严格模式;

      2、在模块的顶级作用域创建的变量,不会自动添加到共享的全局作用域,它们只会在模块顶级作用域的内部存在

           3、模块顶级作用域的this值为undefined

           4、模块不允许在代码中使用HTML风格的注释;

           5、对于需要让模块外部访问的内容,模块必须导出它们;

           6、允许模块从其他模块导入绑定;

    我们这里定义一个模块文件(calc.js)放到modules目录下,内容如下:

    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 }

     

  2. 如何加载模块?
    ES6规范中定义了模块的语法以及抽象的加载机制,但没有定义如何加载他们,因此具体的实现环境(例如web浏览器、Node.js)可以自行决定用什么方式实现,以便契合各自的环境。

    →在Web浏览器中使用模块(使用script标签)

    方式一:

    <!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对象的属性。

    输出结果为:
    ES6之模块入门(一)

    方式二:

    <!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>

     

  3. 模块加载次序

    模块相对脚本的独特之处在于:它们能使用 import 来指定必须要加载的其他文件,以保证正确执行。为了支持此功能, <script type="module"> 总是表现得像是已经应用了 defer 属性。

    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之模块入门(一)

     

     

     

  4. 总结

    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