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

时间:2011-06-18 13:47:28

标签: javascript node.js this coffeescript v8

我正在研究Trevor Burnham的 CoffeeScript 一书,我遇到了一个关于this / @的奇怪谜题。这个谜题有几个部分(我可能只是非常困惑),所以我会尽量让它尽可能清楚。

我遇到的主要问题是,通过不同的REPL和解释器运行相同的代码会产生各种不一致的结果。我正在测试(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是正确的:在全球范围内namethis.name应该是同一个东西,我会想到。但我已经准备好相信我不明白Javascript中的this。)

修改:如果我在调用this.name = null之前添加@name = null / setName(正如Pointy建议的那样),那么Coffeescript和Node会给我'Lulu'和' null'back但v8仍为两者返回'Lulu'。 (v8在这里对我来说更有意义。我最初在全局上下文中将name设置为null,但后来setName将它(在全局上下文中)设置为'Lulu'。所以之后,这就是我应该在那里看到的。)

3 个答案:

答案 0 :(得分:9)

所以,首先,有一个关于CoffeeScript REPL的错误,issue 1444,我在Telemachus引起我的注意后报告了这个错误。

但是这里更有趣的问题(我需要在my CoffeeScript book中注意到的)是Node.js模块最外层范围内的this不是global - 这是该模块的exports。试试这个:

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

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

答案 1 :(得分:2)

我不知道你为什么会得到不同的结果,但是知道函数的调用隐含地涉及设置this。因此,内部函数“setName()”具有其自己的this值,该值独立于定义它的外部函数中的this的值。因此,通过“.call()”调用设置this的事实对内部“setName()”函数内的this值没有任何影响,因为当“setName()”时被称为没有接收器。

答案 2 :(得分:1)

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

(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);

那么,为什么引擎之间不匹配?

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