ホームページ > 記事 > ウェブフロントエンド > Node.js の JavaScript リファレンスの詳細な紹介
この記事は主にNode.jsZhongkanJavaScriptの引用の関連情報を紹介していますので、必要な方は参考にしてください
私が初期のNode.jsを学習していた頃(2011-) 2012)、そのほとんどは PHP から移されたものでしたが、当時、コードを編集した後に Node.js を再起動する必要があるという問題を指摘する人もいました (PHP ではこのプロセスは必要ありません)。プロジェクトを開始するためにノードスーパーバイザーモジュールを使用することを提唱し始めました、コードを編集した後に自動的に再起動できます。ただし、Node.js を再起動すると以前のコンテキストが失われるため、PHP に比べてまだ便利とは言えません。
再起動プロセス中のデータ損失を減らすために最初は、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;2つのJSファイルが書き込まれた後、a.jsから開始してページを更新すると、b.jsの1024が出力され、 b. たとえば、js ファイルにエクスポートされた値は 2048 に変更されます。ページを再度更新すると、元の 1024 のままになります。 require を再度実行してもコードは更新されません。 require は実行中にコードをロードした後、モジュールによってエクスポートされたデータを require.cache に配置します。 require.cache は { }
オブジェクトで、モジュールの絶対パスが key として、モジュールの詳細データが value として指定されます。そこで私は次のことを試し始めました:
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);再度要求する前に、要求時にモジュールのキャッシュをクリーンアップし、前の方法を使用して再度テストします。 b.js のコードが正常に更新され、新しく変更された値が出力されることがわかります。 この点を理解した後、この原則を使用して、再起動せずにノードスーパーバイザーのホットアップデートバージョンを実装したいと考えました。モジュールをカプセル化するプロセスでは、感情的な理由から、モジュールを導入するための require を置き換えるために、PHP の
include に似た function を提供することを検討してください。実際、require は依然として内部でロードに使用されています。 b.js を例にとると、ファイル b.js が更新された後、元の書き込みメソッドが に変更され、外部から最新のコードを取得できるように、インクルードが内部で自動的に更新されます。 var b = include(‘./b')
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);しかし、この目標に従ってインクルードをカプセル化するときに、問題が見つかりました。 include.js 内でどのように実装しても、最初のように新しい b.num を取得することはできません。 最初のコードを比較すると、b = xx が欠落していることに問題があることがわかりました。つまり、次のように記述できます: 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);これを次のように変更すると、最新のコードが毎回正しく更新され、インスタンスを再起動する必要がなくなります。興味のある読者は、このインクルードがどのように実装されているかについて学ぶことができます。この手法は広く使用されておらず、記述もあまり洗練されていないため、この記事では詳しく説明しません。代わりに、JavaScript の引用という重要な問題があります。 。
JavaScript 参照と従来の参照の違い
この問題を議論するには、まず JavaScript 参照と他の言語の違いを理解する必要があります。C++ では、参照は外部値を直接変更できます。#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; }JavaScript では:
var obj = { name: 'Alan' }; function test1(obj) { obj = { hello: 'world' }; // 试图修改外部obj } test1(obj); console.log(obj); // { name: 'Alan' } // 并没有修改① function test2(obj) { obj.name = 'world'; // 根据该对象修改其上的属性 } test2(obj); console.log(obj); // { name: 'world' } // 修改成功②上記のコード①より、C++とは異なり、JavaScriptは参照を渡すのではなく、新しいこのややぎこちない結論を理解するために、コードの一部を見てみましょう:
var obj = { name: 'Alan' }; function test1(obj) { obj = { hello: 'world' }; // 试图修改外部obj } test1(obj); console.log(obj); // { name: 'Alan' } // 并没有修改① function test2(obj) { obj.name = 'world'; // 根据该对象修改其上的属性 } test2(obj); console.log(obj); // { name: 'world' } // 修改成功②
通过这个例子我们可以看到,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))。
要实现这样的热更新必须在架构上就要严格避免旧代码被引用的可能性,否则很容易写出内存泄漏的代码。
以上がNode.js の JavaScript リファレンスの詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。