返回顶部
首页 > 资讯 > 前端开发 > node.js >Node.js中看JavaScript的引用
  • 956
分享到

Node.js中看JavaScript的引用

中看Nodejs 2022-06-04 17:06:33 956人浏览 泡泡鱼
摘要

早期学习 node.js 的时候 (2011-2012),有挺多是从 PHP 转过来的,当时有部分人对于 node.js 编辑完代码需要重启一下表示麻烦(php不需要这个过程),于是社区里的朋友就开始提倡使

早期学习 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);

b.js


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

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

但按照这个目标封装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);

修改成这样,就可以保证每次能可以正确的刷新到最新的代码,并且不用重启实例了。读者有兴趣的可以研究这个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 << "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 中并没有传递一个引用,而是拷贝了一个新的变量,即值传递。根据 ② 可知拷贝的这个变量是一个可以访问到对象属性的“引用”(与传统的 C++ 的引用不同,下文中提到的 JavaScript 的引用都是这种特别的引用)。这里需要总结一个绕口的结论: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的引用,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程网网站的支持!

--结束END--

本文标题: Node.js中看JavaScript的引用

本文链接: https://lsjlt.com/news/12645.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Node.js中看JavaScript的引用
    早期学习 Node.js 的时候 (2011-2012),有挺多是从 PHP 转过来的,当时有部分人对于 Node.js 编辑完代码需要重启一下表示麻烦(PHP不需要这个过程),于是社区里的朋友就开始提倡使...
    99+
    2022-06-04
    中看 Node js
  • Node.js中怎么引用JavaScript
    Node.js中怎么引用JavaScript,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。a.jsvar express&nbs...
    99+
    2024-04-02
  • Node.js 全局对象:深入理解 JavaScript 的引擎
    全局对象的属性 全局对象包含以下重要的属性: process: 提供有关当前进程的信息和方法,例如进程 ID、命令行参数和内存使用情况。 console: 提供了一个用于在控制台上输出消息的接口,包括 console.log() 和 c...
    99+
    2024-04-02
  • JavaScript中的弱引用和强引用是什么
    本篇内容介绍了“JavaScript中的弱引用和强引用是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!...
    99+
    2024-04-02
  • node.js中引擎指的是什么
    这篇文章主要为大家展示了“node.js中引擎指的是什么”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“node.js中引擎指的是什么”这篇文章吧。 ...
    99+
    2024-04-02
  • Node.js V8 引擎与 WebAssembly:探索 JavaScript 的新兴前沿
    Node.js 是一个基于 JavaScript 的运行时环境,而 V8 是 Chrome 浏览器中用于执行 JavaScript 代码的高性能引擎。WebAssembly 是一种二进制指令格式,旨在在 Web 浏览器和诸如 Node.j...
    99+
    2024-04-02
  • Node.js V8 引擎在机器学习中的应用:赋能 JavaScript 驱动的数据科学
    1. 高性能计算和优化 V8 引擎利用高性能编译技术,将 JavaScript 代码编译为机器码,极大地提高了执行速度。这种优化可以显著提升机器学习模型训练和推理过程的效率,缩短算法运行时间。 2. 动态编译和解释 V8 引擎采用动态编译...
    99+
    2024-04-02
  • 掌握 Node.js V8 引擎:提升你的 JavaScript 开发技能
    Node.js V8 引擎是 JavaScript 运行时环境的核心,它使 Node.js 能够有效高速地执行 JavaScript 代码。掌握 V8 引擎可以显著提升 JavaScript 开发人员的技能,从而开发出高性能和可伸缩的应用...
    99+
    2024-04-02
  • Node.js中JavaScript操作MySQL的常用方法整理
    一、建立数据库连接:createConnection(Object)方法 该方法接受一个对象作为参数,该对象有四个常用的属性host,user,password,database。与php中链接...
    99+
    2022-06-04
    常用 操作 方法
  • JavaScript中怎么引用对象的途径
    JavaScript中怎么引用对象的途径,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。JavaScript引用对象的途径(转)[@more@]  一个对象要真正...
    99+
    2023-06-03
  • JavaScript中怎么实现值引用和地址引用
    这篇文章将为大家详细讲解有关JavaScript中怎么实现值引用和地址引用,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。js中任何事务都是对象。包括基本数据...
    99+
    2024-04-02
  • javascript中的引擎是什么
    本篇内容主要讲解“javascript中的引擎是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“javascript中的引擎是什么”吧! ...
    99+
    2024-04-02
  • Node.js项目中调用JavaScript的EJS模板库的方法
    作为外部模块,调用的方法和mysql模块是相同的,不再赘述。 ejs的render函数有两个参数 第一个是字符串,第二个是可选的对象,和其他javascript模版一样需要渲染的数据也是包含在option对...
    99+
    2022-06-04
    模板 方法 项目
  • 怎么引用JavaScript
    这篇文章主要介绍“怎么引用JavaScript”,在日常操作中,相信很多人在怎么引用JavaScript问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么引用JavaScript”的疑惑有所帮助!接下来,请跟...
    99+
    2023-06-27
  • 方法引用 javascript
    方法引用 JavaScript在 JavaScript 中,方法引用是一种重要的概念,可以帮助我们简化代码并更加灵活地编写程序。下面将详细介绍方法引用及其在程序设计中的应用。方法引用的定义方法引用是指使用一个函数的引用来调用另一个函数。在 ...
    99+
    2023-05-14
  • Node.js V8 引擎的未来之路:下一代 JavaScript 运行时的愿景
    持续性能优化 性能一直是 V8 引擎的核心关注点。未来,V8 将继续专注于优化性能,提高执行速度和减少内存占用。这包括改进编译器、垃圾回收器和其他关键组件。 更好的并发支持 随着 Web 应用程序变得越来越复杂,对并发支持的需求也越来越高...
    99+
    2024-04-02
  • Node.js V8 引擎的进化之旅:从诞生到主宰 JavaScript 领域
    诞生:源自谷歌的雄心壮志 2005 年,谷歌为了提升其搜索应用程序的速度和响应能力,启动了 V8 引擎项目。目标是创建一个高效、快速的 JavaScript 解释器,能够处理大型、动态的网络应用程序。 早期发展:证明概念和性能优化 V8 ...
    99+
    2024-04-02
  • Node.js V8 引擎与并发:揭示多线程 JavaScript 编程的奥秘
    Node.js 采用 V8 JavaScript 引擎,该引擎通过提供并发执行来实现 JavaScript 的高性能。在 V8 架构中,事件循环和线程池相互协作,以同时处理多个请求,从而最大限度地提高资源利用率。 事件循环 事件循环是一个...
    99+
    2024-04-02
  • Node.js V8 引擎的最佳实践:提升 JavaScript 应用程序的可靠性和效率
    使用Buffer代替String来存储二进制数据。 避免创建不必要的对象和数组。 优化数据结构和算法以减少内存消耗。 提升线程性能 使用worker_threads模块将计算密集型任务分发到多个线程。 优化线程通信以最小化开销。 避...
    99+
    2024-04-02
  • Node.js V8 引擎的性能优化指南:释放 JavaScript 应用程序的全部潜力
    1. 启用严格模式 启用严格模式可防止意外的全局变量创建,并消除对未声明变量的隐式引用,从而提高代码质量和安全性。 2. 避免使用 eval() eval() 函数会在运行时求值字符串,这会导致性能问题。应尽可能避免使用它,转而使用 Fu...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作