標籤:

為什麼nodejs不給每一個.js文件以獨立的上下文來避免作用域被污染?

剛入門nodejs,看到一個不錯的帖子,但是作者最後拋出這個問題,實在太菜,表示不懂,求知道的大神指點。


作用域 scope上下文 context 可不是一回事。

======

1. 什麼是上下文 context ?

上下文 context ,在不同的語境下有不同的含義。所以我們在討論問題的時候,一定要明確所謂的上下文到底是什麼上下文。

在 ECMAScript 規範里, execution context 是代碼的運行環境,包括變數和值、this binding 等等 https://www.ecma-international.org/ecma-262/7.0/#sec-execution-contexts

在 v8 的語境下,context 是指 v8::Context ,是互相隔離的 JS 運行環境 https://github.com/v8/v8/wiki/Embedder%27s-Guide#contexts

本章結論:

  1. context 有不同的含義
  2. execution context 之間不是隔離的,v8::Context 之間是隔離的。

=======

2. 「獨立的上下文」 能不能避免 「作用域被污染」?

獨立的 execution context 不能阻止 scope 被污染。

觀察如下代碼:

var fn0 = function() {
leak0 = 0;
};

var fn1 = function() {
leak1 = 1;
};

fn0();
fn1();

console.log(typeof leak0); // number
console.log(typeof leak1); // number

fn0() 與 fn1() 各自有其 execution context ,但這並不能避免它們污染全局作用域。

一個 v8::Context 實例的全局 scope 被污染了,不會影響到另一個的 v8::Context 實例。

我們先記住這個結論,例子請看下一章。

本章結論:

  1. 獨立的 execution context 不能阻止 scope 被污染。
  2. 一個 v8::Context 實例的全局 scope 被污染了,不會影響到另一個的 v8::Context 實例。

========

3. Node.js 有沒有為每一個 js 模塊文件分配單獨的 v8::Context ?

答案是... yes and no.

Node.js 默認不會給每個 js 模塊文件分配單獨的 v8::Context 。

但是在 Node.js 版本 0.1 ~ 1.5 裡面,有一個環境變數 NODE_MODULE_CONTEXTS 可以使 Node.js 為每個 js 模塊分配單獨的 v8::Context 。https://github.com/nodejs/node/blob/f19e9b6a7603c852bc9cec9516c0fe70fc8db173/lib/module.js#L394

從 1.6 開始這個環境變數被拿掉了。nodejs/node

我們先構建如下代碼:

/*
* entry.js
*/
var mod0 = require("./module-0");
var mod1 = require("./module-1");

console.log("global equals", mod0.global === mod1.global);
console.log("Number equals", mod0.Number === mod1.Number);

console.log("leak from module-0", typeof leak0);
console.log("leak from module-1", typeof leak1);

console.log("local0 from module-1", typeof local0);
console.log("local1 from module-1", typeof local1);

/*
* module-0.js
*/
leak0 = 0;

var local0 = 0;

exports.global = global;
exports.Number = Number;

/*
* module-1.js
*/
leak1 = 1;

var local1 = 1;

exports.global = global;
exports.Number = Number;

然後拿一個 Node.js 1.5 (io.js)試試:

$ node entry.js
global equals true
Number equals true
leak from module-0 number
leak from module-1 number
local0 from module-1 undefined
local1 from module-1 undefined

再次驗證了上文的其中兩個結論:

  1. 獨立的 execution context 不能阻止 scope 被污染。
  2. Node.js 默認不會給每個 js 模塊文件分配單獨的 v8::Context 。

然後,我們設置 NODE_MODULE_CONTEXTS 再測一遍

$ NODE_MODULE_CONTEXTS=1 node entry.js
global equals false
Number equals false
leak from module-0 undefined
leak from module-1 undefined
local0 from module-1 undefined
local1 from module-1 undefined

可見,如果給每個模塊 js 都分配單獨的 v8::Context ,確實它們會訪問到不同的全局作用域,一個模塊內部污染了全局作用域,不會影響另一個模塊的全局作用域。

======

4. 那麼為什麼 Node.js 不給每個 js 模塊文件分配單獨的 v8::Context 呢?

我覺得 Node.js 現在這個做法還挺自然的吧,一是確實有使用全局變數的場景存在,二是 runInNewContext() 的性能真的慢幾個數量級。

======

總結

  1. context 有不同的含義
  2. execution context 之間不是隔離的,v8::Context 之間是隔離的。
  3. 獨立的 execution context 不能阻止 scope 被污染。
  4. 一個 v8::Context 實例的全局 scope 被污染了,不會影響到另一個的 v8::Context 實例。
  5. Node.js 默認不會給每個 js 模塊文件分配單獨的 v8::Context 。在 Node.js 的早期版本里有一個環境變數能改變這個行為,後來這個環境變數也沒有了。
  6. 寫這麼長的文章真的累。

======

參考文獻:

[1] Execution Context: https://www.ecma-international.org/ecma-262/7.0/#sec-execution-contexts

[2] v8::Context: https://github.com/v8/v8/wiki/Embedder%27s-Guide#contexts

[3] NODE_MODULES_CONTEXTS: https://github.com/nodejs/node/blob/f19e9b6a7603c852bc9cec9516c0fe70fc8db173/lib/module.js#L394

[4] io.js 1.6.0 changelog: nodejs/node


NodeJs 的模塊系統,模塊之間本身就是獨立的。除了一個global對象,process,要訪問另一個模塊必須使用require。

而NodeJs緩存模塊實例,其他模塊引用它後可以對它修改,在es6以後已經有了很大的改善。

NodeJs 和客戶端的JavaScript,其實比靜態語言,更需要設計模式和編碼規範。再者,建議多寫寫代碼,編程有很多文縐縐的術語,當遇到相關的問題,踩過坑,就能水到渠成融會貫通,否則,它們就像周易,就像一些枯燥無味的法條,學習的效率會很低。


作用域被污染這個素質高的程序員被處理得非常好,就算我這個半吊子也不會出現這種低級錯誤,不能因為降低程序員犯錯的幾率而改變架構,況且也不方便了很多


推薦閱讀:

nodejs中,zlib.gzip系列純cpu計算函數為什麼會有非同步版本?
es6 import from xx , 是怎麼實現找到 node_modules目錄下的?
Node.js伺服器端項目怎麼打包成單文件?

TAG:Nodejs |