无需登录 数据私有 本地保存

ES Module 与普通脚本对比演示 - 作用域与加载

79
0
0
0

ES Module

ES Module是ECMAScript 2015引入的标准JavaScript模块系统。它使用import和export关键字来声明模块之间的依赖关系和导出接口。ES Module的核心特性包括静态结构,import和export声明必须位于模块顶层,这使得JavaScript引擎能够在不执行代码的情况下分析模块的依赖图。ES Module默认以严格模式运行,无需显式声明"use strict"。每个模块都有自己独立的作用域,顶层变量不会污染全局作用域。ES Module支持异步加载,适合浏览器环境中的按需加载。通过script标签的type="module"属性在HTML中引入,浏览器会自动以CORS方式加载和执行模块代码。

普通Script

普通Script是指通过HTML中的script标签(不带type="module"属性)加载和执行的JavaScript代码。普通Script是最传统的JavaScript加载方式。其执行特点是:脚本顶层声明的变量和函数会成为全局变量,挂载到window对象上;默认不在严格模式下运行,除非脚本开头显式声明了"use strict";脚本从上到下同步执行,阻塞HTML的解析(除非使用defer或async属性);同一个script标签引用的脚本在页面刷新后会重新加载和执行。普通Script适合需要兼容旧版浏览器或加载非模块化第三方库的场景。

模块作用域

模块作用域是ES Module中每个模块独立拥有的执行上下文和变量可见范围。在模块作用域中声明的所有变量、函数和类都只在该模块内部可访问,外部代码无法直接访问。模块作用域的隔离是ES Module最重要的特性之一,它从根本上解决了传统JavaScript开发中的全局变量污染问题。模块之间通过明确的export和import声明来共享接口,实现了真正的信息隐藏和接口隔离。在普通Script中,顶层变量会自动成为全局变量,不同脚本之间的变量容易产生命名冲突,模块作用域彻底消除了这种冲突的可能性。

全局作用域

全局作用域是JavaScript中所有代码共享的最外层作用域。在浏览器环境中,全局作用域对应于window对象,任何在全局作用域中声明的变量和函数都会成为window对象的属性,可以在代码的任何位置访问。全局作用域的便利性在于变量的全局可访问性,但其弊端更为严重:不同脚本之间容易产生命名冲突、全局变量的修改难以追踪和调试、大量全局变量会导致代码的可维护性急剧下降。ES Module的设计初衷之一就是减少对全局作用域的依赖,通过模块作用域来组织代码,只在必要时通过export/import机制暴露和获取共享功能。

this绑定

this是JavaScript中的特殊关键字,它的值在运行时动态确定,取决于函数的调用方式和执行上下文。在ES Module中,模块顶层的this值为undefined,这是因为模块有自己独立的执行上下文,不与全局对象关联。在普通Script的顶层代码中,this值指向window对象,因为顶层代码在全局上下文中执行。在函数中,this的值取决于调用方式:作为对象方法调用时this指向对象、作为普通函数调用时this在非严格模式下指向window在严格模式下为undefined、通过call/apply/bind调用时this指向指定对象、箭头函数的this继承外层作用域的this。理解this在不同环境下的行为对于编写正确的JavaScript代码至关重要。

严格模式

严格模式是JavaScript的一种受限执行模式,通过在脚本或函数开头添加"use strict"声明来启用。在严格模式下,JavaScript引擎会执行更严格的语法检查和运行时约束,帮助开发者避免常见的错误和不安全的行为。严格模式禁止的主要行为包括使用未声明的变量(会抛出ReferenceError)、删除不可删除的属性(会抛出TypeError)、重复的参数名(会抛出SyntaxError)、八进制字面量(会抛出SyntaxError)、对只读属性赋值(会抛出TypeError)。ES Module默认启用严格模式,这是ES Module相比普通Script的一个重要优势,能够在开发阶段更早地发现潜在的bug。

模块缓存

模块缓存是ES Module的一种优化机制,确保同一个模块在整个应用程序生命周期中只被执行一次。当一个模块第一次被导入时,JavaScript引擎会执行该模块的代码并缓存其导出接口。后续任何代码再次导入该模块时,引擎不会重新执行模块代码,而是直接返回缓存的导出结果。模块缓存的实现机制保证了模块的单例语义,无论模块被导入多少次,其中的代码只执行一次,导出的状态在所有导入者之间共享。这对于管理模块中的副作用非常重要,避免了重复执行导致的问题。相比之下,普通Script标签每次在HTML中出现都会重新加载和执行脚本,没有缓存机制。

import声明

import声明是ES Module中用于导入其他模块导出接口的语法。import声明有多种形式:命名导入import名称from模块路径导入指定的具名导出;默认导入导入模块的默认导出;整体导入将模块的所有导出作为命名空间对象导入;副作用导入仅执行模块代码而不导入任何接口。import声明的核心特性是静态性,它们必须位于模块的顶层,不能在条件语句或函数内部使用。这种静态结构使得JavaScript引擎能够在不执行代码的情况下分析模块的依赖关系,为树摇优化和按需加载提供基础。import声明也可以使用import()函数进行动态导入,但这是异步操作,返回Promise。

export声明

export声明是ES Module中用于向外部暴露模块接口的语法。export声明有两种主要形式:命名导出导出具名接口,可以一次导出多个名称;默认导出导出一个默认接口,每个模块只能有一个默认导出。命名导出允许外部代码通过解构导入的方式精确获取需要的接口,支持树摇优化去除未使用的导出。默认导出适用于模块主要提供单一功能的场景,导入时不需要知道导出的名称。export声明和import声明共同构成了ES Module的接口契约,明确了模块之间共享什么以及如何共享。

树摇优化

树摇优化是一种在打包过程中自动移除未使用代码的优化技术。树摇优化的名称来源于想象将代码比作一棵树,摇动树干后未被引用的枯叶会被抖落。树摇优化依赖于ES Module的静态结构特性:由于import和export声明必须位于模块顶层且在编译时就能确定,打包工具能够分析整个模块依赖图,识别出哪些导出的接口没有被任何代码使用,然后从最终的打包结果中移除这些未使用的代码。普通Script不支持树摇优化,因为它的代码依赖关系在运行时才能确定。树摇优化能够显著减小打包文件的体积,提升应用加载性能,是ES Module的重要优势之一。

作用域链

作用域链是JavaScript中变量查找机制的核心概念。当代码需要访问一个变量时,JavaScript引擎首先在当前作用域中查找;如果没有找到,就沿着作用域链向上一级作用域查找;如此继续直到到达全局作用域。如果在整条作用域链上都没有找到变量,就会抛出ReferenceError。在ES Module中,模块顶层的作用域就是模块作用域,模块内部的函数和代码通过作用域链可以访问模块作用域中的变量,但外部代码无法逆向访问模块内部的变量。这种单向的作用域链机制是ES Module实现信息隐藏和接口隔离的基础。理解作用域链对于理解变量可见性和闭包的工作原理非常重要。

动态import

动态import是ES Module中通过import()函数实现的异步模块加载方式。与静态import声明不同,动态import可以在任何JavaScript代码中使用,包括条件语句和函数内部,加载时机由运行时逻辑决定。动态import返回一个Promise,resolve时提供模块的命名空间对象。动态import的典型应用场景包括按需加载路由组件以减少初始加载时间、根据用户操作条件加载特定功能模块、在运行时根据配置动态选择加载不同的实现。动态import保留了ES Module的模块缓存特性,同一模块只会被加载和执行一次。动态import弥补了静态import声明灵活性不足的缺点,是实现代码分割和按需加载的重要工具。

CORS

CORS(跨源资源共享)是浏览器的一种安全机制,控制网页能否加载和访问来自不同源的资源。ES Module在浏览器中加载时强制执行CORS策略,这意味着模块文件必须来自允许跨域访问的源,否则加载会失败。这是ES Module与普通Script的一个重要区别:普通Script标签不受同源策略的严格限制,但ES Module必须遵守CORS规则。CORS的强制执行是出于安全考虑,防止恶意脚本通过模块加载机制绕过同源策略。在开发环境中,使用本地文件系统直接打开HTML文件可能无法正确加载ES Module,需要通过本地HTTP服务器来提供服务。