在 Node.js 中看 JavaScript 的引用

在 Node.js 中看 JavaScript 的引用

2017/05/05 · JavaScript
· NodeJS

初稿出处: lellansin   

前期学习 Node.js 的时候 (2011-2012),有挺多是从 PHP
转过来的,当时有一些人对此 Node.js
编辑完代码须要重启一下意味麻烦(PHP不须求以此历程),于是社区里的仇人就初始提倡使用
node-supervisor
这么些模块来启动项目,能够编制完代码之后自动重启。但是绝对于 PHP
而言照旧不够方便,因为 Node.js 在重启以后,之前的上下文都不见了。

虽说可以经过将 session
数据保存在数据库或者缓存中来缩小重启进程中的数据丢失,可是只倘诺在生产的场所下,更新代码的重启间隙是迫不得已处理请求的(PHP可以,此外越发时候
Node.js 还不曾 cluster)。由于那上头的标题,加上我是从 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

在 Node.js 中看 JavaScript 的引用。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的引用。

前期学习Node.js的时候,有挺多是从PHP转过来的,当时有一对人对于Node.js编辑完代码要求重启一下意味着麻烦(PHP不要求那些进程),于是社区里的情人就从头发起使用node-supervisor这么些模块来启动项目,能够编写完代码之后自动重启。不过相对于PHP而言照旧不够方便,因为Node.js在重启未来在此以前的上下文都有失了。

最初学习 Node.js 的时候 (2011-2012),有挺多是从 PHP
转过来的,当时有一对人对此 Node.js
编辑完代码须要重启一下代表麻烦(PHP不需求那几个进度),于是社区里的爱人就起来发起使用
node-supervisor
那些模块来启动项目,可以编制完代码之后自动重启。但是绝对于 PHP
而言依旧不够便利,因为 Node.js 在重启将来,之前的上下文都有失了。

【转】

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: ‘Alan’
} // 并从未改动① 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
的引用都是那种更加的引用)。这里须求计算一个绕口的结论: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: ‘鲍伯’, add: function (a,
b) { return a + b; } }; //
data是一个引用,直接赋值给它,只是让那个变量等于别的一个引用,并不会修改到obj本身
console.log(data); // { name: ‘鲍勃’, 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] } }

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)罢了。让它等于此外一个目的就好比:

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

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

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

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

即使能够透过将session数据保存在数据库或者缓存中来压缩重启进程中的数据丢失,不过如若是在生产的状态下,更新代码的重启间隙是无奈处理请求的(PHP可以,其余尤其时候还从未cluster)。由于那地点的标题,加上我是从PHP转到Node.js的,于是从当时开端商讨有没有主意可以在不重启的景色下热更新Node.js的代码。

虽说能够经过将 session
数据保存在数据库或者缓存中来缩短重启进程中的数据丢失,不过只如果在生产的情形下,更新代码的重启间隙是搓手顿脚处理请求的(PHP可以,此外越发时候
Node.js 还不曾 cluster)。由于那地点的难点,加上我是从 PHP 转到
Node.js 的,于是从这时伊始研讨,有没有艺术可以在不重启的图景下热更新
Node.js 的代码。

规行矩步的模块化规范不雷同

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.。

最起先把眼光瞄向了require这么些模块。想法很简单,因为Node.js中引入一个模块都是经过require那么些办法来加载的。于是就从头思索require能无法在创新代码之后重新require一下。尝试如下:

最开端把目光瞄向了 require 这一个模块。想法很简短,因为 Node.js
中引入一个模块都是因而 require 那几个点子加载的。于是就开头思索 require
能不可能在更新代码之后重新 require 一下。尝试如下:

模块化规范:即为 JavaScript
提供一种模块编写、模块信赖和模块运行的方案。何人让最初的 JavaScript
是那么的裸奔呢——全局变量就是它的模块化规范。

更扑朔迷离的事例

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

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}

A.js代码

a.js

require/exports 出生在野生规范当中,什么叫做野生规范?即这么些专业是
JavaScript
社区中的开发者自己起草的规则,获得了大家的确认或者大规模的施用。比如
CommonJS、英特尔、CMD 等等。import/export 则是豪门正派。TC39 制定的新的
ECMAScript 版本,即 ES6(ES2015)中涵盖进来。

总结

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))。
  5. 要兑现如此的热更新必须在架设上即将严苛防止旧代码被引述的可能,否则很简单写出内存泄漏的代码。

    1 赞 收藏
    评论

亚洲必赢官网 1

var express = require(‘express’);

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

出现的年华不一

var b = require(‘./b.js’);

b.js

require/exports 相关的正统由于野生性质,在 2010 年前后出生。英特尔、CMD
相对命比较短,到 2014
年基本上就摇头欲坠了。一伊始我们还比较喜欢在浏览器上利用那种异步小模块的加载情势,但并不是银弹。随着
Node.js 流行和 Browsersify
的兴起,运行时异步加载渐渐被打造时模块合并分块所取代。Wrapper
函数再也不需求了。 2014 年 Webpack
照旧新东西,现在早已是前者必备神器了。

var app = express();

exports.num = 1024;

Browsersify、Webpack 一开端的目的就是包裹 CommonJS 模块。

app.get(‘/’, function (req, res) {

多少个 JS 文件写好之后,从 a.js 启动,刷新页面会输出 b.js 中的
1024,然后修改 b.js 文件中导出的值,例如修改为
2048。再度刷新页面依然是原本的 1024。

CommonJS 作为 Node.js 的正统,一向沿用至今。由于 npm 上 CommonJS
的类库众多,以及 CommonJS 和 ES6 之间的距离,Node.js(这里不太标准)
无法直接包容 ES6。所以现阶段 require/exports 如故是必不可少且务必的。出自
ES6 的  import/export 相对就晚了重重。被大家所熟悉和使用也是 2015
年之后的事了。 这实在要感谢 babel(原来项目名为做 6to5,后更名为 babel)
这么些神一般的门类。由于有了 babel
将还未被宿主环境(各浏览器、Node.js)直接支持的 ES6 Module 编译为 ES5 的
CommonJS —— 也就是 require/exports 那种写法 —— Webpack 插上 babel-loader
那么些翅膀才起来高飞,大家也才可以称 ” 我在应用 ES6! “

b = require(‘./b.js’);

再一次实施三遍 require 并不曾刷新代码。require
在履行的进程中加载完代码之后会把模块导出的多少放在 require.cache
中。require.cache 是一个 { } 对象,以模块的相对路径为
key,该模块的事无巨细数据为 value。于是便开端做如下尝试:

那也就是怎么前面说 require/exports
是必备且务必的。因为实际是,如今你编写的 import/export 最后都是编译为
require/exports 来推行的。

res.send(b.num);

a.js

require/exports 和 import/export 格局不平等

});

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/exports 的用法唯有以下三种简易的写法:

app.listen(3000);

双重 require 以前,将 require 之上关于该模块的 cache
清理掉后,用事先的办法重新测试。结果发现,可以成功的基础代谢 b.js
的代码,输出新修改的值。

const fs = require(‘fs’)exports.fs = fsmodule.exports = fs

B.js代码

精晓到那个点后,就想通过该原理已毕一个无重启热更新版本的
node-supervisor。在包装模块的历程中,出于情怀的原故,考虑提供一个接近
PHP 中 include 的函数来代替 require 去引入一个模块。实际内部仍然是运用
require 去加载。以b.js为例,原本的写法改为
var b = include(‘./b'),在文书 b.js 更新之后 include
内部可以自行刷新,让外界得到新型的代码。

而 import/export 的写法就数见不鲜:

exports.num = 1024;

只是事实上的付出进程中,那样火速就遇上了难点。我们期待的代码可能是如此:

import fs from ‘fs’import {default as fs} from ‘fs’import * as fs from
‘fs’import {readFile} from ‘fs’import {readFile as read} from ‘fs’import
fs, {readFile} from ‘fs’export default fsexport const fsexport function
readFileexport {readFile, read}export * from ‘fs’

多个JS文件写好以后,从a.js启动,刷新页面会输出b.js中的1024,然后修改b.js文件中导出的值,例如修改为2048。再一次刷新页面如故是原先的1024。

web.js

require/exports 和 import/export 本质上的距离

重新实施四遍require并从未刷新代码。require在执行的经过中加载完代码之后会把模块导出的数目放在require.cache中。require.cache是一个{}对象,以模块的相对路径为key,该模块的详实数据为value。于是便初叶做如下尝试:

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

花样上看起来五花八门,但真相上:

A.js代码

但依照那个目的封装include的时候,大家发现了难点。无论大家在include.js内部中如何完毕,都不可能像开端那样获得新的
b.num。

CommonJS 依旧 ES6 Module
输出都可以看做是一个负有四个特性或者措施的目的;

var path = require(‘path’);

对照起来的代码,大家发现题目出在少了 b = xx。也就是说那样写才方可:

default 是 ES6 Module 所独有的主要性字,export default fs
输出默许的接口对象,import fs from ‘fs’ 可径直导入那一个目的;

var express = require(‘express’);

web.js

ES6 Module 中导入模块的品质或者措施是强绑定的,包蕴基础项目;而 CommonJS
则是一般的值传递或者引用传递。

var b = require(‘./b.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 须求看个例证:

var app = express();

修改成这么,就足以确保每便能得以正确的基础代谢到新型的代码,并且不要重启实例了。读者有趣味的可以商讨那么些include是怎么落到实处的,本文就不深入研究了,因为这么些技术使开销不高,写起起来不是很优雅[1],反而这中间有一个更重视的标题——JavaScript的引用。

// counter.jsexports.count=0setTimeout(function(){console.log(‘increase
count to’,++exports.count,’in counter.js after 500ms’)},500)//
commonjs.jsconst{count}=require(‘./counter’)setTimeout(function(){console.log(‘read
count after 1000ms in commonjs
is’,count)},1000)//es6.jsimport{count}from’./counter’setTimeout(function(){console.log(‘read
count after 1000ms in es6 is’,count)},1000)

app.get(‘/’, function (req, res) {

JavaScript
的引用与传统引用的分别

分级运行 commonjs.js 和 es6.js:

if (true) { // 检查文件是不是修改

要商讨那些题材,大家第一要打听 JavaScript
的引用于此外语言中的一个分别,在 C++ 中引用能够一向改动外部的值:

➜testnode commonjs.jsincrease count to1in counter.js after
500msreadcount after 1000ms in commonjs is 0➜testbabel-node
es6.jsincrease count to1in counter.js after 500msreadcount after 1000ms
in es6 is 1

flush();

#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 中:

链接:

res.send(b.num);

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
的引用都是那种专门的引用)。那里须求计算一个绕口的下结论:Javascript
中均是值传递,对象在传递的进度中是拷贝了一份新的引用。

【转】commonjs模块与es6模块的区分

function flush() {

为了了解那几个相比较生硬的下结论,让大家来看一段代码:

到近期为止,已经实习了三个月的时日了。目前在面试,在面试题里面有标题涉及到模块循环加载的文化。趁着这么些空子,将commonjs模块与es6模块之间有些着重的的差异做个小结。语法上有啥不一致就不现实说了,主要琢磨引用的界别。

delete require.cache[path.join(__dirname, ‘./b.js’)];

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' } // 修改成功②

转发请阐明出处:commonjs模块与es6模块的界别

b = require(‘./b.js’);

透过那一个事例大家得以观望,data 尽管像一个引用一样指向了
obj.data,并且经过 data 可以访问到 obj.data 上的特性。不过由于
JavaScript 值传递的特色间接改动 data = xxx 并不会使得 obj.data = xxx。

commonjs

}

打个比方最初安装 var data = obj.data 的时候,内存中的情形差不离是:

对此基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。

app.listen(3000);

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

对此复杂数据类型,属于浅拷贝。由于四个模块引用的靶子指向同一个内存空间,由此对该模块的值做修改时会影响另一个模块。

在重复require此前将require之上关于该模块的cache清理掉之后,用事先的不二法门重复测试。结果发现,可以成功的基础代谢b.js的代码,输出新修改的值。

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

当使用require命令加载某个模块时,就会运行总体模块的代码。

摸底这一个点,原本觉得通过这一个规律就足以写一个跟node-supervisor类似的模块,将起重启的部分换成通过该原理刷新就足以写一个更好的。可是在其实的支付进度中立刻就碰见了难题。在卷入模块的历程中,出于情怀的案由考虑提供一个近乎PHP中include的函数来代替require去引入一个模块。实际内部如故是接纳require去加载。以b.js为例,原本的写法就创作var
b =
include(‘./b’),在文书b.js更新之后include内部可以自行刷新,让外界获得最新的代码。

然后设置 data = xxx,由于 data
是拷贝的一个新的值,只是那么些值是一个引用(指向内存1)罢了。让它相当于此外一个目的就好比:

当使用require命令加载同一个模块时,不会再履行该模块,而是取到缓存之中的值。也就是说,commonjs模块无论加载多少次,都只会在率先次加载时运行四次,未来再加载,就赶回第五回运行的结果,除非手动清除系统缓存。

只是实际上的付出进程中,这样神速就赶上了难题。大家期待的代码可能是那样:

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

循环加载时,属于加载时举行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被”循环加载”,就只输出已经实施的局地,还未执行的局部不会输出。

Web.js代码

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

ES6模块

var include = require(‘./include’);

如果是价值观的引用(如上文中涉及的 C++ 的引用),那么 obj.data
本身会成为新的内存2,但 JavaScript
中均是值传递,对象在传递的历程中拷贝了一份新的引用。所以那些新拷贝的变量被改变并不影响原本的目的。

es6模块中的值属于【动态只读引用】。

var express = require(‘express’);

Node.js 中的 module.exports 与
exports

对此只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是着力数据类型照旧复杂数据类型。当模块碰着import命令时,就会转变一个只读引用。等到脚本真的履行时,再按照这一个只读引用,到被加载的要命模块里面去取值。

var b = include(‘./b.js’);

亚洲必赢官网 ,上述例子中的 obj.data 与 data 的涉嫌,就是 Node.js 中的 module.exports
与 exports 之间的涉及。让我们来看望 Node.js 中 require
一个文件时的骨子里社团:

对此动态来说,原始值暴发变化,import加载的值也会暴发变化。不论是骨干数据类型依然复杂数据类型。

var app = express();

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

循环加载时,ES6模块是动态引用。只要七个模块之间存在某个引用,代码就可以推行。

app.get(‘/’, function (req, res) {

之所以很当然的:

下面说了有的重大分裂。现在举一些例证来验证每一点吧

res.send(b.num);

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

commonjs

});

Node.js 中的 exports 就是拷贝的一份 module.exports 的引用。通过 exports
可以修改Node.js 当前文件导出的品质,可是无法改改当前模块本身。通过
module.exports 才足以修改到其自身。表现上来说:

对于着力数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。

app.listen(3000);

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

// b.js

不过在按照这几个目标封装include的时候,大家发现了难题。无论大家在include.js内部中哪些促成,都不可以像初步那样让获得新的b.num。

那是两岸呈现上的差别,其余位置用起来都未曾差别。所以您现在应有知道写module.exports.xx
= xxx; 的人实际上是多写了一个module.。

let count = 1

相比较起来的代码,大家发现难点出在少了b = xx。也就是说那样写才方可:

更扑朔迷离的例证

let plusCount = () => {

Web.js代码

为了再操练一下,大家在来看一个比较复杂的事例:

count++

var include = require(‘./include’);

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

}

var express = require(‘express’);

听从开端的下结论我们得以一步步的来看那几个题材:

setTimeout(() => {

var app = express();

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

console.log(‘b.js-1’, count)

app.get(‘/’, function (req, res) {

内部结构:

}, 1000)

var b = include(‘./b.js’);

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

module.exports = {

res.send(b.num);

接轨往下看:

count,

});

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

plusCount

app.listen(3000);

a 即使是援引,但是 JavaScript
是值传的这几个引用,所以被改动不影响原本的地点。

}

修改成这么就足以确保每一趟能得以正确的基础代谢到新型的代码,并且毫不重启实例了。读者有趣味的能够探究这么些include怎么落实,本文就不浓密座谈了,因为那个技术使开支不高,写起起来不是很优雅①,反而那里面有一个更着重的难点————JavaScript的引用。

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

// a.js

JavaScript的引用与观念引用的分化

之所以最终的结果

let mod = require(‘./b.js’)

要切磋那么些标题,我们首先要精通JavaScript的引用于其余语言中的一个界别,在C++中引用是一直可以修改外部的值:

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

console.log(‘a.js-1’, mod.count)

C++代码

总结

mod.plusCount()

#include 

JavaScrip
t中一向不引用传递,唯有值传递。对象(引用类型)的传递只是拷贝一个新的引用,那些新的引用可以访问原本对象上的属性,但是那一个新的引用我是身处其余一个格子上的值,直接往这几个格子赋新的值,并不会影响原本的目的。本文开头所谈论的
Node.js
热更新时遇到的也是以此题材,差别是目的自我改变了,而原先拷贝出来的引用还指向旧的内存,所以通过旧的引用调用不到新的法门。

console.log(‘a.js-2’, mod.count)

using namespace std;

Node.js 并从未对 JavaScript 施加黑魔法,其中的引用问题依旧是 JavaScript
的内容。如 module.exports 与 exports
那样暗藏了一部分细节简单使人误解,本质依然 JavaScript 的题材。

setTimeout(() => {

void test(int &p) // 引用传递

注[1]:

mod.count = 3

{

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

console.log(‘a.js-3’, mod.count)

p = 2048;

把 b = include(xxx)
写在调用内部,仍是可以够透过设置成中间件绑定在公私地点来写。

}, 2000)

}

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

node a.js

int main()

还足以由此中间件的情势绑定在框架的公用对象上(如:ctx.b =
include(xxx))。

a.js-1 1

{

要完成如此的热更新必须在架设上即将严刻防止旧代码被引述的可能,否则很不难写出内存泄漏的代码。

a.js-2 1

int a = 1024;

上述所述是小编给大家介绍的Node.js中看JavaScript的引用,希望对大家有所协助,如若大家有其余疑问请给自己留言,作者会及时回复咱们的。在此也卓殊感谢大家对剧本之家网站的支撑!

b.js-1 2  // 1秒后

int &p = a; // 设置引用p指向a

你或许感兴趣的篇章:

  • Node.js查找当前目录下文件夹实例代码
  • 行使node.js搭建简易web服务器的章程教程
  • 接纳n
    升级工具升级Node.js版本及在mac环境下的坑

a.js-3 3  // 2秒后

test(p); // 调用函数

如上代码可以看看,b模块export的count变量,是一个复制行为。在plusCount方法调用之后,a模块中的count不受影响。同时,可以在b模块中更改a模块中的值。假诺愿意可以联手代码,可以export出去一个getter。

cout << “p: ” << p << endl; // 2048

// 其余代码相同

cout << “a: ” << a << endl; // 2048

module.exports = {

return 0;

get count () {

}

return count

而在JavaScript中:

},

Javascript代码

plusCount

var obj = { name: ‘Alan’ };

}

function test1(obj) {

node a.js

obj = { hello: ‘world’ }; // 试图修改外部obj

a.js-1 1

}

a.js-2 1

test1(obj);

b.js-1 2  // 1秒后

console.log(obj); // { name: ‘Alan’ } // 并从未改动②

a.js-3 2  // 2秒后,
由于并未定义setter,因而无法对值举行设置。所以仍然回到2

function test2(obj) {

对此复杂数据类型,属于浅拷贝。由于三个模块引用的目的指向同一个内存空间,因而对该模块的值做修改时会影响另一个模块。

obj.name = ‘world’; // 根据该目的修改其上的习性

// b.js

}

let obj = {

test2(obj);

count: 1

console.log(obj); // { name: ‘world’ } // 修改成功③

}

咱俩发现与C++差距,依据②可见JavaScript中并没有传递一个引用,而是拷贝了一个新的变量,即值传递。依照③可见拷贝的那些变量是一个方可访问到目的属性的“引用”(与观念的C++的引用不同,下文中关系的JavaScript的引用都是那种特其他引用)。那里要求计算一个绕口的定论:Javascript中均是值传递,对象在传递的进程中是拷贝了一份新的引用。

let plusCount = () => {

为了驾驭那些比较生硬的结论,让我们来看一段代码:

obj.count++

Javascript代码

}

var obj = {

setTimeout(() => {

data: {}

console.log(‘b.js-1’, obj.count)

};

}, 1000)

// data 指向 obj.data

setTimeout(() => {

var data = obj.data;

console.log(‘b.js-2’, obj.count)

console.log(data === obj.data); // true–>data所操作的就是obj.data

}, 3000)

data.name = ‘Alan’;

module.exports = {

data.test = function () {

obj,

console.log(‘hi’)

plusCount

};

}

// 通过data可以一向修改到data的值

// a.js

console.log(obj) // { data: { name: ‘Alan’, test: [Function] } }

var mod = require(‘./b.js’)

data = {

console.log(‘a.js-1’, mod.obj.count)

name: ‘Bob’,

mod.plusCount()

add: function (a, b) {

console.log(‘a.js-2’, mod.obj.count)

return a + b;

setTimeout(() => {

}

mod.obj.count = 3

};

console.log(‘a.js-3’, mod.obj.count)

// data是一个引用,直接赋值给它,只是让这些变量等于此外一个引用,并不会修改到obj本身

}, 2000)

console.log(data); // { name: ‘Bob’, add: [Function] }

node a.js

console.log(obj); // { data: { name: ‘Alan’, test: [Function] } }

a.js-1 1

obj.data = {

a.js-2 2

name: ‘Bob’,

b.js-1 2

add: function (a, b) {

a.js-3 3

return a + b;

b.js-2 3

}

以上代码可以见到,对于目的的话属于浅拷贝。当执行a模块时,首先打印obj.count的值为1,然后经过plusCount方法,再度打印时为2。接着在a模块修改count的值为3,此时在b模块的值也为3。

};

3.当接纳require命令加载某个模块时,就会运行总体模块的代码。

// 而经过obj.data才能真的修改到data本身

4.当行使require命令加载同一个模块时,不会再实施该模块,而是取到缓存之中的值。也就是说,commonjs模块无论加载多少次,都只会在首先次加载时运行一遍,将来再加载,就回到第五次运行的结果,除非手动清除系统缓存。

console.log(obj); // { data: { name: ‘Bob’, add: [Function] } }

5.循环加载时,属于加载时实施。即脚本代码在require的时候,就会整整执行。一旦出现某个模块被”循环加载”,就只输出已经施行的有的,还未执行的局地不会输出。

因此这几个事例大家可以见见,data纵然像一个引用一样指向了obj.data,并且通过data可以访问到obj.data上的特性。然则出于JavaScript值传递的特色直接修改data
= xxx并不会使得obj.data = xxx。

3, 4, 5可以使用同一个事例表明

// b.js

打个比方最初安装var data = obj.data的时候,内存中的景况大概是:

exports.done = false

亚洲必赢官网 2

let a = require(‘./a.js’)

据此通过data.xx可以修改到obj.data的内存1。

console.log(‘b.js-1’, a.done)

接下来设置data =
xxx,由于data是拷贝的一个新的值,只是那么些值是一个引用(指向内存1)罢了。让它等于其余一个目的就好比:

exports.done = true

亚洲必赢官网 3

console.log(‘b.js-2’, ‘执行完结’)

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

// a.js

倘即使观念的引用(如上文中的C++的情景),那么obj.data本身会化为新的内存2,但JavaScript中均是值传递,对象在传递的长河中拷贝了一份新的引用。之所以那一个新拷贝的变量被转移并不影响原本的目的。

exports.done = false

Node.js中的module.exports与exports

let b = require(‘./b.js’)

上述例子中的obj.data与data的涉嫌,就是Node.js中的module.exports与exports之间的涉及。让大家来探望Node.js中require一个文书时候的实在协会:

console.log(‘a.js-1’, b.done)

Node.js代码

exports.done = true

function require(…) {

console.log(‘a.js-2’, ‘执行完结’)

var module = { exports: {} };

// c.js

((module, exports) => { // Node.js 中文件外部其实被包了一层自实施的函数

let a = require(‘./a.js’)

// 那当中是您模块内部的代码.

let b = require(‘./b.js’)

function some_func() {};

console.log(‘c.js-1’, ‘执行完结’, a.done, b.done)

exports = some_func;

node c.js

// 这样赋值,exports便不再指向module.exports

b.js-1 false

// 而module.exports依旧是{}

b.js-2 执行已毕

module.exports = some_func;

a.js-1 true

// 这样设置才能修改到原来的exports

a.js-2 执行落成

})(module, module.exports);

c.js-1 执行完成 true true

return module.exports;

周详说贝拉米下方方面面经过。

}

在Node.js中进行c模块。此时境遇require关键字,执行a.js中有着代码。

所以很自然的:

在a模块中exports之后,通过require引入了b模块,执行b模块的代码。

Node.js代码

在b模块中exports之后,又require引入了a模块,此时执行a模块的代码。

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

a模块只执行exports.done = false这条语句。

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

重临b模块,打印b.js-1, exports, b.js-2。b模块执行已毕。

Module.exports代码

回去a模块,接着打印a.js-1, exports, b.js-2。a模块执行完成

exports = 1; // 无效

回来c模块,接着执行require,须要引入b模块。由于在a模块中早就引入过了,所以一贯就足以输出值了。

module.exports = 1; // 有效

结束。

这是相互呈现上的分别,其余方面用起来都并未差异。所以您现在应有精晓写module.exports.xx
= xxx;的人实际上是多写了一个module.。

从上述结果和剖析进程可以看看,当蒙受require命令时,会实施相应的模块代码。当循环引用时,有可能只输出某模块代码的一部分。当引用同一个模块时,不会又一次加载,而是获取缓存。

更扑朔迷离的事例

ES6模块

为了再陶冶一下,大家在来看一个相比复杂的事例:

es6模块中的值属于【动态只读引用】。只说美赞臣(Meadjohnson)下复杂数据类型。

Js代码

对此只读来说,即不容许修改引入变量的值,import的变量是只读的,不论是中央数据类型依然复杂数据类型。当模块蒙受import命令时,就会变卦一个只读引用。等到脚本真的实施时,再根据这么些只读引用,到被加载的要命模块里面去取值。

var a = {n: 1};

对此动态来说,原始值发生变化,import加载的值也会暴发变化。不论是骨干数据类型依旧复杂数据类型。

var b = a;

// b.js

a.x = a = {n: 2};

export let counter = {

console.log(a.x);

count: 1

console.log(b.x);

}

依据伊始的结论大家可以一步步的来看那几个标题:

setTimeout(() => {

Js代码

console.log(‘b.js-1’, counter.count)

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

}, 1000)

var b = a;  // 引用b => a => { n:1 }

// a.js

Js代码

import { counter } from ‘./b.js’

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

counter = {}

a
尽管是援引,不过JavaScript是值传的那一个引用,所以被涂改不影响原本的地点。

console.log(‘a.js-1’, counter)

由此最后的结果

// Syntax Error: “counter” is read-only

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

即使不可以将counter重新赋值一个新的对象,不过足以给目的添加属性和艺术。此时不会报错。那种表现类型与重点字const的用法。

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

// a.js

总结

import { counter } from ‘./b.js’

Javascript中没有引用传递,唯有值传递。对象(引用类型)的传递只是拷贝一个新的引用,这么些新的引用可以访问原本对象上的质量,不过那么些新的引用我是位于其余一个格子上的值,直接往那几个格子赋新的值,并不会潜移默化原本的目的。本文开头所研究的Node.js热更新时遇见的也是以此题材,分歧是目标自我改变了,而原本拷贝出来的引用还指向旧的内存。

counter.count++

Node.js并不曾对JavaScript施加黑魔法,其中的引用难题照旧是JavaScript的始末。如module.exports与exports这样暗藏了一部分细节不难使人误解,本质依旧JavaScript的标题。此外推荐一个关于
Node.js 的进阶教程 《Node.js 面试》。

console.log(counter)

越多雅观内容请关切微信公众号:visoon_weixin

// 2

循环加载时,ES6模块是动态引用。只要几个模块之间存在某个引用,代码就可见履行。

// b.js

import {foo} from ‘./a.js’;

export function bar() {

console.log(‘bar’);

if (Math.random() > 0.5) {

foo();

}

}

// a.js

import {bar} from ‘./b.js’;

export function foo() {

console.log(‘foo’);

bar();

console.log(‘执行达成’);

}

foo();

babel-node a.js

foo

bar

实施完成

// 执行结果也有可能是

foo

bar

foo

bar

推行完结

履行落成

是因为在四个模块之间都留存引用。因而可以健康实施。

如上以上。对es6 module和commonjs
module有不打听的同班可以参考一下之下的小说

ES6
module

module的语法

module的加载完结

来源:

网站地图xml地图