


Implementation method of cyclic loading of JavaScript modules_javascript skills
"Circular dependency" means that the execution of script a depends on script b, and the execution of script b depends on script a.
// a.js var b = require('b'); // b.js var a = require('a');
Usually, "loop loading" indicates the existence of strong coupling. If not handled well, it may also lead to recursive loading, making the program unable to execute, so it should be avoided.
But in fact, this is difficult to avoid, especially for large projects with complex dependencies. It is easy for a to depend on b, b to c, and c to depend on a. This means that the module loading mechanism must take into account "loop loading" situations.
This article introduces how the JavaScript language handles "loop loading". Currently, the two most common module formats, CommonJS and ES6, have different processing methods and return different results.
1. Loading principle of CommonJS module
Before introducing how ES6 handles "loop loading", let's first introduce the loading principle of the most popular CommonJS module format.
A module of CommonJS is a script file. The first time the require command loads the script, it will execute the entire script and then generate an object in memory.
{ id: '...', exports: { ... }, loaded: true, ... }
In the above code, the id attribute of the object is the module name, the exports attribute is each interface output by the module, and the loaded attribute is a Boolean value, indicating whether the script of the module has been executed. There are many other attributes, but they are omitted here. (For detailed introduction, please refer to "require() Source Code Interpretation".)
When you need to use this module in the future, you will get the value from the exports attribute. Even if the require command is executed again, the module will not be executed again, but the value will be retrieved from the cache.
2. Loop loading of CommonJS module
An important feature of the CommonJS module is execution when loading, that is, all script code will be executed when required. CommonJS's approach is that once a module is "loop loaded", only the executed part will be output, and the unexecuted part will not be output.
Let’s take a look at the examples in the official documentation. The code of the script file a.js is as follows.
exports.done = false; var b = require('./b.js'); console.log('在 a.js 之中,b.done = %j', b.done); exports.done = true; console.log('a.js 执行完毕');
In the above code, the a.js script first outputs a done variable, and then loads another script file b.js. Note that the a.js code stops here at this time, waiting for b.js to complete execution, and then continues execution.
Look at the b.js code again.
exports.done = false; var a = require('./a.js'); console.log('在 b.js 之中,a.done = %j', a.done); exports.done = true; console.log('b.js 执行完毕');
In the above code, when b.js is executed to the second line, a.js will be loaded. At this time, "loop loading" occurs. The system will get the value of the exports attribute of the object corresponding to the a.js module. However, because a.js has not yet been executed, only the executed part can be retrieved from the exports attribute, not the final value.
The executed part of a.js has only one line.
exports.done = false;
Therefore, for b.js, it only inputs one variable done from a.js, and the value is false.
Then, b.js continues to execute. When all executions are completed, the execution right is returned to a.js. Therefore, a.js continues to execute until the execution is completed. We write a script main.js to verify this process.
var a = require('./a.js'); var b = require('./b.js'); console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
Execute main.js and the results are as follows.
$ node main.js 在 b.js 之中,a.done = false b.js 执行完毕 在 a.js 之中,b.done = true a.js 执行完毕 在 main.js 之中, a.done=true, b.done=true
The above code proves two things. First, in b.js, a.js has not been executed, only the first line has been executed. Second, when main.js is executed to the second line, b.js will not be executed again, but the cached execution result of b.js will be output, that is, its fourth line.
exports.done = true;
3. Loop loading of ES6 modules
The operating mechanism of ES6 modules is different from CommonJS. When it encounters the module loading command import, it will not execute the module, but only generate a reference. Wait until you really need to use it, then get the value in the module.
Therefore, ES6 modules are dynamic references, there is no problem of caching values, and the variables in the module are bound to the module in which they are located. Please see the example below.
// m1.js export var foo = 'bar'; setTimeout(() => foo = 'baz', 500); // m2.js import {foo} from './m1.js'; console.log(foo); setTimeout(() => console.log(foo), 500);
In the above code, the variable foo of m1.js is equal to bar when it is first loaded. After 500 milliseconds, it becomes equal to baz again.
Let’s see if m2.js can read this change correctly.
$ babel-node m2.js bar baz
The above code shows that the ES6 module does not cache the running results, but dynamically obtains the value of the loaded module, and the variable is always bound to the module in which it is located.
This causes ES6 to handle "loop loading" essentially differently from CommonJS. ES6 doesn't care at all whether "loop loading" occurs, it just generates a reference to the loaded module. The developer needs to ensure that the value can be obtained when the value is actually obtained.
Please see the following example (excerpted from "Exploring ES6" by Dr. Axel Rauschmayer).
// a.js import {bar} from './b.js'; export function foo() { bar(); console.log('执行完毕'); } foo(); // b.js import {foo} from './a.js'; export function bar() { if (Math.random() > 0.5) { foo(); } }
按照CommonJS规范,上面的代码是没法执行的。a先加载b,然后b又加载a,这时a还没有任何执行结果,所以输出结果为null,即对于b.js来说,变量foo的值等于null,后面的foo()就会报错。
但是,ES6可以执行上面的代码。
$ babel-node a.js
执行完毕
a.js之所以能够执行,原因就在于ES6加载的变量,都是动态引用其所在的模块。只要引用是存在的,代码就能执行。
我们再来看ES6模块加载器SystemJS给出的一个例子。
// even.js import { odd } from './odd' export var counter = 0; export function even(n) { counter++; return n == 0 || odd(n - 1); } // odd.js import { even } from './even'; export function odd(n) { return n != 0 && even(n - 1); }
上面代码中,even.js里面的函数foo有一个参数n,只要不等于0,就会减去1,传入加载的odd()。odd.js也会做类似操作。
运行上面这段代码,结果如下。
$ babel-node > import * as m from './even.js'; > m.even(10); true > m.counter 6 > m.even(20) true > m.counter 17
上面代码中,参数n从10变为0的过程中,foo()一共会执行6次,所以变量counter等于6。第二次调用even()时,参数n从20变为0,foo()一共会执行11次,加上前面的6次,所以变量counter等于17。
这个例子要是改写成CommonJS,就根本无法执行,会报错。
// even.js var odd = require('./odd'); var counter = 0; exports.counter = counter; exports.even = function(n) { counter++; return n == 0 || odd(n - 1); } // odd.js var even = require('./even').even; module.exports = function(n) { return n != 0 && even(n - 1); }
上面代码中,even.js加载odd.js,而odd.js又去加载even.js,形成"循环加载"。这时,执行引擎就会输出even.js已经执行的部分(不存在任何结果),所以在odd.js之中,变量even等于null,等到后面调用even(n-1)就会报错。
$ node > var m = require('./even'); > m.even(10) TypeError: even is not a function
[说明] 本文是我写的《ECMAScript 6入门》第20章《Module》中的一节。

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



Article discusses creating, publishing, and maintaining JavaScript libraries, focusing on planning, development, testing, documentation, and promotion strategies.

The article discusses strategies for optimizing JavaScript performance in browsers, focusing on reducing execution time and minimizing impact on page load speed.

Frequently Asked Questions and Solutions for Front-end Thermal Paper Ticket Printing In Front-end Development, Ticket Printing is a common requirement. However, many developers are implementing...

There is no absolute salary for Python and JavaScript developers, depending on skills and industry needs. 1. Python may be paid more in data science and machine learning. 2. JavaScript has great demand in front-end and full-stack development, and its salary is also considerable. 3. Influencing factors include experience, geographical location, company size and specific skills.

The article discusses effective JavaScript debugging using browser developer tools, focusing on setting breakpoints, using the console, and analyzing performance.

How to merge array elements with the same ID into one object in JavaScript? When processing data, we often encounter the need to have the same ID...

The article explains how to use source maps to debug minified JavaScript by mapping it back to the original code. It discusses enabling source maps, setting breakpoints, and using tools like Chrome DevTools and Webpack.

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.
