2013-08-19 65 views
2

我已經得到了一些功能,允許在該模塊包含的功能很多的合併命名空間,非常類似於import(我暴露的API與幾十個組合子) 它會產生大量的var f = target.f;爲每命名空間進口

var paco = require('./paco.js'); 

eval(paco.getNamespace('paco', paco)); 

// instead of paco.between(paco.start(),paco.content(),paco.end()) 
between(start(), content(), end()) 

問題

從出口

function getNamespace(name, exports){ 
    var output=''; 
    for(var item in exports){ 
     output += 'var ' + item + ' = '+name+ '.'+item + ';'; 
    } 
    return output; 
} 

和使用項目

我有一種方法可以將eval隱藏到某些函數中嗎?我不希望既不改變全局命名空間,也不想調用vm.runInThisContext,只需要在類似於require的調用函數後添加一些局部變量到調用上下文中。

我的意思是我需要這樣的東西

import('./paco'); 
// this should work like this 
// var paco = require('./paco.js');  
// var between = paco.between; 

但沒有全球性的突變和無EVAL在呼叫範圍內。

回答

3

TL;博士:

號爲了理解爲什麼這是不可能的,它明白了什麼節點是做幕後是很重要的。

比方說,我們在定義一個test.js功能:

function foo() { 
    var msg = 'Hello world'; 
    console.log(msg); 
} 

在傳統瀏覽器的JavaScript,乾脆把在一個文件函數聲明,並用<script>標籤拉動文件中會導致foo被宣佈在全球範圍內。

當文件require()文件時,節點以不同的方式做事。

  1. 首先,它根據a somewhat complex set of rules確定究竟應該加載哪個文件。

  2. 假設文件是​​JS文本(非編譯的C++插件),節點的模塊加載callsfs.readFileSync獲取文件的內容。

  3. 源文本是wrapped在匿名函數中。 test.js最終會實際上是在尋找這樣的:

    (function (exports, require, module, __filename, __dirname) { 
    function foo() { 
        var msg = 'Hello world'; 
        console.log(msg); 
    } 
    }); 
    

    這應該很熟悉誰曾經包裹在一個匿名函數表達他們自己的代碼,以保持變量泄漏到全局範圍在瀏覽器中的任何人。它也應該開始理解Node中的「魔術」變量是如何工作的。

  4. 模塊加載來自步驟3 eval小號源文本,然後調用所得的匿名函數,傳遞在新鮮exports對象。 (見Module#_compile。)

    1 - 真的vm.runInThisContext,這就好比eval除了它沒有訪問調用者的範圍

  5. 匿名包裝函數返回後,module.exports值在內部緩存,然後由require返回。 (以require()後續調用返回的緩存值)。

正如我們所看到的,節點通過簡單地在一個匿名函數包裹文件的源代碼,實現了「模塊」。因此,將函數「導入」模塊是不可能的,因爲JavaScript不能直接訪問功能–的execution context,即函數本地變量的集合。

換句話說,我們無法遍歷函數的局部變量,也沒有辦法讓我們用任意名稱創建局部變量,就像我們可以使用對象的屬性一樣。

例如,使用對象,我們可以做這樣的事情:

var obj = { key: 'value' }; 
for (var k in obj) ... 
obj[propertyNameDeterminedAtRuntime] = someValue; 

但有代表的功能,這將是需要我們複製一個對象的屬性的局部變量沒有對象(如一個模塊的exports)納入函數的本地範圍。

您所做的是使用eval在當前範圍內生成代碼。生成的代碼使用關鍵字var聲明局部變量,然後將該關鍵字注入到調用eval的作用域中。

無法將eval調用移出模塊,因爲這樣做會導致將注入的代碼插入到不同的作用域中。請記住,JavaScript具有靜態範圍,因此只能以詞法形式訪問包含函數的範圍。

另一個解決方法是使用with,但是您應該避免with

with (require('./paco.js')) { 
    between(start(), content(), end()) 
} 

with不應該被用於兩個原因:

  1. absolutely kills performance因爲V8無法執行名稱查找優化。
  2. 已過時,嚴格禁止此內容。

說實話,我建議,而不是做一些棘手與eval,做你將來的維護一個忙,只要按照分配模塊出口到一個局部變量的標準做法。

如果您經常輸入它,請將其設置爲單字符名稱(或使用更好的編輯器)。

-1

根據此回答Global variables for node.js standard modules?global對象在瀏覽器中有相同的window。所以,你可以添加關鍵到該對象

function getNamespace(exports) { 
    for(var item in exports){ 
     global[item] = exports[item]; 
    } 
} 

,並用它作爲:

paco.getNamespace(paco); 

無需EVAL在所有。

+0

正如我所說的,我不能變異全球範圍內,因爲我當我使用幾乎所有的人作爲組合子 –

+0

@MadHollander但你揭露他們都使用eval暴露幾十個功能和典型案例。 ''''''''和'global [item] = exports [item];'完全一樣'''除非你用' (function(){})();'你沒有提到。 – jcubic

+0

其實沒有。當我從我的函數調用importn時,全局範圍是未觸及的。 –

0

不可以。從外部模塊修改本地範圍是不可能的。原因是,當在外部模塊中調用eval時,其上下文將是外部模塊,而不是需要模塊的作用域。

此外,vm.runInThisContextdoes not have access到本地範圍,以便不會幫助你。