| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> JavaScript知识库 -> 随笔-深入理解ES6模块化(三) -> 正文阅读 |
|
[JavaScript知识库]随笔-深入理解ES6模块化(三) |
ES6模块化语法ES6模块是什么
ES6模块如何输入输出
JS代码的静态解析阶段与运行阶段区分
静态作用域说明
ES6模块的输入输出是在静态解析阶段确定的
ES6模块输出接口和输出变量区分
export 命令
export {} 命令
export default 命令
ES6模块输入接口和输入变量区分
import?moduleFilePath?命令
import {} from moduleFilePath?命令
import * as xxx from moduleFilePath?命令
import xxx from moduleFilePath?命令
ES6模块加载浏览器中加载ES6模块ES6模块其实就是JS文件(暂时不考虑其他文件类型),而传统的浏览器引入JS文件的方式就是使用script标签导入。 但是为了让script标签能够区分?模块JS文件 和 非模块JS文件,即让script可以兼容引入以前非模块的JS文件。所以给script标签加入了 type = "module" 属性,来告诉浏览器引入的是ES6模块JS文件。 但是由于?<script src="./a.js" type="module"></script> 只能触发模块JS文件代码执行(首次执行),而无法定义输入接口,所以无法获取到模块的输出。 想要获取到模块的输出,必须使用import命令,即浏览器加载ES6模块的第二种方式,内部JS导入ES6模块 浏览器加载ES6模块的方式有两种: 1、script标签设置type=module 引入 外部JS模块,此时无法获取外部JS模块的输出 2、script标签设置type=module 编写内部JS,在内部JS中使用 import命令导入外部JS模块,此时可以获取到外部JS模块的输出 需要注意的是,这两种方式其实都是依赖于script标签完成的模块加载,而script标签默认是同步加载的。 即浏览器从服务器请求加载好HTML文件,会自上而下逐行解析,构建DOM树,但是构建过程中,如果遇到script标签,则会暂停DOM树构建,即停止解析HTML,转而去加载script的引入的外部JS文件,等外部JS文件下载好后,继续执行JS文件中的代码,等JS文件代码执行完后,继续DOM树构建,如果下面还有script标签,则继续暂停DOM树构建,而去加载执行JS。 这种script标签默认同步加载执行JS文件的方式会对网页加载(DOM树构建)产生阻塞,造成网页延迟演示,网页无法做任何DOM操作,用户体验不好。 所以script标签还支持异步加载JS文件,只要设置defer或async属性 如果script标签设置defer属性,则script标签下载外部JS文件不会阻塞DOM构建,二者并发,等JS文件下载完毕,也不会立刻执行,而是等到DOM构建完成后执行。 如果script标签设置async属性,则script标签下载外部JS文件不会阻塞DOM构建,二者并发,等JS文件下载完毕,DOM还未构建完成的话,就会被阻塞,优先JS代码执行,JS代码执行完继续DOM构建。 而设置了 type="module"的script标签,相当于带了 defer属性,即异步加载JS模块,不阻塞DOM构建,且会等DOM构建完成后,才执行JS模块代码。 Nodejs中加载ES6模块Nodejs默认使用的是CommonJS模块化,所以ES6模块无法在Nodejs中直接加载使用,而需要做一些配置化工作: 方式一:将ES6模块文件的后缀名定义为mjs,来标识自己是ES6模块,此时Nodejs就会按照ES6模块化语法来处理mjs文件 方式二:将项目的package.json配置文件中 type属性改为 module,默认是 commonjs,此时Nodejs处理js文件就会按照ES6模块化语法来处理,而原来的commonjs模块就无法处理了,若需要兼容原来commonjs模块,则需要将commonjs模块后缀名改为 cjs 通常来说,不应将ES6模块和Commonjs模块混用,即ES6模块中无法直接加载commonjs模块,commonjs模块中也无法直接加载ES6模块 因为ES6模块化语法是 export , import ? ? ? ?Commonjs模块化语法是 module.exports , require import 和 require的区别不仅是用法上的,还有它们的加载时机和方式不同: import 是在静态解析阶段加载模块输出接口,且是异步加载,即不阻塞后续JS执行 require 是在运行阶段加载模块输出对象,且是同步加载,会阻塞后续JS执行 export 和 module.exports 的区别也不仅是用法上,还有设计上的区别: export 是在静态解析阶段完成模块的对外接口的输出,它输出的不是一个对象,也不是一个变量,而是静态解析阶段的 符号引用,外部可以通过符号引用直接获取到对应模块中输出变量的实时数据。 module.exports 是在运行阶段完成模块的对外输出,他是一个对象,挂载在该对象上数据都是拷贝数据(浅拷贝),和原模块中的输出变量没有关系了。外部获取module.exports,其实获取的是缓存数据,而不是原模块内的实时数据。 ES6模块和Commonjs模块的相同点就是: 二者对于同一模块多次加载都只会执行一次模块内代码,即首次加载执行,后面加载模块不执行其内部代码。 ES6模块的循环引用问题?请看上面例子,HTML中通过script标签加载a.js触发其内部代码执行。 a.js 执行第一步就是加载b.js触发其执行, b.js 执行第一步也是去加载 a.js,如果也触发了a.js执行,那么形成了死循环,而ES6模块加载机制有一个特性避免了死循环:”模块只在第一次被加载时会执行内部代码,后面无论被加载几次都不会执行内部代码“ 即:HTML中通过script标签加载a.js触发其内部代码执行 已经消耗掉了 首次执行权。 b.js此时加载a.js已经不会触发它内部代码二次执行了。这是避免死循环的根本原因。 那么此时 b.js 通过 import 导入的 foo 有值吗? 我们知道import和export在静态解析阶段就完成了 模块间输入输出接口 的联系。但是输出接口一一对应的输出变量的值,不是在静态解析阶段确定的,而是在运行阶段确定的。 即export只是导出了 变量foo的符号引用,没有导出变量foo的值。变量foo的值是在运行阶段给定的。 而此时 export let foo = 'foo' 代码赋值语句还没有执行。而let foo 又不存在声明提升(var foo有声明提升,且会有初始值undeifned),所以此时 foo是未初始化的,故而,b.js执行到第三行代码使用foo时,报错foo未初始化。 我们可以将a.js中 let foo改为 var foo试试,此时由于var foo 会在预编译时声明提升(静态解析阶段执行),所以运行阶段 foo是有值的,foo值为undefined 而函数声明也会提升,且会带着函数本身一起提升,即函数声明在静态解析阶段就会完成,所以使用函数声明来代替var变量声明 ?上述使用var变量和函数声明 来解决模块循环引用时,前一个模块未执行完,后一个模块就要用输出,导致报错变量未初始化的问题。其底层原因就是利用了 var变量声明 和 函数声明 也是在代码运行阶段之前的 静态解析阶段完成 这一特性。 ES6模块中import和export优先级问题其实import和export只是在静态解析阶段完成 符号引用的导入与导出,不存在优先级问题。 优先级问题 指的是 运行阶段代码执行的先后顺序,而静态解析阶段无关。 但是 import 和 export 又和 运行阶段 有着密切的关系。 比如 import 除了提供输入接口,输入变量,还可能触发 导入模块的代码执行,而代码执行必须在运行阶段完成,所以import导入的模块的代码,会优先于本模块中所有的代码 第一步执行。 给人造成了一种错觉,import在运行阶段也会执行。 另外,export 只会提供 模块输出变量的符号引用,而输出变量的定义和赋值都是在运行阶段完成的,而这也给人造成一种错觉 export也会在运行阶段执行。 实际上,在运行阶段完全可以忽略 import 和 export的存在,只需要机制模块A可以访问模块B中输出变量,但是如果模块A访问模块B输出变量时,模块B还没有完成输出变量的定义和赋值,则会报错。 比如模块循环引用的问题 a.js 优先执行 b.js模块中代码,而b.js又会去加载a.js,但是a.js已经被执行过了,所以无法二次执行,b.js只能硬着头皮继续往下执行,遇到使用a.js输出变量foo时,会根据符号引用,找到a.js中的输出接口,但是此时输出接口一一对应的变量所在行代码还没有被执行,即还没有完成赋值操作,(let foo会在静态解析阶段完成变量声明,但是没有初始化,和var foo有区别,var foo在静态解析阶段机会完成变量声明,也会完成undeifned赋值)所以此时b.js中foo是一个未初始化的let变量。 Commonjs模块循环依赖的问题Commonjs模块间也会存在循环依赖的问题,但是Commonjs解决循环依赖的底层逻辑和ES6模块是相同的。 即 同一个模块只会在首次加载时被触发执行,后续加载不会触发模块代码执行。 ?注意:a.js在require b.js后,自身代码暂停执行,因为 require是同步加载,会阻塞后续代码执行,直到 require返回。 b.js执行后,require a.js,但是a.js已经执行过了(node a 启动执行时),所以不会二次执行,此时b.js中第2行代码 require返回的对象是啥呢?
所以此时b.js中第2行代码 require返回的对象就是 a.js做了exports.done = false后的exports对象。 所以b.js中第3行代码可以访问到a.done的值 |
|
JavaScript知识库 最新文章 |
ES6的相关知识点 |
react 函数式组件 & react其他一些总结 |
Vue基础超详细 |
前端JS也可以连点成线(Vue中运用 AntVG6) |
Vue事件处理的基本使用 |
Vue后台项目的记录 (一) |
前后端分离vue跨域,devServer配置proxy代理 |
TypeScript |
初识vuex |
vue项目安装包指令收集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 9:26:24- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |