SquirtleSquad1988 / js-function-context-this

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

General Assembly Logo

JavaScript: Context & this

What is this?

Objectives

By the end of this lesson, students should be able to:

  • Recall whether or not this is determined at declaration.
  • Explain what this points to in each calling context.
  • Read and follow the execution context of code that uses different this idioms.

Preparation

  1. Fork and clone this repository.
  2. npm install

this Is A Reference

We use this similar to the way we use pronouns in natural languages like English and French. We write: “John is running fast because he is trying to catch the train.” Note the use of the pronoun “he.” We could have written this: “John is running fast because John is trying to catch the train.” We don’t reuse “John” in this manner, for if we do, our family, friends, and colleagues would abandon us. Yes, they would. In a similar aesthetic manner, we use the this keyword as a shortcut, a referent to refer to an object.

Source: Understanding Javascript 'this' pointer.

this in the Global Scope Depends on the Environment

In browsers

  • The top-level scope is the global scope.
  • In the top-level scope in browsers this is equivalent to window.
  • That means that in browsers if you're in the global scope let/const/var will define a global variable.

In Node.js

  • The top-level scope is not the global scope.
  • In the top-level code in a Node module, this is equivalent to module.exports.
  • That means if you let/const/var inside a Node.js module will be local to that module.
  • Node does have a global variable named global and is documented here.
  • Since let/const/var variables are local to each module, global is the true global variable that is shared across modules.
console.log("In Browser vs In Node: this is ", this);
console.log("this === window, ", this === window);
console.log("this === module.exports, ", this === module.exports);

GOTCHA Global variables, methods, or functions can easily create name conflicts and bugs in the global object.

Block Scope

Scope refers to where variables and functions are accessible.

Example 1:

let a = 1;

if (true) {
  a = 2;
  console.log(a) // What logs?
}
console.log(a) // What logs?

Example 2:

let a = 1;

if (true) {
  let a = 2;
  console.log(a) // What logs?
}
console.log(a) // What logs?

Example 3:

const reAssign(){
  a = b;
  console.log( a );
}

let a = 1;
let b = 2;

reAssign(); // What logs?
console.log(a); // What logs?

Example 4:

const reAssign(a, b){
  a = b;
  console.log( a );
}

let a = 1;
let b = 2;

reAssign(); // What logs?
console.log(a); // What logs?

Example 5:

const reAssign(a, b){
  a = b;
  console.log( a );
}

let a = 1;
let b = 2;

reAssign(a, b); // What logs?
console.log(a); // What logs?

Scope can be helpful in understanding call context.

const reAssign(a, b){
  a = b;
  console.log( a );
}

reAssign(2, 3); // what logs
reAssign(10, 11); // what logs
reAssign(10, 11); // what logs

The value of our parameters a and b depend on when the function is called, we can not define what a or b are until the function has been called.

this Changes by Call Context

A function can indiscriminately operate upon any object. When a function is invoked, it is bound to an object on which it operates. The contextual object on which a function operates is referenced using the keyword this.

let xwing = {
    pilot: null,

    setPilot: function(pilot) {
        this.pilot = pilot;
        this.update();
    },

    update: function() {
        console.log('This X-Wing has changed!');
    }
};

xwing.setPilot("Luke Skywalker");
// >> "This X-Wing has changed!"

console.log(xwing.pilot);
// >> "Luke Skywalker"

The Four Patterns of Invocation

We must invoke a function to run it (ie: call upon the function to do its thing). Amazingly, there are FOUR ways to invoke a function in JavaScript. This makes JS both amazingly flexible and absolutely insane.

Function Invocation Pattern

When a function is invoked without context, the function is bound to global scope:

const goBoom = function() {
    console.log('this is ', this);
}

goBoom(); // what logs in the browser vs in node?

Following best practices, we can add use strict to get consistent results

'use strict'
const goBoom = function() {
    console.log('this is ', this);
}

goBoom(); // what logs in the browser vs in node?

Context: this refers to the window object (global scope). Here we would say "a method is called on an object". In this case the object is the window.

Gotcha: This behavior has changed in ECMAScript 5 only when using strict mode: 'use strict';

Method Invocation Pattern

When a function is defined on an object, it is said to be a method of the object. When a method is invoked through its host object, the method is bound to its host:

let deathstar = {
    goBoom: function() {
      console.log('this is ', this);
  }
};

deathstar.goBoom();
// this === deathstar

Context: this refers to the host object.

Call/Apply Invocation Pattern

Function objects have their own set of native methods, most notably are .call and .apply. These methods will invoke the function with a provided contextual object. While the syntax of these functions are almost identical, the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.

const goBoom = function () {
  console.log("this refers to ", this);
};

let deathstar = {
  weapon: 'Planet destroying laser'
};

goBoom.call(deathstar);
// this === deathstar

Context: this refers to the passed object. Here you would say "Call the function goBoom with deathstar as the context (this)".

Constructor Invocation Pattern

Any function may act as a constructor for new object instances. New object instances may be constructed with the "new" keyword while invoking a function.

Constructors are very similar to Ruby class constructors, in that they represent proper nouns within our application. Therefore they should follow the convention of capitalized names:

const Deathstar = function (weapon) {
  console.log("this is ", this);
  this.emporer = "Darth Sidius";
  this.weapon = weapon;
  this.whatIsThis = function(){
    console.log("Inside whatIsThis, this is ", this);
  };
  console.log("this is ", this);
};

let thatsNoMoon = new Deathstar('Mega giant huge laser');
let endor = new Deathstar('Happy little Ewoks');
// this === shiny new Deathstar instance

Context: this refers to the newly-created object instance. Here we would say "the object receives the method".

How this breaks down:

  1. Creates a new empty object ({}) // {}
  2. Attaches the constructor to the object as a property // {}.constructor = Deathstar
  3. Invokes the constructor function on the new object // {}.constructor(`???`);
  4. Returns the object // {}

This and Array Methods

If a this parameter is provided to forEach() and other Array Methods, it will be passed to callback when invoked, for use as its this value. Otherwise, the value undefined will be passed for use as its its value. (forEach)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Using_thisArg]

let counter = {
  sum: 0,
  count: 0,
  add: function (array){
    array.forEach(this.sumAndCount); // Note only 1 argument
  },
  sumAndCount: function (entry){
    this.sum += entry;
    ++this.count;
  }
}

counter.add([1,2,3]);
console.log(counter.sum); // what logs?

As stated in the documentation, this is undefined in an array method unless we pass the value of this as an argument.

let counter = {
  sum: 0,
  count: 0,
  add: function (array){
    array.forEach(this.sumAndCount, this); // Note 2nd argument
  },
  sumAndCount: function (entry){
    this.sum += entry;
    ++this.count;
  }
}

counter.add([1,2,3]);
console.log(counter.sum); // what logs?

What if we re-defined add the following way?

let anyObject = {};

let counter = {
  sum: 0,
  count: 0,
  add: function (array){
    array.forEach(this.sumAndCount, anyObject);  // Note 2nd argument
  },
  sumAndCount: function (entry){
    this.sum += entry;
    ++this.count;
  }
}

counter.add([1,2,3]);
console.log(counter.sum); // what logs?
console.log(anyObject.sum); // what logs?

Since counter.add() calls add() with this referring to counter, passing anyObject into forEach() makes this in the forEach() callback refer to anyObject.

forEach - JavaScript | MDN

Extra: Fat Arrow

Let's look at this problem again, our this value is not being passed into the array method so it is undefined and we do no get our desired results.

let counter = {
  sum: 0,
  count: 0,
  add: function (array){
    array.forEach(this.sumAndCount);
  },
  sumAndCount: function (entry){
    this.sum += entry;
    ++this.count;
  }
}

counter.add([1,2,3]);
console.log(counter.sum); // what logs?

Now with arrow functions (commonly referred to as "fat arrow"), the arrow function does not create it's own this context which means it is not undefined in an array method.

let counter = {
  sum: 0,
  count: 0,
  add: function (array){
    array.forEach((e) => { this.sumAndCount(e) });
  },
  sumAndCount: function (entry){
    this.sum += entry;
    ++this.count;
  }
}

Binding

Consider the following code:

//            <button>Get Random Person</button>​//        <input type="text">​
​
​
let user = {
  data: [
          { name:"T. Woods", handicap:2 },
          { name:"P. Mickelson", handicap:1 },
          { name:"C. Austin", handicap:0 }
        ],
  clickHandler: function(event){
    let randomNum = ((Math.random() * 2 | 0) + 1) - 1; // random number between 0 and 1​
    // This line is adding a random person from the data array to the text field​
    $ ("input").val(this.data[randomNum].name + " " + this.data[randomNum].age);
  }
}
​
​// Assign an eventHandler to the button's click event​
$ ("button").on('click', user.clickHandler);

What is happening and will this work?

With the .bind() method we can bind the context of user.clickHandler to the user object like so:

$ ("button").on('click', user.clickHandler.bind(user));

Summary

  1. Is the function called with new (new binding)? If so, this is the newly constructed object. let bar = new Foo()
  2. Is the function called with call or apply (explicit binding), even hidden inside a bind hard binding? If so, this is the explicitly specified object. let bar = foo.call( obj2 )
  3. Is the function called with a context (implicit binding), otherwise known as an owning or containing object? If so, this is that context object. let bar = obj1.foo()
  4. Otherwise, default the this (default binding). If in strict mode, pick undefined, otherwise pick the global object. let bar = foo()

Source: You-Dont-Know-JS/ch2.md

Lab (Pair)

Pair with a partner and follow the instructions in index.html. Your goal in this assignment is to read and understand the code examples presented. Take time to contemplate the execution flow, and note any questions you have for discussion.

Many of these scripts use the special debugger keyword to stop JS execution and open your console. Use this opportunity to inspect your environment (perhaps by looking at this?) and then continue.

When you're ready to begin, run grunt serve and navigate to (http://localhost:7165/)

Additional Resources

  1. All content is licensed under a CC­BY­NC­SA 4.0 license.
  2. All software code is licensed under GNU GPLv3. For commercial use or alternative licensing, please contact legal@ga.co.

About

License:Other


Languages

Language:JavaScript 89.3%Language:HTML 9.8%Language:CSS 0.9%