改变JavaScript的全局对象?

9
有没有一种方法可以在 JavaScript 中更改 root 对象?
例如,在浏览器中,root 对象是 "window"。
X = 5;
console.log(Y);

等同于:

window.X = 5;
console.log(window.Y);

现在我想做的是更改这个对象,这样当我执行以下操作时:

X = 6;

我需要这个的原因:

Node.js应用程序中,程序的每个部分都可以访问全局对象。这是一个大问题,因为由Node.js web服务器执行的每个脚本都可以向其中添加新变量。它们会一直存在,直到Web服务器重新启动。我想通过更改全局对象来避免这个问题。

更新

我已经测试了以下代码,并得到了一个非常有趣的结果。你对以下代码有什么期望?

var X = {A:"a",B:"b"};

with(X){
    A = 5;
    C = 7;
}
for(a in X){
    console.log(a+" is "+X[a]);
}

/* 
Expected Console Output:
A is 5
B is b
C is 7

Real Console Output:
A is 5;
B is b;

*/

有没有办法让输出符合我的预期?

更新

我现在已经用以下代码测试了模块系统。

//program.js
var t = require("./module.js");
t.Test();

console.log(A);

//module.js
A = 5;
exports.Test = function(){
    console.log("hello world!");
}

输出结果为:
hello world!
5

这告诉我,在module.js中定义的变量“A”已添加到program.js的全局对象中。然而,该模块并未解决我的问题。

2
适当的术语不是“ROOT”,而是“Global”,在浏览器中,“window”实现“Global”。 - Free Consulting
如果您在代码顶部使用"use strict";,则您的示例将无法实现。我来到这里是因为我想做同样的事情,实际上我想用代理对象替换全局对象。这很好地工作了,但失去了它的全局范围。我认为这与对旧对象的引用有关。 - Codebeat
3个回答

8

有一个with语句,但在严格模式下不推荐使用并被禁止。

最好明确地引用持有对象的变量。

针对更新的问题:

with会沿着作用域链向上搜索,直到找到具有匹配属性的对象或到达window。它不能用于定义对象上的新属性。

var X = { A: 5, B: 8, C: 7};
with(X){
    console.log(A, B, C);
}

谢谢!我完全忘记了“with”,这正是我在寻找的。它是否被禁止并不重要,它只需要在V8引擎中运行,而且似乎能够工作。 - Van Coding
好的,我太快了...它不像我预期的那样工作。我更新了我的帖子。 - Van Coding
2
@Flash:我确定我对with语句不像@David那么熟悉,但似乎with会在X中查找属性,如果没有找到,则遍历作用域链。如果都没有找到,则创建全局变量。看起来要使用with,你需要先定义这些属性。var X = {A:null,B:null,C:null,D:"D"}; - user113716
是的,我也发现了同样的问题。但这是个问题...在我的情况下,我不想预定义这些值。那样做毫无意义。 - Van Coding
@Flash:我刚刚更新了我的答案,提供了另一种可能性。有很多方法可以解决这个问题。最简单的方法是在添加属性时直接引用对象(正如David在他的答案中建议的那样)。 - user113716
显示剩余2条评论

4

如果你在谈论变量,JavaScript 的作用域是函数级别的。

X = 5;  // global variable

console.log( window.X );  // 5

(function() {
   var X = 6;  // declare a local variable by using the "var" keyword

   console.log( X );  // 6
})();

console.log( window.X );  // 5

否则,您可以创建一个对象,并向其添加属性。
X = 5; 

console.log( window.X );  // 5

var obj = {};

obj.X = 6;

console.log( obj.X ); // 6

console.log( window.X );  // 5

编辑:添加另一种可能可用的解决方案。

您可以调用一个匿名函数,但将函数的上下文设置为您的X对象。然后函数中的this将指向X

var X = {};
(function(){
    this.A = 5;
    this.B = 8;
    this.C = 7;
}).call(X);
for(a in X){
    console.log(a+" is "+X[a]);
}
.call()方法(以及.apply()方法)允许您显式设置调用上下文的thisArg。您传递的第一个参数将是在调用上下文中定义`this`的方式。
或者只需将X作为参数传入。
var X = {};
(function(X){
    X.A = 5;
    X.B = 8;
    X.C = 7;
})(X);
for(a in X){
    console.log(a+" is "+X[a]);
}

尽管最简单的方法是简单地引用它(如我在上面的回答中所提到的)。
var X = {};
X.A = 5;
X.B = 8;
X.C = 7;
for(a in X){
    console.log(a+" is "+X[a]);
}

或者使用模块模式:

   /****** I'm guessing at the use of "global" here ********/
global.myNamespace = (function(global,undefined) {

    // define the object to be returned
    var X = {};

    //  define private local variables
    var a_local = 'some value';
    var another_local = 'some other value';

    // define private functions
    function myFunc() {
        // do something with local variables
    }

    // give the return object public members
    X.someProperty = 'some value';
    X.anotherProperty = 'another value';
    X.publicFunc = function() {
        //do something with the local variables
        //    or public properties
    };
    X.anotherFunc = function() {
        //do something with the local variables
        //    or public properties
    };
    // return the object
    return X;

})(global);

console.log(myNamespace);

@Down-voter:请解释一下。我的回答哪里有误了? - user113716
@David:感谢您的回复。您能否解释一下是哪个位?我认为OP不熟悉JavaScript,因此当OP执行X = 5并看到它向window添加属性时,我猜测会做出这样的假设,即这通常是添加属性的方法。根据具体需求,我认为这两种解决方案都可能有所帮助。 - user113716
+1 给一个真正好的答案。但是 David 的回答最接近我期望的解决方案。 - Van Coding
@patrick dw:我可以让你放心,因为在我的代码中我不使用while,因为它对我的情况没有帮助。我知道模块模式,我会多读一些关于它的内容,以了解它是否可以防止我的问题。感谢你的帮助! - Van Coding
@Flash:你是在模块内使用“var”关键字声明变量吗?我已经在我的答案中更新了一个示例。 - user113716
显示剩余9条评论

2
我在EcmaScript 6中找到了一种方法来调整with(context) { ... },这样我们分配的任何新变量都将进入context对象,而不是全局/window对象。
感谢这篇文章Metaprogramming in ES6: Part 3 - Proxies教给我ES6代理功能。
在代理中:
  • 我们重写has以返回true,因此我们的上下文似乎具有所有属性,当我们设置任何变量时,它将进入对象。
  • 我们重写get以从我们的上下文获取属性,或者如果实际上不存在,则从一个up对象(默认为全局window)获取属性。
我知道with不受欢迎,但这种技术使得我们可以创建可变的可扩展模块,在其中可以方便地访问成员,例如foo而不是Module.foo,我认为这不是不安全或模糊的。

function scope(x, up=window) {
    return new Proxy(x, {
        has: (o, k) => true,
        get: (o, k) => k in o ? o[k] : up[k]
    });
}

var Test = {};

with (scope(Test)) {
    x = 1;
    y = 2;
    add_x = function(y) {
        console.log(x + y);
    }
}

Test.add_x(10);

with (scope(Test)) {
    x = 3;
    add_y = function(x) {
        console.log(x + y);
    }
}

Test.add_x(20);

Test.y = 5;

Test.add_y(30);


到目前为止,我发现的唯一问题是从模块内部调用window上的setTimeout方法。它会使用错误的this进行调用。最简单的解决方法就是将其作为window.setTimeout调用,或者我们可以修改scope代理以通过一定的一组方法。我尝试了几种其他的解决方法,但都没有起作用。 - Sam Watkins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接