Home > Article > Web Front-end > A detailed introduction to JavaScript references in Node.js
This article mainly introduces the relevant information of citation in Node.js. Friends who need it can refer to it. When I was learning Node.js in the early days (2011-2012), a lot of people switched from PHP. At that time, some people expressed trouble with the need to restart Node.js after editing the code (PHP does not require This process), so friends in the community began to advocate using the node-supervisor module to
start the project, which can automatically restart after editing the code. However, it is still not convenient compared to PHP, because after Node.js is restarted, the previous context is lost. Although you can reduce data loss during the restart process by saving
sessiondata in the database or cache, but if it is in production, there is no way to update the restart gap of the code. Processing requests (PHP can do it, and Node.js did not have clusters at that time). Due to this problem and the fact that I switched from PHP to Node.js, I started thinking about whether there is a way to hot update the Node.js code without restarting. At first, I focused on the module
require. The idea is simple, because every module introduced in Node.js is loaded through the require method. So I started thinking about whether require can be required again after updating the code. Try the following:
a.jsvar 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);
exports.num = 1024;
After the two JS files are written, from When a.js is started, refreshing the page will output 1024 in b.js, and then modify the value exported in the b.js file, for example, to 2048. Refresh the page again and it will still be the original 1024.
Executing require again does not refresh the code. After require loads the code during execution, it will place the data exported by the module in require.cache. require.cache is a { }
object, with the absolute path of the module as key, and the detailed data of the module as value. So I started to try the following:
a.jsvar 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);
Before requiring again, clean up the cache of the module on require, and use the previous method. Test again. It turns out that the code of b.js can be successfully refreshed and the newly modified value is output.
After understanding this point, I wanted to use this principle to implement a hot update version of node-supervisor without restarting. In the process of encapsulating the module, for sentimental reasons, consider providing a
function similar to include in PHP instead of require to introduce a module. In fact, require is still used internally to load. Taking b.js as an example, the original writing method is changed to var b = include('./b'). After the file b.js is updated, the include can be automatically refreshed internally, so that the outside world can get the latest code. . But during the actual development process, we quickly encountered problems. The code we hope may be like this:
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);
But when encapsulating include according to this goal, we found a problem. No matter how we implement it inside include.js, we can't get the new b.num like we did at the beginning.
Comparing the initial code, we found that the problem lies in the missing b = xx. In other words, it can be written like this:
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);
Modify it like this to ensure that the latest code can be correctly refreshed every time, and there is no need to restart the instance. Readers who are interested can study how this include is implemented. This article will not discuss it in depth because this technique is not widely used and it is not very elegant to write [1]. Instead, there is a more important issue - JavaScript citation.
The difference between JavaScript references and traditional referencesTo discuss this issue, we must first understand that JavaScript references are in one of other languages The difference is that in C++ a reference can directly modify the external value:
#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; }
and in 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' } // 修改成功②
We find that unlike C++, according to the above code ① we can see that a reference is not passed in JavaScript , but a new
variableis copied, that is, passed by value. According to ②, it can be seen that the copied variable is a "reference" that can access the object properties (different from traditional C++ references, the JavaScript references mentioned below are all such special references). A convoluted conclusion needs to be drawn here: Javascript is all passed by value, and a new reference is copied to the object during the transfer process. In order to understand this rather awkward conclusion, let us look at a piece of code:
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))。
要实现这样的热更新必须在架构上就要严格避免旧代码被引用的可能性,否则很容易写出内存泄漏的代码。
The above is the detailed content of A detailed introduction to JavaScript references in Node.js. For more information, please follow other related articles on the PHP Chinese website!