来自 前端知识 2019-11-14 17:53 的文章
当前位置: 金沙澳门官网网址 > 前端知识 > 正文

在 Node.js 中看 JavaScript 的引用

在 Node.js 中看 JavaScript 的引用

2017/05/05 · 澳门金莎娱乐手机版 ,JavaScript · NodeJS

初稿出处: lellansin   

前期学习 Node.js 的时候 (二〇一三-二零一三),有挺多是从 PHP 转过来的,这时候有局地人对此 Node.js 编辑完代码供给重启一下象征麻烦(PHP不须要这几个进度卡塔 尔(英语:State of Qatar),于是社区里的爱侣就起来发起使用 node-supervisor 那几个模块来运维项目,能够编制完代码之后自动重启。可是绝对于 PHP 来讲还是非常不足便利,因为 Node.js 在重启未来,早前的上下文皆有失了。

虽说能够经过将 session 数据保存在数据库或许缓存中来收缩重启进度中的数据遗失,然则只假如在分娩的情景下,更新代码的重启间隙是不得已管理央求的(PHP能够,此外极度时候 Node.js 还不曾 cluster卡塔 尔(英语:State of Qatar)。由于那地点的标题,加上自个儿是从 PHP 转到 Node.js 的,于是从此以往时开首斟酌,有未有艺术能够在不重启的意况下热更新 Node.js 的代码。

最最早把眼光瞄向了 require 那么些模块。主张超粗略,因为 Node.js 中引入三个模块都是透过 require 这些艺术加载的。于是就起来探究 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);

1
2
3
4
5
6
7
8
9
10
11
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;

1
exports.num = 1024;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);

再次 require 以前,将 require 之上关于该模块的 cache 清理掉后,用事先的法子重复测量试验。结果开采,能够成功的基本功代谢 b.js 的代码,输出新改良的值。

摸底到这几个点后,就想通过该原理落成三个无重启热更新版本的 node-supervisor。在卷入模块的历程中,出于情怀的缘故,思量提供四个肖似PHP 中 include 的函数来替代 require 去引进一个模块。实际内部照旧是行使 require 去加载。以b.js为例,原来的写法改为 var b = include(‘./b’),在文书 b.js 更新之后 include 内部可以自行刷新,让外部获得最新的代码。

而是事实上的付出进程中,那样快捷就碰着了难题。大家期望的代码大概是如此:

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

1
2
3
4
5
6
7
8
9
10
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
2
3
4
5
6
7
8
9
10
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);

校正成这么,就足以确认保证每便能得以精确的底工代谢到新型的代码,而且不要重启实例了。读者有意思味的能够研究那一个include是怎么贯彻的,本文就不浓厚商讨了,因为那么些技巧使耗费不高,写起起来不是很名贵[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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include
 
using namespace std;
 
void test(int &p) // 引用传递
{
    p = 2048;
}
 
int main()
{
    int a = 1024;
    int &p = a; // 设置引用p指向a
 
    test(p); // 调用函数
 
    cout

而在 JavaScript 中:

var obj = { name: 'Alan' }; function test1(obj) { obj = { hello: 'world' }; // 试图改进外界obj } test1(obj); console.log(obj); // { name: '艾伦' } // 并不曾改造① function test2(obj) { obj.name = 'world'; // 依照该对象改良其上的属性 } test2(obj); console.log(obj); // { name: 'world' } // 纠正成功②

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 中并不曾传递二个援引,而是拷贝了一个新的变量,即值传递。依照 ② 可见拷贝的这些变量是叁个方可访谈到目的属性的“引用”(与思想的 C++ 的引用分化,下文中关系的 JavaScript 的援用都以这种非常的援用卡塔 尔(英语:State of Qatar)。这里须求总计贰个绕口的下结论:Javascript 中均是值传递,对象在传递的经过中是拷贝了后生可畏份新的引用。

为了精通那一个相比刚烈的定论,让大家来看生龙活虎段代码:

var obj = { data: {} }; // data 指向 obj.data var data = obj.data; console.log(data === obj.data); // true-->data所操作的就是obj.data data.name = 'Alan'; data.test = function () { console.log('hi') }; // 通过data可以向来改造到data的值 console.log(obj) // { data: { name: 'Alan', test: [Function] } } data = { name: 'Bob', add: function (a, b) { return a + b; } }; // data是二个引用,直接赋值给它,只是让这几个变量等于此外二个援用,并不会修正到obj本身console.log(data); // { name: 'Bob', add: [Function] } console.log(obj); // { data: { name: 'Alan', test: [Function] } } obj.data = { name: '鲍勃', add: function (a, b) { return a + b; } }; // 而透过obj.data技术确实更正到data自个儿 console.log(obj); // { data: { name: 'Bob', add: [Function] } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var obj = {
  data: {}
};
 
// data 指向 obj.data
var data = obj.data;
 
console.log(data === obj.data); // true-->data所操作的就是obj.data
 
data.name = 'Alan';
data.test = function () {
  console.log('hi')
};
 
// 通过data可以直接修改到data的值
console.log(obj) // { data: { name: 'Alan', test: [Function] } }
 
data = {
  name: 'Bob',
  add: function (a, b) {
    return a + b;
  }
};
 
// data是一个引用,直接赋值给它,只是让这个变量等于另外一个引用,并不会修改到obj本身
console.log(data); // { name: 'Bob', add: [Function] }
console.log(obj); // { data: { name: 'Alan', test: [Function] } }
 
obj.data = {
  name: 'Bob',
  add: function (a, b) {
    return a + b;
  }
};
 
// 而通过obj.data才能真正修改到data本身
console.log(obj); // { data: { name: 'Bob', add: [Function] } }

经过那么些例子大家得以看见,data 就算像一个引用同样指向了 obj.data,何况经过 data 可以访问到 obj.data 上的质量。但是出于 JavaScript 值传递的性状直接改换 data = xxx 并不会使得 obj.data = xxx。

打个纵然最初安装 var data = obj.data 的时候,内存中的情形大概是:

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

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

故而通过 data.xx 能够改进 obj.data 的内部存款和储蓄器1。

然后设置 data = xxx,由于 data 是拷贝的叁个新的值,只是那个值是三个引用(指向内部存储器1卡塔 尔(英语:State of Qatar)罢了。让它也就是此外三个对象就好比:

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

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

让 data 指向了新的一块内部存款和储蓄器2。

只若是守旧的引用(如上文中提到的 C++ 的援用卡塔 尔(英语:State of Qatar),那么 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; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

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

Node.js 中的 exports 正是拷贝的生机勃勃份 module.exports 的援引。通过 exports 能够改进Node.js 当前文件导出的品质,不过不可能修改当前模块本人。通过 module.exports 才方可改良到其本身。表现上来讲:

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

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

1
2
3
4
5
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 }

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

内部结构:

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

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

世袭往下看:

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

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

1
2
3
4
5
|    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 的主题材料。此外推荐一个关于 Node.js 的进级教程 《Node.js 面试》。

注[1]:

  1. 君子固穷说,模块在函数内证明有一点谭浩强的感觉。
  2. 把 b = include(xxx) 写在调用内部,仍可以经过设置成人中学间件绑定在集体地点来写。
  3. 除了那么些之外写在调用内部,也足以导出三个厂子函数,每一遍使用时 b().num 一下调用也得以。
  4. 还是能透过中间件的款式绑定在框架的公用对象上(如:ctx.b = include(xxx)卡塔 尔(英语:State of Qatar)。
  5. 要得以实现如此的热更新必得在架设上就要执法必严防止旧代码被引用的可能性,不然超轻易写出内部存款和储蓄器泄漏的代码。

    1 赞 收藏 评论

澳门金莎娱乐手机版 1

本文由金沙澳门官网网址发布于前端知识,转载请注明出处:在 Node.js 中看 JavaScript 的引用

关键词: