You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
这个要从ES6模块化原理来说明,因为ES6的模块是动态加载,如果使用import从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。首先代码的执行分为两部分,一部分是静态编译阶段,另一部分是代码执行的部分,在静态编译的时候,每个模块的对外接口只是一种静态定义,这种静态定义在代码静态解析阶段就会生成。这样会导致import命令优先执行,优先于模块的其他内容来执行。
就根据上面那个实例来看,当我们执行代码的时候,首先对整个项目做静态分析,判断知道a模块中 bar指向来自b模块bar接口,同理也知道b模块中foo变量指向a模块中的foo,生成每个模块的对外接口的静态定义,生成一个只读引用,这样在静态编译过程就完成了,然后开始执行a模块代码,按照常规的方式来执行,首先进行变量提升,同时将import提升,这这段代码中也就是提升import {bar} from './b';,因为b模块还没有执行过,所以开始执行b模块中的内容,执行b模块中的内容同理,首先提升import语句,但是a模块已经执行过,所以认为接口foo已经初始化完成,所以开始执行b模块后面的代码,不会返回执行a模块的代码,然后执行下一步console.log(foo);,这时候就取foo中引用的值,但是我们发现此时的foo还没有在a模块中定义,所以这时候就会返回foo没有定义的错误。那么怎样才能不报错,可以将a模块中的foo进行提升,比如将foo使用var声明或者将foo变为一个函数,这样的程序就可以取到对应的值了。这就是整个执行过程,但是这里也谈一下babel中的实现,babel转义之后,会将export语句提升,所以如果这是babel中的执行代码,这时候应该会返回undefined,而不是直接报错。
// a.mjsimport{foo}from'./b';console.log('a.js');exportconstbar=1;exportconstbar2=()=>{console.log('bar2');}exportfunctionbar3(){console.log('bar3');}// b.mjsexportletfoo=1;import*asafrom'./a';console.log(a);// node --experimental-modules a.mjs[Module]{bar: <uninitialized>,
bar2: <uninitialized>,
bar3: [Function: bar3] }a.js// 如果这时候取a中bar的值将会报错,报错原因如下: bar is not defined,原因同上
这里主要学习两种模块化规范,一种是ES6中的模块化规范,另一种是CommonJS中的模块规范,针对模块化的发展
为什么要使用模块化
在以前的前端开发中,前端的项目都比较小,并且大部分都是单人开发,所有对于当时出现的global污染、命名冲突等不是很在意,但是对随着前端得发展,前端的项目开始变得比较大,对于一个项目,更多的人参与进来,这时候以前越来越多的全局污染,命名冲突等情况逐渐进入前端开发者的视野,于是前端er开始封装代码,使用IIFE的方法,这样就减少命名冲突和全局污染,但是现在依赖管理出现了问题,需要一个熟悉整个项目的人将代码按照顺序合并起来,这种事情不仅累,并且很容易出错,所以前端er开始思考如何加载代码,根据其他语言的特性,前端这个领域开始兴起各种各样的模块化方案,随着时间的检验,目前比较常用的模块规范有两种,一种是CommonJs规范,主要是因为Node中带起来,主要用于服务端开发,另一种就是ES规范中提出的模块化,是一种语言规范。在这个历史进程中出现模块化
直接定义依赖
、namespace模式
、匿名闭包IIFE
、模板依赖定义
、注释定义依赖
、依赖注入
、CommonJS模式
、AMD模式
、ES2015 Modules
总结起来,模块化主要解决了这几个问题:
CommonJS
commonJs原名是ServiceJS,随着node的火爆,才慢慢改名称之为CommonJS,想要统一服务端和客户端,但是require是同步的,这样就导致AMD和CMD等等异步加载规范的提出,随着ES6中模块化的提出,AMD和CMD渐渐消失在视野中了,但是在Node中,CommonJS还是比较常用的,所以这里我们直接学习Node中的实现CommonJS这套规范:
在Node的实现中,最重要的就是require函数,在Node中引入模块需要经过三个过程,分别是:
ES6模块化
ES6的模块设计思想是尽量模块化,使得在编译时候就可以确定模块之间的依赖关系,以及输入和输出的变量,这个种方式的效率比CommonJS的效率高很多,所以这样的话ES6中的模块化不能按照CommonJS中理解的解构赋值来解释相关模块引入和相关的实例
模块功能主要由两个命令构成:
export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。export
命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。不知道,待续。。。
import
语句,同时输入默认方法和其他接口,可以写成下面这种import()
,以为ES6的模块化要统一客户端和服务器端,因为CommonJS中含有动态加载的功能,所以为了也实现动态加载功能,所以推出这个API,调用之后返回一个Promise对象,调用成功后悔返回一个对象当做then的参数。两种模块规范区别
如果在浏览器中要使用ES6的模块化,需要在
<script>
中添加type="module"
,这样浏览器就会按照模块化的方式来执行,如果将代码放入网上,脚本相当于默认开启defer
,同理我们也可以显示的为async
那个这样的话脚本的加载和执行就会按照async
的方式进行执行。这里面node在8.5+版本上支持使用ES6的模块化,但是Node要求,使用ES6模块,必须使用后缀为
.mjs
文件,并且在执行的时候需要添加相关参数,类似于node --experimental-modules index.mjs
如何处理循环加载
主要分为两种循环加载,一种是Commonjs中的循环加载,另一种就是ES6规范中的循环加载
一旦某个模块出现循环加载,就只输出已经执行的部分,还未执行的部分不会输出,举个例子(摘抄ES6入门):
目前实现方式不同,一种是通过babel进行转义来实现ES6的模块化,另一种就是原生来实现,目前主要有两个阶段,一个是浏览器中实现,另一个就是在node中的实验特性,通过添加
--experimental-modules
进行启动,这里我们主要使用node中V8的实现的模块化为准,babel转义之后可能有所不同。这个要从ES6模块化原理来说明,因为ES6的模块是动态加载,如果使用
import
从一个模块加载变量(即import foo from 'foo'
),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。首先代码的执行分为两部分,一部分是静态编译阶段,另一部分是代码执行的部分,在静态编译的时候,每个模块的对外接口只是一种静态定义,这种静态定义在代码静态解析阶段就会生成。这样会导致import命令优先执行,优先于模块的其他内容来执行。就根据上面那个实例来看,当我们执行代码的时候,首先对整个项目做静态分析,判断知道a模块中 bar指向来自b模块bar接口,同理也知道b模块中foo变量指向a模块中的foo,生成每个模块的对外接口的静态定义,生成一个只读引用,这样在静态编译过程就完成了,然后开始执行a模块代码,按照常规的方式来执行,首先进行变量提升,同时将import提升,这这段代码中也就是提升
import {bar} from './b';
,因为b模块还没有执行过,所以开始执行b模块中的内容,执行b模块中的内容同理,首先提升import语句,但是a模块已经执行过,所以认为接口foo已经初始化完成,所以开始执行b模块后面的代码,不会返回执行a模块的代码,然后执行下一步console.log(foo);
,这时候就取foo中引用的值,但是我们发现此时的foo还没有在a模块中定义,所以这时候就会返回foo没有定义的错误。那么怎样才能不报错,可以将a模块中的foo进行提升,比如将foo使用var声明或者将foo变为一个函数,这样的程序就可以取到对应的值了。这就是整个执行过程,但是这里也谈一下babel中的实现,babel转义之后,会将export语句提升,所以如果这是babel中的执行代码,这时候应该会返回undefined,而不是直接报错。这里还有一个神奇的地方就是:现在可以取到未定义的值,比如在这个里面输出的,应该是ES6中新定义的Module类型。
The text was updated successfully, but these errors were encountered: