es6 依赖循环_require 和 import 的循环依赖详解

说到前端模块化,就不得不说到循环加载,就像混乱背后隐藏着的清晰地秩序。

什么叫循环加载?

我们来看一段代码。1

2

3

4

5

6

7

8

9

10

11

12

13const b = require('./b');

b();

module.exports = function(){

console.log('This is a.js');

}

//b.js

const a = require('./a');

a()

module.exports = function(){

console.log('This is b.js')

}

a 加载了 b,b 也加载了 a,这个时候,循环加载就出现了。

CommonJS 下的循环加载

运行 node a.js 你会发现,报错了:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15/Users/jackiels/learn/test/b.js:8

a()

^

TypeError: a is not a function

at Object. (/Users/jackiels/learn/test/b.js:8:1)

at Module._compile (module.js:571:32)

at Object.Module._extensions..js (module.js:580:10)

at Module.load (module.js:488:32)

at tryModuleLoad (module.js:447:12)

at Function.Module._load (module.js:439:3)

at Module.require (module.js:498:17)

at require (internal/module.js:20:19)

at Object. (/Users/jackiels/learn/test/a.js:8:11)

at Module._compile (module.js:571:32)

为什么会报错?

我们来 console.log('a:',a),返回的结果显示 a 是一个{}。明明 module.exports 导出的是一个函数,为什么就变成了一个空对象?以下内容引自CommonJS 的加载原理CommonJS 的一个模块,就是一个脚本文件。require 命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。1

2

3

4

5

6{

id: '...',

exports: { ... },

loaded: true,

...

}

上面代码中,该对象的 id 属性是模块名,exports 属性是模块输出的各个接口,loaded 属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了。以后需要用到这个模块的时候,就会到 exports 属性上面取值。即使再次执行 require 命令,也不会再次执行该模块,而是到缓存之中取值。

这个时候的 exports 那个字段的值就是 {},之所以这样因为 CommonJS 中非常重要的一个加载模式:一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出

我们回头看看我们的栗子🌰,执行 a.js,a 里面require(./b),所以去执行 b.js

b.js 里面require(./a),循环加载出现,所以 a.js 会输出已执行的部分

a.js 执行完 require('b.js') 之后什么也没做,所以 a.js 相当于只是生成了一个 module 对象,所以 b.js 继续执行。想深入研究 module 对象看这个

这时候 a.js 的 exports 是一个刚刚初始化的对象,什么内容都没有,所以报错了a is not a function。

怎样可以正确运行?

我们把 a.js 改一下在运行一次。1

2

3

4

5

6module.exports = function(){

console.log('This is a.js');

}

const b = require('./b');

b();

运行结果,可以正常执行,1

2This is a

This is b

再来看看执行流程执行 a.js,module.exports = function(){...}先定义了module.exports

执行require('./b'),进入 b.js

执行require('./a'),循环加载出现,所以 a.js 输出已执行的部分

a.js 已执行的部分是module.exports = function(){...},所以,这时候 b.js 中的变量 a 等于function(){...}

a()输出This is a.js,继续执行 b 里面的module.exports = function(){...}

b.js 执行完毕,回到 a.js

a.js 中的变量 b 等于module.exports = function(){...}

b()输出 This is b.js 执行完毕

整个的流程是一个 程序执行栈,先进后出。

再看一个官方的栗子1

2

3

4

5

6console.log('a starting');

exports.done = false;

const b = require('./b.js');

console.log('in a, b.done = %j', b.done);

exports.done = true;

console.log('a done');

b.js:1

2

3

4

5

6console.log('b starting');

exports.done = false;

const a = require('./a.js');

console.log('in b, a.done = %j', a.done);

exports.done = true;

console.log('b done');

main.js:1

2

3

4console.log('main starting');

const a = require('./a.js');

const b = require('./b.js');

console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

运行如下1

2

3

4

5

6

7

8

9$ node main.js

main starting

a starting

b starting

in b, a.done = false

b done

in a, b.done = true

a done

in main, a.done=true, b.done=true

运行过程详解如下:(--->表示在哪个文件之中)1

2

3

4

5

6

7

8

9

10

11

12

13--->main require(a) 执行后,会直接运行 a 脚本,

--->a exports.done = false

--->a require(b) 执行后,会直接运行 b 脚本

--->b exports.done = false

--->b require(a) 执行后出现循环加载,a 模块只会输出已运行的部分, exports.done = false 这时候 b.js 中的 a = { done:false }

--->b console.log(' 在 b.js 之中,a.done = %j', a.done); 在 b.js 之中,a.done = false

--->b exports.done = true;

--->b console.log('b.js 执行完毕 ') 开始继续执行 a

--->a console.log(' 在 a.js 之中,b.done = %j', b.done); 这时候,b 输出已运行的部分,exports.done = true,这时候 a.js 中的 b = { done: true }

--->a exports.done = true;

--->a console.log('a.js 执行完毕 ');

--->main require('b.js'),因为 b.js 在之前已经被 a 执行完了,所以相当于内存中的 loaded 已经为 true 了,所以不会去在执行一次 b.js 而是直接取到它输出的结果 b = { done:true }

--->main console.log(' 在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done); 在 main.js 之中, a.done=true, b.done=true

我前面说过,程序执行栈,用栈的思路捋一下就是:执行 main

—> requrire(a)进栈

—> 执行 a 并保存结果

—> require(b) 进栈

—> 执行 b 并保存结果

—> require(a),a 已在内存中取当前 a 执行结果

—> b 执行完毕并保存结果出栈

—> 执行 a 并保存结果

—> a 执行完毕并保存结果出栈

—> 执行 main

—> require(b),b 已在内存中取当前 b 执行结果

—> 程序结束

以上例子的执行结果,都表现出以下两个重点:require 命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象

出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出

ES6 Module 下的循环加载

我们来看个代码1

2

3

4

5

6

7

8

9

10

11

12

13

14// a.js

import {bar} from './b.js';

export function (){

console.log('在 a.js')

bar();

}

foo() // 启动循环加载

//b.js

import {foo} from './a.js';

export function bar(){

console.log('在 b.js')

foo();

}

执行这段代码不久之后你就会发现,内存爆了。1

2

3

4

5

6

7

8

9

10

11RangeError: Maximum call stack size exceeded

at process.get [as domain] (domain.js:22:16)

at process.nextTick (internal/process/next_tick.js:156:22)

at onwrite (_stream_writable.js:372:15)

at WriteStream.Socket._writeGeneric (net.js:734:5)

at WriteStream.Socket._write (net.js:744:8)

at doWrite (_stream_writable.js:329:12)

at writeOrBuffer (_stream_writable.js:315:5)

at WriteStream.Writable.write (_stream_writable.js:241:11)

at WriteStream.Socket.write (net.js:671:40)

at Console.log (console.js:43:16)

WTF?

我们用 babel 编译一下看看结果

a.js 编译之后1

2

3

4

5

6

7

8

9

10

11

12

13

14

15;

Object.defineProperty(exports, "__esModule", {

value: true

});

exports.foo = foo;

var _b = require('./b.js');

function (){

console.log('在 a.js');

(0, _b.bar)(); // 这个语法目的是绑定 bar 的 this 为当前这个 module,babel 编译的结果,与本文关系不大,耻略

} // a.js

foo(); // 启动循环加载

b.js 编译之后1

2

3

4

5

6

7

8

9

10

11

12

13;

Object.defineProperty(exports, "__esModule", {

value: true

});

exports.bar = bar;

var _a = require('./a.js');

function bar(){

console.log('在 b.js');

(0, _a.foo)();

}

我们可以清晰地看到,所有对函数的执行都是引用方式的,相当于就是两个函数互相调用导致了死循环。所以得出一个结论:在 ES6 中不会出现所谓的“循环加载”,只是会有循环调用,而且这种循环调用也是因为写代码的人的思维短路造成的,所以我们根本我不用担心循环加载,只需要注意自己写代码的逻辑就好了。

参考资料

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556950.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅谈对称加密与非对称加密

在数字加密算法中,通过可划分为对称加密和非对称加密。 一:什么是对称加密? 在对称加密算法中,加密和解密使用的是同一把钥匙,即:使用相同的密匙对同一密码进行加密和解密; 加密过程如下&…

ios跨线程通知_一种基于Metal、Vulkan多线程渲染能力的渲染架构

快手Y-tech 原创最新技术干货分享随着3D渲染场景规模越来越复杂,单线程渲染架构在满足业务性能要求时已经捉襟见肘,因此,多线程渲染显得愈发重要。本文首先介绍了新一代图形渲染接口Metal、Vulkan,以及它们的多线程渲染特性&…

58同城面试盘点

58同城面试盘点 1.一张订单表,有user_name,order_id,order_time,order_amount 四个字段,怎么取出每个用户2021年10月以来第一个订单的金额(下单时间格式为’yyyy-MM-dd HH:mm:ss’)? select user_name,order_id,orde…

stringbuffer判断是否为空

StringBuffer sbnew StringBuffer();if(sb!null && sb.length()>0){System.out.println("证明sb不为空!"); }

virtualbox: win11主机安装deepin双向复制问题

virtualbox: win11主机安装deepin双向复制问题1.安装virtualbox增强组件(确保光驱可用)2.终端挂载3. 运行BoxLinuxAdditions4. 重启虚拟机,验证OK!使用virtualbox安装深度系统deepin虚拟,发现虚拟机和宿主机之间不能双向复制,已经…

基坑监测日报模板_刚刚!温州瓯海突发塌陷,初步判断为临近地块地下室基坑支护桩移位...

资料来源:瓯海新闻网 | 温州百事通 | 土木吧 | 岩土新鲜事 等版权归原作者所有如有侵权请联系删除9月10日中午11点左右,温州市瓯海区娄桥街道商汇路道路塌陷。塌陷路面位于商汇路的公交车站旁,几十米长的路面已经开裂,公交站台发生…

java 从一个总的list集合中,去掉指定的集合元素,得到新的集合——removeAll()

/*** 两个list集合的差集* author*/ public class ListSubstract {public static void main(String[] args) {List<String> list new ArrayList<>();//作为总的listList<String> existList new ArrayList<>();//存在的listlist.add("aa");…

virtualbox:win11上的deepin如何设置与宿主机共享文件

1. 安装virtualbox增强功能 这个没有测试&#xff0c;只是理论上需要。我在上一篇帖子《virtualbox: win11主机安装deepin双向复制问题》已经安装了增强功能&#xff0c;大家可以参考安装。 2.在virtualbox上配置共享 2.1 关闭虚拟机&#xff0c;进行设置 共享文件夹路径点击…

三角形外接球万能公式_秒杀三角形问题!!三角形分角线的几个重要结论及其应用...

点击“高中数学资料共享”关注我们解三角形问题在高考中的选择、填空、解答题一般都会涉及到(最少也有两块涉及到)&#xff0c;其中有一类涉及角平分线长度、中线长、高线长度问题&#xff0c;难度不大&#xff0c;但运算量不小&#xff0c;那我们如果在考试中能在最短时间内把…

java自定义注解为空值——自定义注解的魅力你到底懂不懂

前言 你知道自定义注解的魅力所在吗&#xff1f; 你知道自定义注解该怎么使用吗&#xff1f; 本文一开始的这两个问题&#xff0c;需要您仔细思考下&#xff0c;然后结合这两个问题来阅读下面的内容&#xff1b; 本文主线&#xff1a; 注解是什么&#xff1b;实现一个自定义注…

uniapp动态修改样式_掌握Photoshop图层样式技术

凹凸贴图效果“等等&#xff0c;什么&#xff1f;” 您会惊叹&#xff1a;““图层样式”菜单中没有凹凸贴图效果&#xff01;” 的确如此&#xff0c;但是通过将“图案覆盖”和“斜面和浮雕”结合使用&#xff0c;我们可以使用可控光源实现带纹理的凹凸贴图表面。此技术需要两…

一个专业搬砖人的幻想:全国实现旬休制度

每逢过年&#xff0c;总觉得假期不够忙&#xff0c;如果折腾折腾&#xff0c;应该还是可以的。 于是想了一个替代方案&#xff0c;以弥补春节余额不足、各种假期调休的诟病。以下是设计与比较表单&#xff1a; 调整后可以实现以下愿望&#xff1a; 旬休是每月分上、中、下三…

IoT -- (七)MQTT协议详解

MQTT是什么&#xff1f; MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一种基于发布/订阅&#xff08;Publish/Subscribe&#xff09;模式的轻量级通讯协议&#xff0c;该协议构建于TCP/IP协议上&#xff…

刀片服务器改台式电脑_服务器到底是个什么东东?跟电脑有啥区别?电脑知识学习!...

一位朋友留言点的内容&#xff0c;想了解服务器方面的知识&#xff0c;对于普通用户而言&#xff0c;确实对服务器感觉很神秘&#xff0c;不知道服务器到底是个什么东东&#xff0c;我保证看完这篇&#xff0c;你就会明白服务器到底是个啥了。首先可以很明确的告诉你&#xff0…

IoT -- (一) 物联网平台架构设计分析

现在网上讨论的有关物联网的帖子非常之多&#xff0c;但大部分都是介绍理论或者有关硬件&#xff0c;通讯相关的问题&#xff0c;比如物联网模块&#xff0c;物联网通讯协议MQTT、XMPP、NB_IOT等&#xff0c;个人认为这些只是物联网中一部分&#xff0c;而涉及到物联网的设备如…

SFTP批量下载与中文文件名乱码问题

一、批量下载 #!/bin/bashUSERroot #密码 PASSWORD123456 #下载文件目录 SRCDIR/data #FTP目录(待下载文件目录) DESDIR/ydfile #银联IP IP 192.111.111.111 #端口 PORT22# 清空当前目录下的旧文件 rm -rf /data/*#连接远程服务器摘取数据资源 lftp sftp://${USER}:${PASSWOR…

华为路由器hilink怎么用_华为无线充电怎么用?MatePadPro无线充电使用方法

越来越多的华为产品支持无线充电&#xff0c;比如Mate 30 Pro支持最高27W无线超级快充&#xff0c;FreeBuds 3 蓝牙无线耳机、荣耀V30 Pro&#xff0c;MatePad Pro平板也都支持无线充电。今天就跟大家分享华为手机无线充电技术原理图&#xff0c;无线充怎么用&#xff0c;无线充…

IoT -- (二) 物联网传感器介绍

传感器(Sensor)可以说是 物联网(Internet of Things, IoT)架构下&#xff0c;让智能自动化设备与智能联网产品&#xff0c;像是智能机器人、智能工厂、智能电动车、智能手环、智能医疗装置、智能家电、智能移动电话等&#xff0c;执行即时互动的关键元件。 资策会MIC资深产业分…

python乘法表运算_Python入门教程(三):史上最全的Numpy计算函数总结,建议收藏!...

点击上方 蓝字 关注我们Numpy提供了灵活的、静态类型的、可编译的程序接口口来优化数组的计算&#xff0c;也被称作向量操作&#xff0c;因此在Python数据科学界Numpy显得尤为重要。Numpy的向量操作是通过通用函数实现的。今天小编会给大家较为全面地介绍下Numpy的通用函数。01…

IoT -- (三) 2018 Top物联网项目排名

每年这个时候&#xff0c;知名物联网研究机构IoT Analytics都会基于市场上纷繁的信息来探索物联网项目的具体实施情况&#xff0c;今年也不例外。作为其追踪物联网生态的一个重要组成部分&#xff0c;IoT Analytics对1600个在企业中实际运行的物联网项目进行了整合、验证和分类…