Plugataryov Yura
Plugataryov Yura

Reputation: 168

coffescript scope misunderstanding

I'm trying to understand variable scopes in coffeescript and got confused a little bit, here's an example:

CoffeeScript code:

  x = "localscope"
  z = () -> 
    x = "functionscope"
    console.log(x)

  console.log(x)

Javascript compiled result:

(function() {
  var x, z;

  x = "localscope";

  z = function() {
    x = "functionscope";
    return console.log(x);
  };

  console.log(x);

}).call(this);

As I know in pure javascript all variables wrapped in function acts in the same scope. So as I understood all variables written in coffescript are global?

Upvotes: 4

Views: 211

Answers (3)

elreimundo
elreimundo

Reputation: 6266

It looks like you're confused about how CoffeeScript handles local scoping.

When a variable is defined, CoffeeScript hoists its variable definition to the top of the scope at which the variable is defined. Thus,

x = 'localscope'
z = ->
  x = 'functionscope'

gets parsed exactly as you described to

var x, z;
x = 'globalscope';
z = function () {
  x = 'localscope';
};

When the entire file is compiled, CoffeeScript wraps all of the code you've written into an anonymous function so that even your highest scoped variables do not bleed into the global namespace. That's the (function () { ... }).call(this) that you see at the top and bottom of the JavaScript version of your code.

If you're used to writing in Ruby, your expectation would be

> x = 5
> def z
>   x = 7
>   puts x
> end
> z # logs '7'
> x # still 5

Ruby automatically scopes every variable declaration locally unless the variable is defined otherwise (e.g. as an instance variable or a global variable).

JavaScript, on the other hand, scopes variables any time it sees the var prefix.

var x = 5;
function z () {
  var x = 7;
  console.log(x);
};
z(); // logs '7'
x; // still 5

CoffeeScript assumes that you might want your variables to permeate from higher scope to lower scope, and except for global variables (which can be set on the window object), there's no way to set an 'instance variable'-like variable that is scoped sort-of locally.

As @Esailija notes (although the solution isn't implemented quite perfectly), you can ensure that the x is scoped locally to the z function by passing in x as an argument, since JavaScript automatically scopes arguments only locally to the function that accepts them:

var x = 5;
function z (x) {
  x = 7;
  console.log(x);
};
z(); // logs '7' -- in JS, it's OK to execute a function without a named argument, which defaults to undefined
x; // still 5

or in CoffeeScript:

x = 5
z = (x) ->
  x = 7
  console.log x
z()
console.log x

As a side note, @Esailija 's solution is more idiomatically written using the do invocation:

x = "localscope"
z = do (x) -> () ->
  x = "functionscope"
  console.log(x)
console.log(x)

Upvotes: 0

topr
topr

Reputation: 4612

Javascript has a global scope as default scope. CoffeeScript has typically 'scope per file' by the anonymous function call as default scope. This prevents variable clash between script files in CoffeeScript.

I'm assuming you would like to create a global variable, wouldn't you? For that purpose you need to attach your variable to 'root object'. For a browser script it will be window object. So you just need to:

window.x = 'globalscope'

...and done.

If you ment the opposite, so the variable isolation at function level. It will be created as such but you need to use different name. Please check the example from coffeescript.org (text search for 'scope'):

CoffeeScript

outer = 1
changeNumbers = ->
  inner = -1
  outer = 10
inner = changeNumbers()

Compiled to JavaScript

var changeNumbers, inner, outer;

outer = 1;

changeNumbers = function() {
  var inner;
  inner = -1;
  return outer = 10;
};

inner = changeNumbers();

Upvotes: 0

Esailija
Esailija

Reputation: 140210

To shadow a variable in coffeescript you need to do something like this:

x = "localscope"
z = ((x) -> () -> 
    x = "functionscope"
    console.log(x)
)(x)
console.log(x)

Resulting javascript is:

var x, z;

x = "localscope";

z = (function(x) {
  return function() {
    x = "functionscope";
    return console.log(x);
  };
})(x);

console.log(x);

In your example, the x is not local to the innermost function, but referred to the same "global" x you declared at beginning. By "global", I mean accessible everywhere in the same file.

Upvotes: 1

Related Questions