Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A puzzle about this/@ in Javascript/Coffeescript

I'm working through Trevor Burnham's CoffeeScript book and I've run into a weird puzzle concerning this/@. The puzzle has a few parts (and I may be just very confused), so I'll try to make this as clear as I can.

The main problem I'm having is that I get varied and inconsistent results running the same code through different REPLs and interpreters. I'm testing with (1) the coffee REPL and interpreter, (2) Node's REPL and interpreter and (3) v8's REPL and interpreter.

Here's the code, first as Coffeescript then as 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);

Here are the results:

$ 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

So the real questions, I suppose, are (1) what results should I expect and (2) why can't these interpreters and REPLs get along? (My going theory is that v8 is right: in the global context name and this.name should be the same thing, I would have thought. But I'm very ready to believe that I don't understand this in Javascript.)

Edit: If I add this.name = null/@name = null before calling setName (as Pointy suggests below) then Coffeescript and Node give me 'Lulu' and 'null' back but v8 still returns 'Lulu' for both. (v8 still makes more sense to me here. I set name to null initially in the global context, but then setName sets it (in the global context) to 'Lulu'. So afterwards, this is what I should see there.)

like image 250
Telemachus Avatar asked Jun 18 '11 13:06

Telemachus


2 Answers

So, first off, there's a bug with the CoffeeScript REPL, issue 1444, which I reported after Telemachus brought this to my attention.

But the more interesting issue here (and one that I need to note in my CoffeeScript book) is that this in the outermost scope of a Node.js module isn't global—it's that module's exports. Try this out:

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

You'll find that both statements evaluate to true when you run that code in a Node module. That's why name and @name evaluate to different things: name by itself will always point to global.name, unless it's in the scope of a var name declaration; but @name will only point to global.name in a function called in the global context (the default). In a Node.js module, outside of any function, it'll point to exports.name.

like image 80
Trevor Burnham Avatar answered Oct 28 '22 09:10

Trevor Burnham


I don't know why you get different results, but I know that the invocation of a function implicitly involves setting this. The inner function "setName()" therefore has its very own this value independent of the value of this in the outer function in which it is defined. Thus, the fact that you set this via that ".call()" invocation has no effect whatsoever on the this value inside the inner "setName()" function, because when "setName()" is called there's no receiver involved.

like image 36
Pointy Avatar answered Oct 28 '22 09:10

Pointy