在 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 中可以访问 String
和 Object
的原因是因为它们将它们放入了 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.global
和this
一样好,因为如果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.prototype
的 Object
。 this
也可以指向完全任意的东西,而不是 root
或 global
或 GLOBAL
,或者它可以指向 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,我只需要测试 global
和 window
。因此我决定坚持使用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.global
是undefined
。因此,为了解决这个问题,我最终决定使用以下代码:
(function (global)
// javascript framework
)(typeof global !== "undefined" && global || this);
我使用此代码的原因是因为在 node.js 模块中 this.global
是 undefined
。因此我们必须直接使用global
。因此我们使用typeof global !== "undefined" && global
来获取RingoJS 和node.js 中的global
对象;我们使用 this
作为浏览器中的 global
对象 (window
) 并作为默认后备。
注意:我没有提供在 node.js REPL 中查找 global
对象的任何逻辑,因为我不相信我的框架会直接在 REPL 中使用。然而,一旦理解了在 node.js 中查找global
对象的复杂性,正如本维指出的那样,编写查找它的逻辑应该是相当简单的。我知道我没有。
【讨论】:
以上是关于在 JavaScript 中定义全局对象的独立于实现的版本的主要内容,如果未能解决你的问题,请参考以下文章
我可以在 JavaScript 窗口全局对象中定义的某个对象中找到 JQuery 函数吗?