国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 開發(fā) > JS > 正文

Node.js中看JavaScript的引用

2024-05-06 16:36:42
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

早期學(xué)習(xí) Node.js 的時(shí)候 (2011-2012),有挺多是從 PHP 轉(zhuǎn)過(guò)來(lái)的,當(dāng)時(shí)有部分人對(duì)于 Node.js 編輯完代碼需要重啟一下表示麻煩(PHP不需要這個(gè)過(guò)程),于是社區(qū)里的朋友就開始提倡使用 node-supervisor 這個(gè)模塊來(lái)啟動(dòng)項(xiàng)目,可以編輯完代碼之后自動(dòng)重啟。不過(guò)相對(duì)于 PHP 而言依舊不夠方便,因?yàn)?Node.js 在重啟以后,之前的上下文都丟失了。

雖然可以通過(guò)將 session 數(shù)據(jù)保存在數(shù)據(jù)庫(kù)或者緩存中來(lái)減少重啟過(guò)程中的數(shù)據(jù)丟失,不過(guò)如果是在生產(chǎn)的情況下,更新代碼的重啟間隙是沒(méi)法處理請(qǐng)求的(PHP可以,另外那個(gè)時(shí)候 Node.js 還沒(méi)有 cluster)。由于這方面的問(wèn)題,加上本人是從 PHP 轉(zhuǎn)到 Node.js 的,于是從那時(shí)開始思考,有沒(méi)有辦法可以在不重啟的情況下熱更新 Node.js 的代碼。

最開始把目光瞄向了 require 這個(gè)模塊。想法很簡(jiǎn)單,因?yàn)?Node.js 中引入一個(gè)模塊都是通過(guò) require 這個(gè)方法加載的。于是就開始思考 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;

兩個(gè) JS 文件寫好之后,從 a.js 啟動(dòng),刷新頁(yè)面會(huì)輸出 b.js 中的 1024,然后修改 b.js 文件中導(dǎo)出的值,例如修改為 2048。再次刷新頁(yè)面依舊是原本的 1024。

再次執(zhí)行一次 require 并沒(méi)有刷新代碼。require 在執(zhí)行的過(guò)程中加載完代碼之后會(huì)把模塊導(dǎo)出的數(shù)據(jù)放在 require.cache 中。require.cache 是一個(gè) { } 對(duì)象,以模塊的絕對(duì)路徑為 key,該模塊的詳細(xì)數(shù)據(jù)為 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 之上關(guān)于該模塊的 cache 清理掉后,用之前的方法再次測(cè)試。結(jié)果發(fā)現(xiàn),可以成功的刷新 b.js 的代碼,輸出新修改的值。

了解到這個(gè)點(diǎn)后,就想通過(guò)該原理實(shí)現(xiàn)一個(gè)無(wú)重啟熱更新版本的 node-supervisor。在封裝模塊的過(guò)程中,出于情懷的原因,考慮提供一個(gè)類似 PHP 中 include 的函數(shù)來(lái)代替 require 去引入一個(gè)模塊。實(shí)際內(nèi)部依舊是使用 require 去加載。以b.js為例,原本的寫法改為 var b = include(‘./b'),在文件 b.js 更新之后 include 內(nèi)部可以自動(dòng)刷新,讓外面拿到最新的代碼。

但是實(shí)際的開發(fā)過(guò)程中,這樣很快就碰到了問(wèn)題。我們希望的代碼可能是這樣:

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

但按照這個(gè)目標(biāo)封裝include的時(shí)候,我們發(fā)現(xiàn)了問(wèn)題。無(wú)論我們?cè)趇nclude.js內(nèi)部中如何實(shí)現(xiàn),都不能像開始那樣拿到新的 b.num。

對(duì)比開始的代碼,我們發(fā)現(xiàn)問(wèn)題出在少了 b = xx。也就是說(shuō)這樣寫才可以:

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

修改成這樣,就可以保證每次能可以正確的刷新到最新的代碼,并且不用重啟實(shí)例了。讀者有興趣的可以研究這個(gè)include是怎么實(shí)現(xiàn)的,本文就不深入討論了,因?yàn)檫@個(gè)技巧使用度不高,寫起起來(lái)不是很優(yōu)雅[1],反而這其中有一個(gè)更重要的問(wèn)題——JavaScript的引用。

JavaScript 的引用與傳統(tǒng)引用的區(qū)別

要討論這個(gè)問(wèn)題,我們首先要了解 JavaScript 的引用于其他語(yǔ)言中的一個(gè)區(qū)別,在 C++ 中引用可以直接修改外部的值:

#include using namespace std;void test(int &p) // 引用傳遞 { p = 2048; }int main() { int a = 1024; int &p = a; // 設(shè)置引用p指向a test(p); // 調(diào)用函數(shù) 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' } // 并沒(méi)有修改①function test2(obj) { obj.name = 'world'; // 根據(jù)該對(duì)象修改其上的屬性 }test2(obj); console.log(obj); // { name: 'world' } // 修改成功②

我們發(fā)現(xiàn)與 C++ 不同,根據(jù)上面代碼 ① 可知 JavaScript 中并沒(méi)有傳遞一個(gè)引用,而是拷貝了一個(gè)新的變量,即值傳遞。根據(jù) ② 可知拷貝的這個(gè)變量是一個(gè)可以訪問(wèn)到對(duì)象屬性的“引用”(與傳統(tǒng)的 C++ 的引用不同,下文中提到的 JavaScript 的引用都是這種特別的引用)。這里需要總結(jié)一個(gè)繞口的結(jié)論:Javascript 中均是值傳遞,對(duì)象在傳遞的過(guò)程中是拷貝了一份新的引用。

為了理解這個(gè)比較拗口的結(jié)論,讓我們來(lái)看一段代碼:

var obj = { name: 'Alan' };function test1(obj) { obj = { hello: 'world' }; // 試圖修改外部obj }test1(obj); console.log(obj); // { name: 'Alan' } // 并沒(méi)有修改①function test2(obj) { obj.name = 'world'; // 根據(jù)該對(duì)象修改其上的屬性 }test2(obj); console.log(obj); // { name: 'world' } // 修改成功②

通過(guò)這個(gè)例子我們可以看到,data 雖然像一個(gè)引用一樣指向了 obj.data,并且通過(guò) data 可以訪問(wèn)到 obj.data 上的屬性。但是由于 JavaScript 值傳遞的特性直接修改 data = xxx 并不會(huì)使得 obj.data = xxx。

打個(gè)比方最初設(shè)置 var data = obj.data 的時(shí)候,內(nèi)存中的情況大概是:

|   Addr   |  內(nèi)容  | |----------|-------- | obj.data |  內(nèi)存1 |
| data | 內(nèi)存1 |

所以通過(guò) data.xx 可以修改 obj.data 的內(nèi)存1。

然后設(shè)置 data = xxx,由于 data 是拷貝的一個(gè)新的值,只是這個(gè)值是一個(gè)引用(指向內(nèi)存1)罷了。讓它等于另外一個(gè)對(duì)象就好比:

|   Addr   |  內(nèi)容  | |----------|-------- | obj.data |  內(nèi)存1 |
| data | 內(nèi)存2 |

讓 data 指向了新的一塊內(nèi)存2。

如果是傳統(tǒng)的引用(如上文中提到的 C++ 的引用),那么 obj.data 本身會(huì)變成新的內(nèi)存2,但 JavaScript 中均是值傳遞,對(duì)象在傳遞的過(guò)程中拷貝了一份新的引用。所以這個(gè)新拷貝的變量被改變并不影響原本的對(duì)象。

Node.js 中的 module.exports 與 exports

上述例子中的 obj.data 與 data 的關(guān)系,就是 Node.js 中的 module.exports 與 exports 之間的關(guān)系。讓我們來(lái)看看 Node.js 中 require 一個(gè)文件時(shí)的實(shí)際結(jié)構(gòu):

function require(...) { var module = { exports: {} }; ((module, exports) => { // Node.js 中文件外部其實(shí)被包了一層自執(zhí)行的函數(shù) // 這中間是你模塊內(nèi)部的代碼. function some_func() {}; exports = some_func; // 這樣賦值,exports便不再指向module.exports // 而module.exports依舊是{}  module.exports = some_func; // 這樣設(shè)置才能修改到原本的exports })(module, module.exports); return module.exports; }

所以很自然的:

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

Node.js 中的 exports 就是拷貝的一份 module.exports 的引用。通過(guò) exports 可以修改Node.js 當(dāng)前文件導(dǎo)出的屬性,但是不能修改當(dāng)前模塊本身。通過(guò) module.exports 才可以修改到其本身。表現(xiàn)上來(lái)說(shuō):

exports = 1; // 無(wú)效module.exports = 1; // 有效

這是二者表現(xiàn)上的區(qū)別,其他方面用起來(lái)都沒(méi)有差別。所以你現(xiàn)在應(yīng)該知道寫module.exports.xx = xxx; 的人其實(shí)是多寫了一個(gè)module.。

更復(fù)雜的例子

為了再練習(xí)一下,我們?cè)趤?lái)看一個(gè)比較復(fù)雜的例子:

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

按照開始的結(jié)論我們可以一步步的來(lái)看這個(gè)問(wèn)題:

var a = {n: 1};  // 引用a指向內(nèi)存1{n:1}var b = a; // 引用b => a => { n:1 }

內(nèi)部結(jié)構(gòu):

|   Addr  |     內(nèi)容     | |---------|-------------|
| a | 內(nèi)存1 {n:1} | | b | 內(nèi)存1 |

繼續(xù)往下看:

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

a 雖然是引用,但是 JavaScript 是值傳的這個(gè)引用,所以被修改不影響原本的地方。

| Addr | 內(nèi)容 | |-----------|-----------------------|
| 1) a | 內(nèi)存2({n:2}) | | 2) 內(nèi)存1.x | 內(nèi)存2({n:2}) |
| 3) b | 內(nèi)存1({n:1, x:內(nèi)存2}) |

所以最后的結(jié)果

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

總結(jié)

JavaScrip t中沒(méi)有引用傳遞,只有值傳遞。對(duì)象(引用類型)的傳遞只是拷貝一個(gè)新的引用,這個(gè)新的引用可以訪問(wèn)原本對(duì)象上的屬性,但是這個(gè)新的引用本身是放在另外一個(gè)格子上的值,直接往這個(gè)格子賦新的值,并不會(huì)影響原本的對(duì)象。本文開頭所討論的 Node.js 熱更新時(shí)碰到的也是這個(gè)問(wèn)題,區(qū)別是對(duì)象本身改變了,而原本拷貝出來(lái)的引用還指向舊的內(nèi)存,所以通過(guò)舊的引用調(diào)用不到新的方法。

Node.js 并沒(méi)有對(duì) JavaScript 施加黑魔法,其中的引用問(wèn)題依舊是 JavaScript 的內(nèi)容。如 module.exports 與 exports 這樣隱藏了一些細(xì)節(jié)容易使人誤會(huì),本質(zhì)還是 JavaScript 的問(wèn)題。

注[1]:

老實(shí)說(shuō),模塊在函數(shù)內(nèi)聲明有點(diǎn)譚浩強(qiáng)的感覺(jué)。

把 b = include(xxx) 寫在調(diào)用內(nèi)部,還可以通過(guò)設(shè)置成中間件綁定在公共地方來(lái)寫。

除了寫在調(diào)用內(nèi)部,也可以導(dǎo)出一個(gè)工廠函數(shù),每次使用時(shí) b().num 一下調(diào)用也可以。

還可以通過(guò)中間件的形式綁定在框架的公用對(duì)象上(如:ctx.b = include(xxx))。

要實(shí)現(xiàn)這樣的熱更新必須在架構(gòu)上就要嚴(yán)格避免舊代碼被引用的可能性,否則很容易寫出內(nèi)存泄漏的代碼。

以上所述是小編給大家介紹的Node.js中看JavaScript的引用,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)VeVb武林網(wǎng)網(wǎng)站的支持!


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到JavaScript/Ajax教程頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 莒南县| 启东市| 津南区| 教育| 五原县| 通河县| 阳信县| 阜康市| 清原| 乌海市| 勐海县| 乌兰察布市| 深州市| 拜城县| 嘉鱼县| 衡山县| 肇庆市| 抚顺市| 永寿县| 托克托县| 赫章县| 江阴市| 巧家县| 乌拉特前旗| 泽库县| 泰安市| 韶山市| 宁阳县| 全椒县| 东安县| 亳州市| 荣成市| 屯留县| 凉城县| 西充县| 玉龙| 常山县| 乐陵市| 乐陵市| 从江县| 沿河|