关于这个/ @在Javascript / Coffeescript中的一个难题

我正在通过Trevor Burnham的CoffeeScript书,我遇到了一个关于this / @的奇怪的难题。 这个难题有几个部分(我可能只是很困惑),所以我会尽力使这个尽可能清楚。

我遇到的主要问题是,通过不同的REPL和解释器,运行相同的代码会得到各种不一致的结果。 我正在testing(1) coffee REPL和解释器,(2)Node的REPL和解释器,以及(3)V8的REPL和解释器。

这里是代码,首先是Coffeescript,然后是Javascript:

 // coffeescript setName = (name) -> @name = name setName 'Lulu' console.log name console.log @name // Javascript via the coffee compiler (function() { var setName; setName = function(name) { return this.name = name; }; setName('Lulu'); // console.log for node below - print for v8 // uncomment one or the other depending on what you're trying // console.log(name); // console.log(this.name); // print(name); // print(this.name); }).call(this); 

结果如下:

 $ coffee setName.coffee Lulu undefined # coffee REPL # This appears to be a bug in the REPL # See https://github.com/jashkenas/coffee-script/issues/1444 coffee> setName = (name) -> @name = name [Function] coffee> setName 'Lulu' 'Lulu' coffee> console.log name ReferenceError: name is not defined at repl:2:1 at Object.eval (/Users/telemachus/local/node-v0.4.8/lib/node_modules/coffee-script/lib/coffee-script.js:89:15) at Interface.<anonymous> (/Users/telemachus/local/node-v0.4.8/lib/node_modules/coffee-script/lib/repl.js:39:28) at Interface.emit (events.js:64:17) at Interface._onLine (readline.js:153:10) at Interface._line (readline.js:408:8) at Interface._ttyWrite (readline.js:585:14) at ReadStream.<anonymous> (readline.js:73:12) at ReadStream.emit (events.js:81:20) at ReadStream._emitKey (tty_posix.js:307:10) coffee> console.log @name undefined $ v8 setName.js Lulu Lulu # v8 REPL >> (function(){var setName; setName=function(name){return this.name=name;};setName('Lulu');print(name);print(this.name);}).call(this); Lulu Lulu # Switch print to console.log or require puts from sys $ node setName.js Lulu undefined # node REPL > (function() { ... var setName; ... setName = function(name) { ... return this.name = name; ... }; ... setName('Lulu'); ... console.log(name); ... console.log(this.name); ... }).call(this); Lulu Lulu 

所以我想,真正的问题是(1)我应该期待什么结果?(2)为什么这些解释器和REPL不能相处? (我的理论是,V8是正确的:在全球范围内name和这个name应该是同样的东西,我会想,但我已经准备好相信,我不明白this在Javascript中。

编辑 :如果我在调用setName之前添加this.name = null / @name = null (如下面的Pointybuild议),然后Coffeescript和Node给我'Lulu'和'null',但是v8仍然返回'Lulu'。 (v8对我来说更有意义,我在全局上下文中将name设置为null ,但是setName将它设置为(在全局上下文中)为'Lulu',所以之后这就是我应该看到的。

所以,首先,在Telemachus提出这个问题之后,我报告了CoffeeScript REPL, issue 1444 。

但是这里更有趣的问题(我在CoffeeScript书中需要注意的一个问题)是Node.js模块的最外层范围不是global – 它是模块的exports 。 试试这个:

 console.log this is exports console.log do -> this is global 

在Node模块中运行该代码时,您会发现这两个语句的计算结果都为true 。 这就是为什么name@name评估不同的东西: name本身将始终指向global.name ,除非它在var name声明的范围内; 但@name只会在global上下文中调用的函数中指向global.name (默认值)。 在任何函数之外的Node.js模块中,它将指向exports.name

我不知道你为什么得到不同的结果,但知道一个函数的调用隐式地涉及到设置this 。 因此,内部函数“setName()”具有它自己的this值,与定义它的外部函数中的值无关。 因此,通过“.call()”调用来设置this事实对内部“setName()”函数内部的this值没有任何影响,因为当调用“setName()”时不涉及接收器。

我不太了解CoffeeScript,但对JavaScript有一点点的了解,所以我只能试图从编译代码(或者应该做的)的angular度来解释这一点:

 (function(){ // In here "this" === [global] // // The purpose of this wrapper pattern is that it causes "this" to be // the global object but all var declared variables will still be // scoped by the function. var ctx = this; // let's keep test a ref to current context var setName; setName = function(name) { console.log(this === ctx); // !! true !! // Because in here "this" is the global context/object // this is setting [global].name = name return this.name = name; }; setName('Lulu'); // It's context will be [global] console.log(name); // (name === [global].name) == true console.log(this.name); // (this.name === [global].name) == true }).call(this); 

什么是(或者应该是)发生的事情是这样的(假设全球化window是浏览器):

 (function() { var setName; setName = function(name) { return window.name = name; }; setName('Lulu'); console.log(window.name); // 'Lulu' console.log(window.name); // 'Lulu' }).call(this); 

那么,为什么这些引擎之间没有匹配呢?

因为不同的环境使用不同的方式将全局对象交给你并处理范围界定。 很难肯定地说,每个环境都可能有单独的行为原因。 这很大程度上取决于他们如何评估代码(这是假设没有引擎有错误)。