ES6---Module的加载实现

浏览器加载
传统方法:在html网页中,浏览器通过<script>标签加载js脚本。

<!-- 页面内嵌的脚本 -->
<script type="application/javascript">
  // module code
</script>

<!-- 外部脚本 -->
<script type="application/javascript" src="path/to/myModule.js">
</script>

因为浏览器脚本默认语言是js脚本,所以type="application/javascript"可以省略
默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。

<script src="path/to/myMethod.js" defer></script>
<script src="path/to/myMethod.js" async></script>

<script>标签打开defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。
defer和async的区别:前者要等到整个页面正常渲染结束,才会执行,后者一旦下载完,渲染引擎就会终端渲染,执行这个脚本以后,再继续渲染,一句话,defer是“渲染完再执行”,async是下载完就执行,另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

加载规则
浏览器加载ES6模板,也是使用<script>标签,但是要加入type = “module”属性。

<script type= 'module' src = "foo.js"> </script>

网页中插入一个模块foo.js,由于type属性设为module,所以浏览器知道这是一个 ES6 模块。
浏览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。

<script type="module" src="foo.js"></script>
<!-- 等同于 -->
<script type="module" src="foo.js" defer></script>

<script>标签的async属性也可以打开,这时只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染。

<script type="module" src="foo.js" async></script>

ES6模块允许内嵌在网页中,语法行为与加载外部脚本一致;

<script type="module">
  import utils from "./utils.js";

  // other code
</script>

外部的模块脚本(foo.js),有几点需要注意:
(1) 代码是在模块作用域之中运行,而不是在全局作用域运行,模块内部的顶层变量,外部不可见。
(2) 模块之中,可以使用import命令加载其他模块(.js后缀不可省略,也可以使用export命令输出对外接口)
(3)模块之中,顶层的this关键字返回undefined,而不是指向window,也就是说:在模块顶层使用this关键字是无意义的;
(4)同一个模块如果加载多次只执行一次;

//实例模块
import utils from "https://www.baidu.com/js/utils.js";
const x =1;
console.log(x ===window)// false
console.log(this === undefined) //true
delete x ;//语法错误,不能删除x

利用顶层的this等于undefined这个语法点,可以侦测当前代码是否在 ES6 模块之中。

const isNotModuleScript = this !== undefined;

ES6模块与CommonJS模块的差异
(1)CommonJS模块输出的是一个值的拷贝,ES6模块输出得失值的引用;
(2)CommonJS模块是运行时加载,ES6模块是编译时输出接口。
CommonJS模块输出的是值的拷贝,也就是说一旦输出一个值,模块内部的变化就影响不到这个值;

//lib.js
var counter = 3;
function inCounter (){
   counter++;
}
module.exports = {
 counter :counter,
inCounter : inCounter,
};

//在main.js里面加载这个模块
var mod = require(".lib");
console.log(mod.counter);//3
mod.inCounter();
console.log(mod.counter);//3

lib.js模块加载以后,他的内部变化就影响不到输出的mod.counter了,这是因为mod.counter是一个元素类型的值;
ES6模块的运行机制与CommonJS不一样,js引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

//lib.js
export let counter = 3;
export function inCounter () {
     counter++;
}

//main.js
import { counter,inCounter} from "./lib";
console.log(counter);//3
inCounter();
console.log(counter);//4

ES6 模块输入的变量counter是活的,完全反应其所在模块lib.js内部的变化。

内部变量
ES6 模块应该是通用的,同一个模块不用修改,就可以用在浏览器环境和服务器环境。为了达到这个目标,Node 规定 ES6 模块之中不能使用 CommonJS 模块的特有的一些内部变量。
首先,就是this关键字。ES6 模块之中,顶层的this指向undefined;CommonJS 模块的顶层this指向当前模块,这是两者的一个重大差异。
其次,以下这些顶层变量在 ES6 模块之中都是不存在的。
arguments
require
module
exports
filename
dirname
CommonJS模块的加载原理
CommonJS的一个模块,就是一个脚本文件,require命令第一次加载该脚本,就会执行这个脚本,然后在内存生成一个对象。

{
   id: "...",
    exports:{...},
     loaded:true,
    ...
}

Node内部加载模块后生成的一个对象,该对象的id属性是模块名,exports属性是模块输出的各个接口,loaded属性是一个布尔值,表示该模板的脚本是否执行完毕,等等属性;
以后需要用到这个模块的时候就会到export属性上面取值,即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值,也就是说:CommonJS模块无论加载多少次,只在第一次加载时运行一次,以后再加载,就返回第一次运行的结果

 

备注:es6模块:每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 

备注: 模块化的好处主要是两点:

1. 避免全局变量污染
2. 有效的处理依赖关系

作者:startitunderground
链接:https://www.imooc.com/article/20630?block_id=tuijian_wz
来源:慕课网

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ES6模块和CommonJS的区别在于它们的语法和实现方式不同。ES6模块是ES6规范中定义的一种模块化方式,它使用import和export关键字来导入和导出模块。而CommonJS是Node.js中使用的一种模块化方式,它使用require和module.exports来导入和导出模块。 ES6模块支持静态分析,可以在编译时确定模块的依赖关系,从而实现更好的性能和可靠性。而CommonJS模块是动态的,需要在运行时才能确定模块的依赖关系,因此在性能和可靠性方面不如ES6模块。 此外,ES6模块还支持命名导入和导出,可以更灵活地控制模块的导入和导出。而CommonJS模块只支持默认导入和导出,无法实现命名导入和导出。 ### 回答2: ES6 module和CommonJS是两种不同的模块化解决方案,虽然两者主要目的都是为了让JavaScript代码更容易组织和重用,但是它们在具体实现上存在很大的区别,本文将从以下四个方面来介绍它们的区别: 1.语法区别 ES6 module使用了新的关键字来声明模块中的变量、方法和类等例如export、import等,而CommonJS则用require()和module.exports。ES6 module的语法更简洁易读且与JavaScript同步,CommonJS的语法比较复杂,且需要异步模块。 2.模块方式不同 ES6模块在编译的时候会根据import引用的模块信息,去异步依赖的模块,这种方式叫做静态。而CommonJS的模块则是在运行时同步,也就是说CommonJS会将所有依赖的模块全部完成,然后再运行代码,这种方式比较适合服务器端的开发。 3.ES6模块有作用域 ES6模块和CommonJS中的模块是有作用域的,但是ES6模块中的作用域是静态的,而CommonJS中的模块作用域是动态的,也就是说在CommonJS中,模块中变量的值在代码运行期间是可变的,而在ES6模块中则不是。 4.ES6模块支持循环依赖 ES6模块中是支持循环依赖的,它会先给所有需要依赖的模块都分配一个空间,然后再去填充它们,这样就避免了循环依赖的问题。而CommonJS中的模块是不支持循环依赖的,因为在CommonJS中,模块的是同步的,所以不能把某个模块的工作放在另外一个模块完毕之后再执行。 总之,ES6 module是一种新的、更先进和可靠的模块化方案,并且支持静态和循环依赖等功能,虽然目前不被所有浏览器支持,但是它的优点是显而易见的。相比之下,CommonJS更适用于服务器端的开发,但是因为它还需要异步和模块作用域的问题等,所以在实际的开发过程中并不是很方便和实用。 ### 回答3: ES6模块和CommonJS模块是JavaScript中两种主要的模块化规范,它们有一些明显的区别。 1. 语法不同 ES6模块的导入和导出是基于语法的,也就是使用import和export关键字来操作。而CommonJS模块则是使用require()函数来导入模块,使用module.exports(或exports的简写)来导出模块。 2. 执行方式不同 ES6模块是在代码编译时进行解析的,也就是在代码执行前进行模块解析和编译。而CommonJS模块是在代码运行时才会进行解析。 3. 是否支持静态分析 ES6模块支持静态分析,也就是可以在编译时对模块进行分析和优化,这在性能上有很大的优势。而CommonJS模块不支持静态分析,这在性能上有些影响。 4. 值拷贝与动态绑定不同 在ES6模块中,导入的变量是值拷贝,也就是说,导入的变量与导出的变量不是同一个引用,它们是两个相互独立的对象。而在CommonJS模块中,导入的变量是动态绑定,也就是说,导入的变量与导出的变量是同一个引用。这可能会导致意外的副作用。 5. 是否支持异步 ES6模块原生支持异步,也就是可以在代码中使用import()函数来异步模块。而CommonJS模块不支持异步,必须在同步代码中使用require()来模块。 总体来说,ES6模块在语法、执行方式、静态分析、值拷贝与动态绑定、支持异步等方面都有优势,这也是为什么越来越多的JavaScript开发者选择使用ES6模块。不过,在Node.js环境下,CommonJS模块仍然是主要的模块化规范。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值