Maison  >  Article  >  interface Web  >  Méthode de mise en œuvre du chargement cyclique des modules JavaScript_javascript skills

Méthode de mise en œuvre du chargement cyclique des modules JavaScript_javascript skills

WBOY
WBOYoriginal
2016-05-16 15:26:031469parcourir

"Dépendance circulaire" signifie que l'exécution du script a dépend du script b, et l'exécution du script b dépend du script a.

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

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

Habituellement, le « chargement en boucle » indique l'existence d'un couplage fort. S'il n'est pas bien géré, cela peut également conduire à un chargement récursif, rendant le programme incapable de s'exécuter, il doit donc être évité.

Mais en fait, cela est difficile à éviter, surtout pour les grands projets avec des dépendances complexes. Il est facile pour a de dépendre de b, de b à c et de c de dépendre de a. Cela signifie que le mécanisme de chargement des modules doit prendre en compte les situations de « chargement en boucle ».

Cet article présente comment le langage JavaScript gère le « chargement en boucle ». Actuellement, les deux formats de modules les plus courants, CommonJS et ES6, ont des méthodes de traitement différentes et renvoient des résultats différents.

1. Principe de chargement du module CommonJS

Avant de présenter comment ES6 gère le "chargement en boucle", introduisons d'abord le principe de chargement du format de module CommonJS le plus populaire.

Un module de CommonJS est un fichier script. La première fois que la commande require charge le script, elle exécutera l'intégralité du script puis générera un objet en mémoire.

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

Dans le code ci-dessus, l'attribut id de l'objet est le nom du module, l'attribut exports est chaque interface sortie par le module et l'attribut chargé est une valeur booléenne, indiquant si le script du module a été exécuté. Il existe de nombreux autres attributs, mais ils sont omis ici. (Pour une introduction détaillée, veuillez vous référer à "require() Interprétation du code source".)

Lorsque vous aurez besoin d'utiliser ce module à l'avenir, vous obtiendrez la valeur de l'attribut exports. Même si la commande require est à nouveau exécutée, le module ne sera pas exécuté à nouveau, mais la valeur sera récupérée du cache.

2. Chargement en boucle du module CommonJS

Une fonctionnalité importante du module CommonJS est l'exécution lors du chargement, c'est-à-dire que tout le code du script sera exécuté lorsque cela est nécessaire. L'approche de CommonJS est qu'une fois qu'un module est « chargé en boucle », seule la partie exécutée sera sortie, et la partie non exécutée ne sera pas sortie.

Jetons un coup d'œil aux exemples dans la documentation officielle. Le code du fichier script a.js est le suivant.

exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');

Dans le code ci-dessus, le script a.js génère d'abord une variable done, puis charge un autre fichier de script b.js. Notez que le code a.js s'arrête ici à ce moment-là, en attendant que b.js termine son exécution, puis continue l'exécution.

Regardez à nouveau le code b.js.

exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');

Dans le code ci-dessus, lorsque b.js est exécuté sur la deuxième ligne, a.js sera chargé à ce moment-là, un "chargement en boucle" se produit. Le système obtiendra la valeur de l'attribut exports de l'objet correspondant au module a.js. Cependant, comme a.js n'a pas encore été exécuté, seule la partie exécutée peut être récupérée de l'attribut exports, pas la valeur finale.

La partie exécutée de a.js n'a qu'une seule ligne.


exports.done = false;


Par conséquent, pour b.js, il n’entre qu’une seule variable créée à partir de a.js et la valeur est fausse.

Ensuite, b.js continue de s'exécuter lorsque toutes les exécutions sont terminées, le droit d'exécution est renvoyé à a.js. Par conséquent, a.js continue de s'exécuter jusqu'à ce que l'exécution soit terminée. Nous écrivons un script main.js pour vérifier ce processus.

var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);

Exécutez main.js et les résultats sont les suivants.

$ 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

Le code ci-dessus prouve deux choses. Premièrement, dans b.js, a.js n'a pas été exécuté, seule la première ligne a été exécutée. Deuxièmement, lorsque main.js est exécuté sur la deuxième ligne, b.js ne sera pas exécuté à nouveau, mais le résultat de l'exécution en cache de b.js sera affiché, c'est-à-dire sa quatrième ligne.

exports.done = true;

3. Chargement en boucle des modules ES6

Le mécanisme de fonctionnement des modules ES6 est différent de CommonJS. Lorsqu'il rencontre la commande d'importation de module, il n'exécutera pas le module, mais générera uniquement une référence. Attendez d'avoir vraiment besoin de l'utiliser, puis récupérez la valeur dans le module.

Par conséquent, les modules ES6 sont des références dynamiques, il n'y a pas de problème de mise en cache des valeurs, et les variables du module sont liées au module dans lequel elles se trouvent. Veuillez consulter l'exemple ci-dessous.

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

Dans le code ci-dessus, la variable foo de m1.js est égale à bar lors de son premier chargement. Après 500 millisecondes, elle redevient égale à baz.

Voyons si m2.js peut lire correctement ce changement.

$ babel-node m2.js

bar
baz

Le code ci-dessus montre que le module ES6 ne met pas en cache les résultats en cours d'exécution, mais obtient dynamiquement la valeur du module chargé, et la variable est toujours liée au module dans lequel elle se trouve.

Cela amène ES6 à gérer le "chargement de boucle" essentiellement différemment de CommonJS. ES6 ne se soucie pas du tout de savoir si un "chargement en boucle" se produit, il génère simplement une référence au module chargé. Le développeur doit s'assurer que la valeur peut être obtenue lorsqu'elle est réellement obtenue.

Veuillez consulter l'exemple suivant (extrait de "Exploring ES6" du 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》中的一节。

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn