在 JavaScript 中定义全局对象的独立于实现的版本

Posted

技术标签:

【中文标题】在 JavaScript 中定义全局对象的独立于实现的版本【英文标题】:Defining an implementation independent version of the global object in JavaScript 【发布时间】:2012-01-06 23:38:27 【问题描述】:

我正在尝试在 javascript 中定义 global 对象,如下所示:

var global = this.global || this;

上面的语句是在全局范围内的。因此,在浏览器中,this 指针是window 对象的别名。假设它是在当前网页上下文中执行的第一行 JavaScript,那么global 的值将始终与this 指针或window 对象的值相同。

在 CommonJS 实现中,例如 RingoJS 和 node.js,this 指针指向当前的ModuleScope。但是,我们可以通过ModuleScope 上定义的属性global 访问global 对象。因此我们可以通过this.global 属性访问它。

因此,这段代码 sn-p 适用于所有浏览器,至少适用于 RingoJS 和 node.js,但我还没有测试过其他 CommomJS 实现。因此,我想知道这段代码在任何其他 CommonJS 实现上运行时是否不会产生正确的结果,如果是,我该如何修复它。

最后,我打算在 lambda 表达式中为我的实现独立的 JavaScript 框架使用它,如下所示(来自 jQuery 的想法):

(function (global) 
    // javascript framework
)(this.global || this);

【问题讨论】:

【参考方案1】:

this 与范围无关。

(function()
    (function()
        (function()

            (function()
            alert( this ); //global object
            )()

        ).bind()()
    ).apply()
).call()

this 仅在函数调用期间解析,归结为几个简单的规则。

    如果函数作为某个对象的属性调用,则该对象将在函数内部为this 如果按原样调用函数,this 将未定义,因此在非严格模式下它将是全局对象 如果函数使用.call/.apply 调用,那么this 由您自己显式设置。

如您所见,它属于规则#2,它解析为undefined。而且由于没有"use strict";

将 ThisBinding 设置为全局对象

编辑:我现在在 RingoJS 中进行了一些快速测试,他们实际上将“全局对象”放入了实际的全局对象(由标准定义),即 ModuleScope。仅仅因为大多数 js 实现中的实际全局对象都有 Object 和 String 等,如果它下面也有这些对象,则不会使对象成为全局对象。在 RingoJS 中可以访问 StringObject 的原因是因为它们将它们放入了 ModuleScope 原型中:

var logs = require('ringo/logging').getLogger("h");

logs.info( Object.getPrototypeOf( this ) === this.global );
//true

进一步证明ModuleScope 是实际的全局对象:

this.property = "value";
logs.info( property );
//"value"

所以这种诡计没有任何收获,它不能解决任何问题:

function injectGlobal()
globalProperty = "value"; // "use strict" would fix this!


injectGlobal()

logs.info( globalProperty );
//"value"

吐槽,this 已经根据本文前面给出的规则指代了实际的全局对象。 this.global 不是标准定义的真正的全局对象,它只是一个容器。

此外,您可以在浏览器中模拟这种行为:

考虑 scopehack.js

this.global = window.global || top.global || ;

考虑 main.html

<script src="scopehack.js"></script>
<script>
this.global.helloWorld = "helloWorld"; //"global scope"
this.helloWorld = "helloWorld" //"ModuleScope"
</script>

<iframe src="module.html"></iframe>

最后是一个“模块”module.html:

<script src="scopehack.js"></script>
<script>
    with( this.global )  //poor mans RhinoJS scope injection, doesn't work for writing
        console.log( helloWorld ); //"global scope" - "helloWorld"
        console.log( this.helloWorld ); //"ModuleScope" undefined
    
</script>

module.html 和 main.html 中哪个是实际的全局对象?它仍然是this

TLDR:

var obj = 
"String": String,
"Object": Object,
.....
;

不使 obj 成为全局对象。

【讨论】:

这很好,但我已经知道了。这就是我在代码中使用 this 而不是 window 的原因 - 它适用于所有 JavaScript 实现。然而,在 RingoJS 和 node.js 中,this 指针并不指向global 对象。它指向ModuleScope。因此我不得不写this.global || this。这不是我所期望的答案。对不起。 =( @AaditMShah,对。 this.globalthis 一样好,因为如果global 是一个全局变量,那么this 也必须是全局变量,this.global 才能工作。你可以在任何地方使用global,除非它在本地被覆盖,否则它将解析为全局对象......我不太明白你的问题是什么:( 顺便说一句,当我说“上述语句在全局范围内。因此在浏览器中,this 指针是window 对象的别名。”,我试图传达这一点它没有写在函数内部,因此它只能是window 对象(规则#1 和#3 不适用)。就我而言,规则 #2 也部分不正确,因为 this 指针不在函数内。当我们在全局上下文中使用 this 时,它在浏览器中始终是 window 对象,仅此而已。 @Esailija - 真的很难解释。看,this.global 是一个指向 global 对象的局部变量,this.global !== this 将返回 true 因为在 RingoJS 和 node.js 中,this 指针永远不会指向 global 对象(就像你规则#2)。相反,它指向ModuleScope。在ModuleScope 上有一个名为global 的属性,它指向global 对象。无法像在浏览器环境中那样直接访问 global 对象。 @AaditMShah,在模块内你可以尝试做:var a = (function()return this;)(); a 仍然是模块对象吗?根据 javascript 规范,它应该是未定义的或全局的。【参考方案2】:

实现独立版本并非易事

(function (global) 
    // javascript framework
)(
   this && this.global || // ringoJS
   typeof root !== "undefined" && root || // node.js
   typeof global !== "undefined" && global || // more node.js
   typeof GLOBAL !== "undefined" && GLOBAL || // more node.js
   typeof window !== "undefined" && window || // browsers
   this // either undefined or some global default?
);

您将不得不对每个环境的特征检测进行硬编码。

【讨论】:

root 是 Node 中的实际全局对象。 @AaditMShah 不,不是。 this || this.global 将返回 this 而不是 this.global root === GLOBAL 应该始终为真。 root === global 将取决于您当前所处的上下文。例如,在 REPL 中,如果您在全局上下文中运行,那么它是正确的,否则它不是。 这实际上是一个非常重要的区别,因为它改变了当你做扩展原生原型之类的事情时发生的事情。如果您不在全局上下文中,root.Object 将返回 undefined,但如果您在,那么它将指向包含所有 Node 对象继承的 Object.prototypeObjectthis 也可以指向完全任意的东西,而不是 rootglobalGLOBAL,或者它可以指向 root 据我所知,这在任何地方都没有记录,但是 Node 的大部分代码是用 JavaScript 实现的,所以它很容易上手。相关的代码位是github.com/joyent/node/blob/master/lib/module.js#L394github.com/joyent/node/blob/master/src/node.js#L125github.com/joyent/node/blob/master/src/node_script.cc#L112【参考方案3】:

阅读 Esailija 和 Raynos 的回答后,我了解到我的代码 this.global || this 不适用于 node.js 中的所有情况;如果全局范围内已经存在名为 global 的变量,它甚至可能在浏览器中失败。

Esailija 指出this.global 并不是真正的global 对象,而是指出this 是RingoJS 中的global 对象;虽然我理解他的论点,但出于我的目的,我需要this.global 而不是this

Raynos 建议我为每个 CommonJS 环境硬编码特征检测。但是由于我目前只支持 RingoJS 和 node.js,我只需要测试 globalwindow。因此我决定坚持使用this.global || this

尽管如此,正如我之前所说,this.global || this 并不适用于 node.js 中的所有情况,正如我从 benvie 的 cmets 中了解到的那样。在 node.js REPL 中,我意识到我需要 this 而不是 this.global。但是,this.global || this 表示 this.global。在 node.js 模块中,我需要 this.global 而不是 this。但是,它表示this,因为this.globalundefined。因此,为了解决这个问题,我最终决定使用以下代码:

(function (global) 
    // javascript framework
)(typeof global !== "undefined" && global || this);

我使用此代码的原因是因为在 node.js 模块中 this.globalundefined。因此我们必须直接使用global。因此我们使用typeof global !== "undefined" &amp;&amp; global 来获取RingoJS 和node.js 中的global 对象;我们使用 this 作为浏览器中的 global 对象 (window) 并作为默认后备。

注意:我没有提供在 node.js REPL 中查找 global 对象的任何逻辑,因为我不相信我的框架会直接在 REPL 中使用。然而,一旦理解了在 node.js 中查找global 对象的复杂性,正如本维指出的那样,编写查找它的逻辑应该是相当简单的。我知道我没有。

【讨论】:

以上是关于在 JavaScript 中定义全局对象的独立于实现的版本的主要内容,如果未能解决你的问题,请参考以下文章

我可以在 JavaScript 窗口全局对象中定义的某个对象中找到 JQuery 函数吗?

javaScript 对于全局对象对象的理解

JavaScript全局变量与局部变量

JavaScript中的全局对象,内置对象和预定义对象是啥?

JavaScript Window对象 整理

JavaScript --- [基本语法之函数]