>  기사  >  웹 프론트엔드  >  Node.js의 JavaScript 참조에 대한 자세한 소개

Node.js의 JavaScript 참조에 대한 자세한 소개

黄舟
黄舟원래의
2017-04-24 09:14:501036검색

본 글에서는 JavaScriptNode.js에서 인용 관련 내용을 주로 소개하고 있으니 필요하신 분들은 참고하시면 됩니다. 다음

Node.js를 처음 배울 때(2011~2012), 많은 사람들이 PHP에서 전환했는데, 그 당시 어떤 사람들은 편집 후 Node.js를 다시 시작해야 한다고 불편을 토로했습니다. (PHP에는 이 프로세스가 필요하지 않음) 커뮤니티의 친구들은 코드 편집 후 자동으로 다시 시작할 수 있는 프로젝트 시작을 위해 node-supervisor 모듈을 사용하는 것을 옹호하기 시작했습니다. 그러나 Node.js를 다시 시작한 후에는 이전 컨텍스트가 손실되기 때문에 여전히 PHP에 비해 편리하지 않습니다.

세션 데이터를 데이터베이스나 캐시에 저장하면 재시작 프로세스 중 데이터 손실을 줄일 수 있지만, 프로덕션 중인 경우 재시작 간격을 업데이트할 방법이 없습니다. 코드 처리 요청(PHP는 이를 수행할 수 있으며 당시 Node.js에는 클러스터가 없었습니다). 이 문제와 PHP에서 Node.js로 전환했다는 사실로 인해 다시 시작하지 않고 Node.js 코드를 핫 업데이트할 수 있는 방법이 있는지 생각하기 시작했습니다.

처음에는 require 모듈에 중점을 두었습니다. 아이디어는 간단합니다. Node.js에 도입된 모든 모듈은 require 메소드를 통해 로드되기 때문입니다. 그래서 코드를 업데이트한 후에 require를 다시 요구할 수 있을지 고민하기 시작했습니다. 다음을 시도해 보세요.

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;

두 개의 JS 파일이 작성된 후, a.js가 시작되면 페이지를 새로 고치면 b.js에 1024가 출력되고 b.js 파일에 내보낸 값은 예를 들어 2048로 수정됩니다. 페이지를 다시 새로 고치면 여전히 원래 1024가 됩니다.

require를 다시 실행해도 코드가 새로 고쳐지지 않습니다. require가 실행 중에 코드를 로드한 후 모듈에서 내보낸 데이터를 require.cache에 배치합니다. require.cache는 { } 객체이며 모듈의 절대 경로는 key이고 모듈의 세부 데이터는 값입니다. 그래서 다음과 같은 방법을 시도하기 시작했습니다.

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의 코드가 성공적으로 새로 고쳐지고 새로 수정된 값이 출력되는 것으로 나타났습니다.

이 점을 이해한 후, 이 원칙을 활용하여 다시 시작하지 않고도 node-supervisor의 핫 업데이트 버전을 구현하고 싶었습니다. 모듈을 캡슐화하는 과정에서 감상적인 이유로 모듈 도입 요구를 대체하기 위해 PHP의 include와 유사한 함수를 제공하는 것을 고려해보세요. 실제로 require는 여전히 내부적으로 로드에 사용됩니다. b.js를 예로 들면, 원래의 작성 방식이 var b = include(‘./b')로 변경됩니다. b.js 파일이 업데이트된 후 내부적으로 포함이 자동으로 새로 고쳐져 외부 세계에서 최신 코드를 얻을 수 있습니다.

그러나 실제 개발 과정에서 문제가 빠르게 발생했다. 우리가 바라는 코드는 다음과 같습니다:

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

그러나 이 목표에 따라 include를 캡슐화할 때 문제를 발견했습니다. 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);

최신 코드를 매번 올바르게 새로 고칠 수 있도록 이렇게 수정하면 됩니다. 인스턴스를 다시 시작합니다. 관심 있는 독자는 이 기술이 널리 사용되지 않고 [1]을 작성하는 것이 그리 우아하지 않기 때문에 이 기사에서는 이에 대해 깊이 논의하지 않을 것입니다. 바로 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: &#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; } // 修改成功②

C++와 달리 위의 코드에 따르면 다음을 볼 수 있습니다. JavaScript에서는 참조가 전달되지 않지만 새로운 변수 , 즉 값 전송을 복사합니다. ②에 따르면, 복사된 변수는 객체 속성에 접근할 수 있는 '참조'임을 알 수 있다(전통적인 C++ 참조와 달리, 아래에 언급된 JavaScript 참조는 모두 이러한 특수 참조이다). 여기서 복잡한 결론을 도출해야 합니다. 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; } // 修改成功②

通过这个例子我们可以看到,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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.