Maison  >  Article  >  interface Web  >  Une introduction détaillée aux références JavaScript dans Node.js

Une introduction détaillée aux références JavaScript dans Node.js

黄舟
黄舟original
2017-04-24 09:14:501056parcourir

Cet article présente principalement les informations pertinentes sur la citation de dans Node.js dans JavaScript. . Suivant

Lorsque j'apprenais Node.js au début (2011-2012), de nombreuses personnes ont quitté PHP. À cette époque, certaines personnes se sentaient gênées par la nécessité de redémarrer Node.js après avoir modifié le fichier. code (PHP ne nécessite pas ce processus), alors les amis de la communauté ont commencé à préconiser l'utilisation du module node-supervisor pour démarrer le projet, qui peut redémarrer automatiquement après avoir modifié le code. Cependant, ce n'est toujours pas pratique par rapport à PHP, car après le redémarrage de Node.js, le contexte précédent est perdu.

Bien que la perte de données pendant le processus de redémarrage puisse être réduite en enregistrant les données de session dans la base de données ou le cache, mais si elles sont en production, il n'y a aucun moyen de mettre à jour l'écart de redémarrage du code. Traitement des requêtes (PHP peut le faire, et Node.js n'avait pas de clusters à cette époque). En raison de ce problème et du fait que je suis passé de PHP à Node.js, j'ai commencé à me demander s'il existait un moyen de mettre à jour à chaud le code Node.js sans redémarrer.

Au début, je me suis concentré sur le module require. L'idée est simple, car chaque module introduit dans Node.js est chargé via la méthode require. J'ai donc commencé à réfléchir à la question de savoir si require pouvait être à nouveau requis après la mise à jour du code. Essayez ce qui suit :

a.js

var express = require('express');
var b = require('./b.js'); 
var app = express();
app.get('/', function (req, res) {
 b = require('./b.js');
 res.send(b.num);
 });
app.listen(3000);

b.js

exports.num = 1024;

Deux fichiers JS écrits Après cela, partez de a.js, actualisez la page et le 1024 dans b.js sera affiché, puis modifiez la valeur exportée dans le fichier b.js, par exemple en 2048. Actualisez à nouveau la page et ce sera toujours le 1024 d'origine.

Exécuter à nouveau require n'actualise pas le code. Après avoir chargé le code lors de l'exécution, il placera les données exportées par le module dans require.cache. require.cache est un { } objet, avec le chemin absolu du module comme clé, et les données détaillées du module comme valeur. J'ai donc commencé à essayer ce qui suit :

a.js

var path = require('path');
var express = require('express');
var b = require('./b.js'); 
var app = express();
app.get('/', function (req, res) {
 if (true) { // 检查文件是否修改
 flush();
 }
 res.send(b.num);
 });
function flush() {
 delete require.cache[path.join(dirname, './b.js')];
 b = require('./b.js');
 }
app.listen(3000);

Avant de redemander, nettoyez le cache du module sur requérir, et utilisez-le avant La méthode a été testée à nouveau. Il s'avère que le code de b.js peut être actualisé avec succès et que la valeur nouvellement modifiée est affichée.

Après avoir compris ce point, j'ai voulu utiliser ce principe pour implémenter une version de mise à jour à chaud de node-supervisor sans redémarrer. Dans le processus d'encapsulation du module, pour des raisons sentimentales, pensez à fournir une fonction similaire à include en PHP pour remplacer require pour introduire un module. En fait, require est toujours utilisé en interne pour charger. En prenant b.js comme exemple, la méthode d'écriture d'origine est modifiée en var b = include(‘./b') Une fois le fichier b.js mis à jour, l'inclusion peut être automatiquement actualisée en interne, afin que le monde extérieur puisse obtenir le dernier code.

Cependant, au cours du processus de développement proprement dit, des problèmes ont été rapidement rencontrés. Le code que nous espérons ressemblera peut-être à ceci :

web.js

var include = require('./include');
var express = require('express');
var b = include('./b.js');
var app = express(); 
app.get('/', function (req, res) {
 res.send(b.num);
 });
app.listen(3000);

Mais lors de l'encapsulation de l'inclusion selon cet objectif, nous avons trouvé un problème. Quelle que soit la manière dont nous l'implémentons dans include.js, nous ne pouvons pas obtenir le nouveau b.num comme nous l'avons fait au début.

En comparant le code initial, nous constatons que le problème réside dans le b = xx manquant. En d'autres termes, cela peut être écrit comme ceci :

web.js

var include = require('./include');
var express = require('express');
var app = express(); 
app.get('/', function (req, res) {
 var b = include('./b.js');
 res.send(b.num);
 });
app.listen(3000);

Modifiez-le comme ceci pour vous assurer que le dernier code peut être correctement actualisé à chaque fois, et qu'il n'y a pas besoin de redémarrer l'instance. Les lecteurs intéressés peuvent étudier comment cette inclusion est implémentée. Cet article n'en discutera pas en profondeur car cette technique n'est pas largement utilisée et il n'est pas très élégant à écrire [1]. .

La différence entre les références JavaScript et les références traditionnelles

Pour discuter de cette question, nous devons d'abord comprendre comment les références JavaScript sont utilisées dans d'autres langages . La différence est qu'en C++, les références peuvent modifier directement les valeurs externes :

#include 
using namespace std;
void test(int &p) // 引用传递 {
 p = 2048;
 }
int main() {
 int a = 1024;
 int &p = a; // 设置引用p指向a
 test(p); // 调用函数
 cout << "p: " << p << endl; // 2048
 cout << "a: " << a << endl; // 2048
 return 0;
 }

, tandis qu'en JavaScript :

var obj = { name: &#39;Alan&#39; };
function test1(obj) {
 obj = { hello: &#39;world&#39; }; // 试图修改外部obj
 }
test1(obj);
 console.log(obj); // { name: &#39;Alan&#39; } // 并没有修改①
function test2(obj) {
 obj.name = &#39;world&#39;; // 根据该对象修改其上的属性
 }
test2(obj);
 console.log(obj); // { name: &#39;world&#39; } // 修改成功②

On constate que contrairement au C++, d'après le code ci-dessus ①. nous pouvons voir qu'en JavaScript Au lieu de passer une référence, une nouvelle variable est copiée, c'est-à-dire une transmission de valeur. Selon ②, on peut voir que la variable copiée est une "référence" qui peut accéder aux propriétés de l'objet (différentes des références C++ traditionnelles, les références JavaScript mentionnées ci-dessous sont toutes de telles références spéciales). Une conclusion alambiquée doit être tirée ici : Javascript est entièrement transmis par valeur et une nouvelle référence est copiée sur l'objet pendant le processus de transfert.

Afin de comprendre cette conclusion plutôt gênante, regardons un morceau de code :

var obj = { name: &#39;Alan&#39; };
function test1(obj) {
 obj = { hello: &#39;world&#39; }; // 试图修改外部obj
 }
test1(obj);
 console.log(obj); // { name: &#39;Alan&#39; } // 并没有修改①
function test2(obj) {
 obj.name = &#39;world&#39;; // 根据该对象修改其上的属性
 }
test2(obj);
 console.log(obj); // { name: &#39;world&#39; } // 修改成功②

通过这个例子我们可以看到,data 虽然像一个引用一样指向了 obj.data,并且通过 data 可以访问到 obj.data 上的属性。但是由于 JavaScript 值传递的特性直接修改 data = xxx 并不会使得 obj.data = xxx。

打个比方最初设置 var data = obj.data 的时候,内存中的情况大概是:

|   Addr   |  内容  | 
|----------|-------- 
| obj.data |  内存1 |
| data | 内存1 |

所以通过 data.xx 可以修改 obj.data 的内存1。

然后设置 data = xxx,由于 data 是拷贝的一个新的值,只是这个值是一个引用(指向内存1)罢了。让它等于另外一个对象就好比:

|   Addr   |  内容  |
 |----------|-------- 
| obj.data |  内存1 || data | 内存2 |

让 data 指向了新的一块内存2。

如果是传统的引用(如上文中提到的 C++ 的引用),那么 obj.data 本身会变成新的内存2,但 JavaScript 中均是值传递,对象在传递的过程中拷贝了一份新的引用。所以这个新拷贝的变量被改变并不影响原本的对象。

Node.js 中的 module.exports 与 exports

上述例子中的 obj.data 与 data 的关系,就是 Node.js 中的 module.exports 与 exports 之间的关系。让我们来看看 Node.js 中 require 一个文件时的实际结构:

function require(...) {
 var module = { exports: {} };
 ((module, exports) => { // Node.js 中文件外部其实被包了一层自执行的函数
 // 这中间是你模块内部的代码.
 function some_func() {};
 exports = some_func;
 // 这样赋值,exports便不再指向module.exports
 // 而module.exports依旧是{} 
 module.exports = some_func;
 // 这样设置才能修改到原本的exports
 })(module, module.exports);
 return module.exports;
 }

所以很自然的:

console.log(module.exports === exports); // true
// 所以 exports 所操作的就是 module.exports

Node.js 中的 exports 就是拷贝的一份 module.exports 的引用。通过 exports 可以修改Node.js 当前文件导出的属性,但是不能修改当前模块本身。通过 module.exports 才可以修改到其本身。表现上来说:

exports = 1; // 无效
module.exports = 1; // 有效

这是二者表现上的区别,其他方面用起来都没有差别。所以你现在应该知道写module.exports.xx = xxx; 的人其实是多写了一个module.。

更复杂的例子

为了再练习一下,我们在来看一个比较复杂的例子:

var a = {n: 1}; 
var b = a; 
a.x = a = {n: 2}; 
console.log(a.x);
console.log(b.x);

按照开始的结论我们可以一步步的来看这个问题:

var a = {n: 1};  // 引用a指向内存1{n:1}
var b = a; // 引用b => a => { n:1 }

内部结构:

|   Addr  |     内容     | |---------|-------------|
| a | 内存1 {n:1} | | b | 内存1 |

继续往下看:

a.x = a = {n: 2}; // (内存1 而不是 a ).x = 引用 a = 内存2 {n:2}

a 虽然是引用,但是 JavaScript 是值传的这个引用,所以被修改不影响原本的地方。

| Addr | 内容 | |-----------|-----------------------|
| 1) a | 内存2({n:2}) | | 2) 内存1.x | 内存2({n:2}) |
| 3) b | 内存1({n:1, x:内存2}) |

所以最后的结果

a.x 即(内存2).x ==> {n: 2}.x ==> undefined
b.x 即(内存1).x ==> 内存2 ==> {n: 2}

总结

JavaScrip t中没有引用传递,只有值传递。对象(引用类型)的传递只是拷贝一个新的引用,这个新的引用可以访问原本对象上的属性,但是这个新的引用本身是放在另外一个格子上的值,直接往这个格子赋新的值,并不会影响原本的对象。本文开头所讨论的 Node.js 热更新时碰到的也是这个问题,区别是对象本身改变了,而原本拷贝出来的引用还指向旧的内存,所以通过旧的引用调用不到新的方法。

Node.js 并没有对 JavaScript 施加黑魔法,其中的引用问题依旧是 JavaScript 的内容。如 module.exports 与 exports 这样隐藏了一些细节容易使人误会,本质还是 JavaScript 的问题。

注[1]:

老实说,模块在函数内声明有点谭浩强的感觉。

把 b = include(xxx) 写在调用内部,还可以通过设置成中间件绑定在公共地方来写。

除了写在调用内部,也可以导出一个工厂函数,每次使用时 b().num 一下调用也可以。

还可以通过中间件的形式绑定在框架的公用对象上(如:ctx.b = include(xxx))。

要实现这样的热更新必须在架构上就要严格避免旧代码被引用的可能性,否则很容易写出内存泄漏的代码。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

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