Implementation of cyclic loading for JavaScript modules _javascript skills

Source: Internet
Author: User

"Cyclic loading" (circular dependency) means that the execution of a script relies on the B script, while the execution of the B script relies on a script.

A.js
var b = require (' B ');

B.js
var a = require (' a ');

In general, "cyclic loading" means strong coupling, which, if poorly handled, can also result in recursive loading that prevents the program from executing and should therefore be avoided.

But in fact, this is very difficult to avoid, especially the complex dependencies of large projects, it is easy to rely on a b,b rely on c,c and rely on a situation. This means that the module loading mechanism must consider the case of "cyclic loading".

This article describes how the JavaScript language handles "cyclic loading." At present, the two most common module formats COMMONJS and ES6, the processing method is different, the result of the return is not the same.

The load principle of COMMONJS module

This paper introduces how ES6 to deal with "cyclic loading" before introducing the load principle of the most popular COMMONJS module format.

A module of COMMONJS is a script file. The Require command loads the script for the first time, executes the entire script, and then generates an object in memory.

{
 ID: ' ... ',
 exports: {...},
 loaded:true,
 ...
}

In the code above, the ID property of the object is the module name, the exports property is the interface of the module output, and the loaded property is a Boolean value that indicates whether the script for the module has been executed. There are many other attributes that are omitted here. (in detail, see "require () source code interpretation".) )

When you need to use this module in the future, you will go to the exports property to take the value. Even if the Require command is executed again, the module is not executed again, but the value is taken from the cache.

Second, the cyclic loading of the Commonjs module

An important feature of the COMMONJS module is loading-time execution, in which the script code executes when it is require. Commonjs's approach is that once a module is "cycled", it outputs only the part that has been executed, and the part that has not been executed will not be output.

Let's look at the example in the official document. The script file a.js the following code.

Exports.done = false;
var B = require ('./b.js ');
Console.log (' among a.js, B.done =%j ', b.done);
Exports.done = true;
Console.log (' a.js execution completed ');

In the code above, the A.js script prints a done variable first, then loads another script file b.js. Note that at this point the A.js code is parked here, waiting for B.js to execute, and then down.

Look at the B.js code again.

Exports.done = false;
var a = require ('./a.js ');
Console.log (' among b.js, A.done =%j ', a.done);
Exports.done = true;
Console.log (' b.js execution completed ');

In the code above, b.js execution to the second line, will go to load a.js, at this time, there is a "cyclic loading." The system will a.js the exports attribute of the module's corresponding object, but since A.js has not been executed, the exports property can only retrieve the part that has been executed, not the final value.

A.js has already executed the section, only one row.


Exports.done = false;


Therefore, for b.js, it enters only one variable from A.js, and the value is false.

Then, B.js goes down, waits until all is finished, then returns the execution power to a.js. The a.js then executes down until the execution is complete. We write a script main.js to verify this process.

var a = require ('./a.js ');
var B = require ('./b.js ');
Console.log (' Among main.js, A.done=%j, B.done=%j ', A.done, B.done);

Execute the Main.js, the results of the operation are as follows.

$ node Main.js

in B.js, A.done = False
b.js execution completed
in a.js, B.done = True
a.js execution completed
in Main.js, a.do Ne=true, B.done=true

The above code proves two things. First, in the B.js, the a.js did not finish, only to execute the line. Second, when Main.js executes to the second line, the b.js is not executed again, but the result of the output-cached b.js, that is, its fourth row.

Exports.done = true;

Third, the ES6 module's cyclic loading

The operating mechanism of the ES6 module is not the same as the COMMONJS, when it encounters the import of the module load command, it does not execute the module, but only generates a reference. Wait until it really needs to be used, and then go to the module to fetch the value.

Therefore, the ES6 module is a dynamic reference, there is no problem with the cached value, and the variables inside the module bind to the module in which it resides. Take a look at the example below.

M1.js
export var foo = ' Bar ';
SetTimeout (() => foo = ' Baz ');

M2.js
import {foo} from './m1.js ';
Console.log (foo);
SetTimeout (() => Console.log (foo), 500);

In the code above, M1.js's variable foo, which is equal to bar at the time of the load, has passed 500 milliseconds and becomes equal to Baz.

Let's see if m2.js can read this change correctly.

$ babel-node m2.js

Bar
Baz

The surface code indicates that the ES6 module does not cache the results of the operation, but instead dynamically takes the value of the loaded module, and the variable always binds to the module in which it resides.

This causes ES6 to deal with "cyclic loading" and commonjs are essentially different. ES6 does not care if "cyclic loading" occurs, but simply generates a reference to the loaded module, requiring the developer to ensure that the value is fetched when the value is actually fetched.

Take a look at the example below (excerpt from Dr Axel Rauschmayer's exploring ES6).

A.js
Import {bar} from './b.js ';
Export function foo () {
 bar (); 
 Console.log (' execution completed ');
}
Foo ();

B.js
import {foo} from './a.js ';
Export function Bar () { 
 if (math.random () > 0.5) {
 foo ();
 }
}

The code above cannot be executed according to the COMMONJS specification. A first loads B, then B loads a, then a has no execution results, so the output is null, that is, for b.js, the value of variable foo equals NULL, and the following foo () is an error.

However, ES6 can execute the above code.

$ Babel-node A.js

Execution completed

The reason A.js is able to execute is because the variables loaded by ES6 are dynamically referencing the module in which it resides. As long as the reference is present, the code can execute.

Let's look at an example given by the ES6 module loader 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);
}

In the code above, even.js inside the function foo has a parameter n, as long as not equal to 0, will subtract 1, in the Loaded odd (). Odd.js will do similar things.

Run the above code, and the results are as follows.

$ babel-node
> Import * as M from './even.js ';
> M.even (ten);
True
> M.counter
6
> M.even (a)
true
> M.counter
17

In the code above, when parameter n changes from 10 to 0, Foo () executes 6 times, so the variable counter equals 6. When the second call to even (), the parameter n changes from 20 to 0,foo () is performed 11 times, plus the preceding 6 times, so the variable counter equals 17.

If this example is rewritten as a commonjs, it cannot be performed at all and an error is made.

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);
}

In the code above, Even.js loads the odd.js, and odd.js loads the even.js to form a "cyclic load." At this point, the execution engine outputs the part of the even.js that has already been executed (no results), so in odd.js, the variable even equals null, and the error occurs when the subsequent call to even (n-1).

$ node
> var m = require ('./even ');
> M.even (a)
Typeerror:even is not a function

[description] This is a section of the 20th chapter, "Module", which I wrote in "ECMAScript 6".

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.