Slides at: davidql.github.io/scope_talk

A Little this, a Little _that:

Understanding Scope and Context in JavaScript

David Aragon
@davidmaragon

Slides at:

davidql.github.io/scope_talk


Code sample:

function() {
   alert("code will be this big");
}

Road Map
(Is This Talk for Me?)

  1. Scope from the ground up
  2. The magic variable `this`
  3. What is context?
  4. The future with EcmaScript 6

Let's look at scope from the ground up

What is scope?

Scope: the lifetime of a variable


function add(n1, n2) {
    var sum = n1 + n2;
    if (true) {
        var sum = 42;
    }
    return sum;
};

Scope in Pseudocode

JavaScript does NOT use block scope

JavaScript uses functional scope:


function add(number1, number2) {
    // anything inside is local
    var sum = number1 + number2;
};

For a variable to be local, it must:


  1. Be declared with `var`
  2. Be declared inside a function



function add(number1, number2) {
    var sum = number1 + number2;
};

Scope trick: Self-Invoking functions


(function() {
    // This function runs the second 
    // it's encountered.
    // Gives me a private scope on demand
})();

Self-Invoking functions


// Step 1: normal function
function() {};


// Step 2: wrap the whole thing in parentheses
(function() {});


// Step 3: call your function!
(function() {
    // private scope
})();

Let's play GUESS THAT SCOPE

What gets printed?


var episode_1 = "Winter Is Coming";
if (true) {
    var episode_1 = "The Kingsroad";
}
console.log(episode_1);

"The Kingsroad"


var episode_1 = "Winter Is Coming";
if (true) {
    var episode_1 = "The Kingsroad";
}
console.log(episode_1);
// "The Kingsroad"

What gets printed?


var episode_1 = "Winter Is Coming";
(function() {
    var episode_2 = "The Kingsroad";
})();
console.log(episode_2);

Error: episode_2 is not defined


var episode_1 = "Winter Is Coming";
(function() {
    var episode_2 = "The Kingsroad";
})();
console.log(episode_2);
// Error: episode_2 is not defined

What gets printed?


var episode_3 = "Lord Snow";
function printName(episode_3) {
    console.log(episode_3);
}
printName("A Golden Crown");

"A Golden Crown"


var episode_3 = "Lord Snow";
function printName(episode_3) {
    console.log(episode_3);
}
printName("A Golden Crown");

// prints: "A Golden Crown" 

If only it were that easy

A variable is global if it is not:


  1. Declared with `var`
  2. Declared inside a function

var episode_1 = "Winter Is Coming";
(function() {
    // declaring without var
    episode_2 = "The Kingsroad";
})();
console.log(episode_2);
// "The Kingsroad"

Why is global scope so bad?


"Browsers are the most hostile software development environment imaginable". -Douglas Crockford

  • Everyone has access to the global scope
  • 3rd party code can change your values at any time

The Dangers of Global Scope

http://blog.safeshepherd.com/23/how-one-missing-var-ruined-our-launch/

Detecting Globals


Object.keys(window);
// ["top", "window", "location", 
//  "external", "chrome", "document", ...

The magic variable `this`
What is context?
  • If a function is a sentence, the context is the subject of that sentence. It's who the function is about.
  • We access the context through the variable `this`.
Why is `this` so hard?
  • We're used to lexical scoping (where we can see the value of a variable in the visual scope).
  • `this` uses dynamic scoping, which we're less used to thinking about.
  • But we all kinda sorta know what `this` should be.
`this` in a jQuery event callback

$('button').on('click', function() {
    $(this).addClass('was_clicked');
    // $(this) refers to the button the was clicked 
});

`this` in a jQuery element loop

$('a.disabled').each(function() {
    $(this).remove();
    // $(this) refers to the current anchor 
    // element in our loop of the matched set 
});

4 ways to set the value of `this` in a function

4 ways to set the value of `this` in a function


  1. Call a method *on* an object
  2. Pass `this` in, with .call() and .apply()
  3. Use `new` to make `this` start out empty
  4. .bind() `this` with 3rd party methods
1. Call a method *on* an object
2. Call a function and pass `this` in, with .call();
2. (part b) Call a function and pass `this` in, with .apply();
3. Use `new` to create a brand new function context

function User(handle) {
    this.handle = handle;
}
var andrew = new User("@AndrewWK");
// andrew === {handle: "AndrewWK"}

4. .bind() methods ($.proxy, _.bind, native .bind())

var alertName = function() {
    alert(this.handle);
}
$.proxy(alertName, {handle: "@AndrewWK"})(); 
// alerts "@AndrewWK"

Performance

on jsPerf: http://jsperf.com/bind-vs-jquery-proxy/5

4 ways to set the value of `this` in a function


  1. Call a method *on* an object
  2. Pass `this` in, with .call() and .apply()
  3. Use `new` to make `this` start out empty
  4. .bind() `this` with 3rd party methods

  5. If none of the above are used, `this` is global object

Let's play GUESS THAT CONTEXT

What is `this`?


(function() {
    console.log(this);
})();

It's global object


(function() {
    console.log(this);
    // Window {top: Window, window: Window, location: ... 
})();

What is `this`?


var background_colors = ["green", "blue", "red"];

(function() {
    console.log(this);
}).call(background_colors);

It's `background_colors`


var background_colors = ["green", "blue", "red"];

(function() {
    console.log(this);
    // ["green", "blue", "red"];
}).call(background_colors);

What is `this`?


var user = {
    print: function() {
        console.log(this);
    }
};
user.print();

It's `user`


var user = {
    print: function() {
        console.log(this);
    }
};
user.print();
// Object {print: function}


`this` in jQuery source


$("div.disabled").each(function(index) {
    console.log(this + " is element " + index);
});

Annotated $().each()

Beware of Callbacks


$('button').on('click', function() {
    setTimeout(function() {
        // Uh oh! `this` is the global object!
        $(this).addClass('clicked');
    }, 1000); 
});

Dealing With Callbacks

Using `_this` and `_that`


$('button').on('click', function() {
    var _this = this;
    setTimeout(function() {
        $(_this).addClass('clicked'); // All better
    }, 1000); 
});

Our Future With EcmaScript 6

  • `let` allows block scope (!)
  • Arrow functions pull context from scope

`let` gives a variable block scope


// EcmaScript 6
if (true) {
    let local_var = "I'm local to the if block!";
}
console.log(local_var); // Error: undefined local_var

Browser support for `let`:


Chrome 19+ (expiremental flag enabled)
Firefox 24+
Safari No Support
IE 11+
Node (0.10.26) No Support

Arrow Functions


// current (ES5) declaration
var myFunc = function(params) { }

// arrow function
var myFunc = (params) => { }

Without Arrow Functions (ES 5)


$('button').click(function() {
    var _this = this;
    setTimeout(function() {
        // lookup _this from parent scope
        $(_this).addClass('clicked');
    }, 1000); 
});

With Arrow Functions


$('button').click(function() {
    setTimeout(() => {
        $(this).addClass('clicked');
    }, 1000); 
});

Browser support for arrow functions:


Chrome No support
Firefox 24+
Safari No Support
IE No support
Node (0.10.26) No Support

Where to go from here

Where to go from here

  • You know all the rules.
  • These two concepts underlie important developments in client-side coding:
    • Modules that mix private vars with public exports (like RequireJS)
    • Full fledged MVCs in the browser, like Backbone
    • Functional programming toolbelts like Underscore.js

Thank you!


@davidmaragon