es6学习

ES6 最大的一个改进就是引入了模块规范。这个规范全方位简化了之前 出现的模块加载器,原生浏览器支持意味着加载器及其他预处理都不再 必要。从很多方面看,ES6 模块系统是集 AMD CommonJS 之大成者。

模块标签及定义

ECMAScript 6 模块是作为一整块 JavaScript 代码而存在的。带 有type="module" 属性的 <script> 标签会告诉浏览器相关代码应该作为模 块执行,而不是作为传统的脚本执行。模块可以嵌入在网页中,也可以 作为外部文件引入:
<script type="module"> 
// 模块代码 
</script>
 <script type="module" src="path/to/myModule.js">
</script>
即使与常规 JavaScript 文件处理方式不同, JavaScript 模块文件也没有专 门的内容类型。
        与传统脚本不同,所有模块都会像<script defer> 加载的脚本一样按顺 序执行。解析到<script type="module"> 标签后会立即下载模块文件, 但执行会延迟到文档解析完成。无论对嵌入的模块代码,还是引入的外 部模块文件,都是这样。<script type="module"> 在页面中出现的顺序 就是它们执行的顺序。与<script defer> 一样,修改模块标签的位置, 无论是在<head> 还是在 <body> 中,只会影响文件什么时候加载,而不会 影响模块什么时候加载。
下面演示了嵌入模块代码的执行顺序:
<!-- 第二个执行 -->
 <script type="module"></script>
 <!-- 第三个执行 -->
 <script type="module">
</script>
 <!-- 第一个执行 -->
 <script></script>
另外,可以改为加载外部 JS 模块定义:
<!-- 第二个执行 --> 
<script type="module" src="module.js"></script> 
<!-- 第三个执行 -->
<script type="module" src="module.js"></script>
<!-- 第一个执行 --> 
<script><script>
        也可以给模块标签添加async 属性。这样影响就是双重的:不仅模块执 行顺序不再与<script> 标签在页面中的顺序绑定,模块也不会等待文档 完成解析才执行。不过,入口模块仍必须等待其依赖加载完成。
        与<script type="module"> 标签关联的 ES6 模块被认为是模块图中的入 口模块。一个页面上有多少个入口模块没有限制,重复加载同一个模块 也没有限制。同一个模块无论在一个页面中被加载多少次,也不管它是 如何加载的,实际上都只会加载一次,如下面的代码所示:
<!-- moduleA在这个页面上只会被加载一次 --> 
<script type="module">
 import './moduleA.js' 
<script> 
<script type="module"> 
    import './moduleA.js' 
<script> 
<script type="module" src="./moduleA.js"></script> 
<script type="module" src="./moduleA.js"></script>
        嵌入的模块定义代码不能使用import 加载到其他模块。只有通过外部文 件加载的模块才可以使用import 加载。因此,嵌入模块只适合作为入口 模块。

模块加载

        ECMAScript 6模块的独特之处在于,既可以通过浏览器原生加载,也可 以与第三方加载器和构建工具一起加载。有些浏览器还没有原生支持 ES6模块,因此可能还需要第三方工具。事上,很多时候使用第三方 工具可能会更方便。
        完全支持ECMAScript 6 模块的浏览器可以从顶级模块加载整个依赖图, 且是异步完成的。浏览器会解析入口模块,确定依赖,并发送对依赖模块的请求。这些文件通过网络返回后,浏览器就会解析它们的内容,确 定它们的依赖,如果这些二级依赖还没有加载,则会发送更多请求。这 个异步递归加载过程会持续到整个应用程序的依赖图都解析完成。解析 完依赖图,应用程序就可以正式加载模块了。
        这个过程与AMD 风格的模块加载非常相似。模块文件按需加载,且后 续模块的请求会因为每个依赖模块的网络延迟而同步延迟。即,如果 moduleA依赖 moduleB moduleB 依赖 moduleC 。浏览器在对 moduleB 的请求 完成之前并不知道要请求moduleC 。这种加载方式效率很高,也不需要 外部工具,但加载大型应用程序的深度依赖图可能要花费很长时间。

模块行为

ECMAScript 6 模块借用了 CommonJS AMD 的很多优秀特性。下面简单
列举一些。
        
模块代码只在加载后执行。
模块只能加载一次。
模块是单例。
模块可以定义公共接口,其他模块可以基于这个公共接口观察和交
互。
模块可以请求加载其他模块。
支持循环依赖。

ES6模块系统也增加了一些新行为。

                ES6模块默认在严格模式下执行。
                ES6模块不共享全局命名空间。
                模块顶级this的值是undefined(常规脚本中是window)。
                模块中的var声明不会添加到window对象。
                ES6模块是异步加载和执行的。
        浏览器运行时在知道应该把某个文件当成模块时,会有条件地按照上述 ECMAScript 6模块行为来施加限制。与<script type="module"> 关联或 者通过import 语句加载的 JavaScript 文件会被认定为模块。

模块导出

        ES6模块的公共导出系统与 CommonJS 非常相似。控制模块的哪些部分 对外部可见的是export 关键字。 ES6 模块支持两种导出:命名导出和默 认导出。不同的导出方式对应不同的导入方式,下一节会介绍导入。
        export关键字用于声明一个值为命名导出。导出语句必须在模块顶级, 不能嵌套在某个块中:
// 允许 
export ... 
// 不允许 
if (condition) { export ... }
        导出值对模块内部JavaScript 的执行没有直接影响,因此 export 语句与导 出值的相对位置或者export 关键字在模块中出现的顺序没有限 制。
        export语句甚至可以出现在它要导出的值之前:
// 允许 
const foo = 'foo'; export { foo }; 
// 允许 
export const foo = 'foo'; 
// 允许,但应该避免 
export { foo }; const foo = 'foo';
        命名导出(named export)就好像模块是被导出值的容器。行内命名导 出,顾名思义,可以在同一行执行变量声明。下面展示了一个声明变量 同时又导出变量的例子。外部模块可以导入这个模块,而foo将成为这 个导入模块的一个属性:
export const foo = 'foo';
        变量声明跟导出可以不在一行。可以在export 子句中执行声明并将标识 符导出到模块的其他地方:
const foo = 'foo'; 
export { foo };
        导出时也可以提供别名,别名必须在export 子句的大括号语法中指定。 因此,声明值、导出值和为导出值提供别名(as)不能在一行完成。
        在下面的 例子中,导入这个模块的外部模块可以使用myFoo访问导出的值:
const foo = 'foo'; 
export { foo as myFoo };
        因为ES6 命名导出可以将模块作为容器,所以可以在一个模块中声明多 个命名导出。导出的值可以在导出语句中声明,也可以在导出之前声明:
export const foo = 'foo'; 
export const bar = 'bar'; 
export const baz = 'baz';
        考虑到导出多个值是常见的操作,ES6 模块也支持对导出声明分组,可 以同时为部分或全部导出值指定别名:
const foo = 'foo'; 
const bar = 'bar'; 
const baz = 'baz'; 
export { foo, bar as myBar, baz };
        默认导出(default export)就好像模块与被导出的值是一回事。默认导 出使用default关键字将一个值声明为默认导出,每个模块只能有一个 默认导出。
        重复的默认导出会导致SyntaxError。 下面的例子定义了一个默认导出,外部模块可以导入这个模块,而这个 模块本身就是foo 的值:
const foo = 'foo'; 
export default foo;
        另外,ES6 模块系统会识别作为别名提供的 default 关键字。此时,虽然 对应的值是使用命名语法导出的,实际上则会成为默认导出:
const foo = 'foo'; 
// 等同于
export default foo; 
export { foo as default };
        因为命名导出默认导出不会冲突,所以ES6 支持在一个模块中同时定 义这两种导出:
const foo = 'foo'; 
const bar = 'bar'; 
export { bar }; 
export default foo;
        这两个export 语句可以组合为一行:
const foo = 'foo'; 
const bar = 'bar'; 
export { foo as default, bar };
        ES6规范对不同形式的 export 语句中可以使用什么不可以使用什么规定 了限制。某些形式允许声明和赋值,某些形式只允许表达式,而某些形 式则只允许简单标识符。注意,有的形式使用了分号,有的则没有:
// 命名行内导出 
export const baz = 'baz'; 
export const foo = 'foo', bar = 'bar'; 
export function foo() {} 
export function* foo() {} 
export class Foo {} 
// 命名子句导出 
export { foo }; 
export { foo, bar }; 
export { foo as myFoo, bar }; 
// 默认导出 
export default 'foo'; 
export default 123; 
export default /[a-z]*/; 
export default { foo: 'foo' }; 
export { foo, bar as default }; 
export default foo export default function() {} 
export default function foo() {} 
export default function*() {} 
export default class {} 
// 会导致错误的不同形式: 
// 行内默认导出中不能出现变量声明 
export default const foo = 'bar'; 
// 只有标识符可以出现在export子句中 
export { 123 as foo } 
// 别名只能在export子句中出现 
export const foo = 'foo' as myFoo;
注意:什么可以或不可以与exprot关键字出现在同一行可能很难记住。一般来说,声明、赋值和导出标识符最好分开。这样就不容易 搞错了,同时也可以让export语句集中在一块。

模块导入

        模块可以通过使用import 关键字使用其他模块导出的值。与 export 类 似,import 必须出现在模块的顶级:
// 允许 import ... 
// 不允许 
if (condition) {
 import ... 
}
        import语句被提升到模块顶部。因此,与 export 关键字类似, import 语句与使用导入值的语句的相对位置并不重要。不过,还是推荐把导入语句放在模块顶部。
// 允许 
import { foo } from './fooModule.js'; 
console.log(foo); // 'foo' 

// 允许,但应该避免 
console.log(foo); // 'foo' 
import { foo } from './fooModule.js';
        模块标识符可以是相对于当前模块的相对路径,也可以是指向模块文件 的绝对路径。它必须是纯字符串,不能是动态计算的结果。例如,不能 是拼接的字符串。
        如果在浏览器中通过标识符原生加载模块,则文件必须带有.js 扩展名, 不然可能无法正确解析。不过,如果是通过构建工具或第三方模块加载 器打包或解析的ES6 模块,则可能不需要包含文件扩展名。
// 解析为/components/bar.js 
import ... from './bar.js'; 
// 解析为/bar.js 
import ... from '../bar.js'; 
// 解析为/bar.js 
import ... from '/bar.js';
        不是必须通过导出的成员才能导入模块。如果不需要模块的特定导出, 但仍想加载和执行模块以利用其副作用,可以只通过路径加载它:
import './foo.js';
        导入对模块而言是只读的,实际上相当于const 声明的变量。在使用( *) 执 行批量导入时,赋值给别名的命名导出就好像使用Object.freeze() 冻结 过一样。直接修改导出的值是不可能的,但可以修改导出对象的属性。 同样,也不能给导出的集合添加或删除导出的属性。要修改导出的值, 必须使用有内部变量和属性访问权限的导出方法。
import foo, * as Foo './foo.js'; 

foo = 'foo'; // 错误 

Foo.foo = 'foo'; // 错误 

foo.bar = 'bar'; // 允许
        命名导出默认导出的区别也反映在它们的导入上。命名导出可以使 用*批量获取并赋值给保存导出集合的别名,而无须列出每个标识符:
const foo = 'foo', bar = 'bar', baz = 'baz'; 
export { foo, bar, baz } 
import * as Foo from './foo.js'; 

console.log(Foo.foo); // foo 
console.log(Foo.bar); // bar 
console.log(Foo.baz); // baz
        要指名导入,需要把标识符放在import 子句中。使用 import 子句可以为 导入的值指定别名:
import { foo, bar, baz as myBaz } from './foo.js';
 
console.log(foo); // foo 
console.log(bar); // bar 
console.log(myBaz); // baz
        默认导出就好像整个模块就是导出的值一样。可以使用default 关键字 并提供别名来导入。也可以不使用大括号,此时指定的标识符就是默认 导出的别名:
// 等效 
import { default as foo } from './foo.js';
import foo from './foo.js';
        如果模块同时导出了命名导出默认导出,则可以在import 语句中同时 取得它们。可以依次列出特定导出的标识符来取得,也可以使用* 来取 得:
import foo, { bar, baz } from './foo.js'; 
import { default as foo, bar, baz } from './foo.js'; 
import foo, * as Foo from './foo.js';

模块转移导出

        模块导入的值可以直接通过管道转移到导出。此时,也可以将默认导出 转换为命名导出,或者相反。如果想把一个模块的所有命名导出集中在 一块,可以像下面这样在bar.js中使用 * 导出:
export * from './foo.js';
        这样,foo.js 中的所有命名导出都会出现在导入 bar.js 的模块中。如果 foo.js有默认导出,则该语法会忽略它。使用此语法也要注意导出名称 是否冲突。如果foo.js 导出 baz bar.js 也导出 baz ,则最终导出的是 bar.js 中的值。这个“ 重写 是静默发生的:
foo.js
export const baz = 'origin:foo';
bar.js
export * from './foo.js'; 
export const baz = 'origin:bar';
main.js
import { baz } from './bar.js'; 
console.log(baz); // origin:bar
        此外也可以明确列出要从外部模块转移本地导出的值。该语法支持使用 别名:
export { foo, bar as myBar } from './foo.js';
        类似地,外部模块的默认导出可以重用为当前模块的默认导出:
export { default } from './foo.js';
        这样不会复制导出的值,只是把导入的引用传给了原始模块。在原始模 块中,导入的值仍然是可用的,与修改导入相关的限制也适用于再次导 出的导入。
        在重新导出时,还可以在导入模块修改命名或默认导出的角色。比如, 可以像下面这样将命名导出指定为默认导出:
export { foo as default } from './foo.js';

向后兼容

        ECMAScript模块的兼容是个渐进的过程,能够同时兼容支持和不支持 的浏览器对早期采用者是有价值的。对于想要尽可能在浏览器中原生使用 ECMAScript 6 模块的用户,可以提供两个版本的代码:基于模块的版 本与基于脚本的版本。如果嫌麻烦,可以使用第三方模块系统(如
SystemJS )或在构建时将 ES6 模块进行转译,这都是不错的方案。
        第一种方案涉及在服务器上检查浏览器的用户代理,与支持模块的浏览 器名单进行匹配,然后基于匹配结果决定提供哪个版本的JavaScript 文 件。这个方法不太可靠,而且比较麻烦,不推荐。更好、更优雅的方案 是利用脚本的type 属性和 nomodule 属性。
        浏览器在遇到<script> 标签上无法识别的 type 属性时会拒绝执行其内 容。对于不支持模块的浏览器,这意味着<script type="module"> 不会 被执行。因此,可以在<script type="module"> 标签旁边添加一个回 退<script> 标签:
// 不支持模块的浏览器不会执行这里的代码 
<script type="module" src="module.js"></script> 
// 不支持模块的浏览器会执行这里的代码 
<script src="script.js"></script>
        当然,这样一来支持模块的浏览器就有麻烦了。此时,前面的代码会执 行两次,显然这不是我们想要的结果。为了避免这种情况,原生支持 ECMAScript 6模块的浏览器也会识别 nomodule 属性。此属性通知支持 ES6模块的浏览器不执行脚本。不支持模块的浏览器无法识别该属性,
从而忽略这个属性的存在。
        因此,下面代码会生成一个设置,在这个设置中,支持模块和不支持模 块的浏览器都只会执行一段脚本:

// 支持模块的浏览器会执行这段脚本 
// 不支持模块的浏览器不会执行这段脚本
 <script type="module" src="module.js"></script> 

// 支持模块的浏览器不会执行这段脚本 
// 不支持模块的浏览器会执行这段脚本 
<script nomodule src="script.js"></script>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值