Coder Social home page Coder Social logo

js-function-context-this's Introduction

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 [email protected].

js-function-context-this's People

Contributors

jrhorn424 avatar micfin avatar payne-chris-r avatar raq929 avatar realweeks avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

js-function-context-this's Issues

remove (weapon) since it's not used

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

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

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

Should be:

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

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

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

Replace this example / add more explanation

This (haha) example uses this in two different ways, and is therefore confusing to beginners.

function Counter() {
  this.sum = 0;
  this.count = 0;
}
Counter.prototype.add = function(array) {
  array.forEach(function(entry) {
    this.sum += entry;
    ++this.count;
    console.log(this);
  }, this);
  // ^---- Note
};

let obj = new Counter();
obj.add([2, 5, 9]);
obj.count
// 3
obj.sum
// 16

Add objective or remove section from lesson

The Array Methods section and its accompanying Fat Arrow section is not part of the objectives and adds a layer of complexity that did not seem beneficial. We want to note that the node-api lesson does utilize the arrow function as a callback in loops so that this is not undefined but could potentially be better taught as a "gotchya" moment, issue or bug.

@jrhorn424 discussed that this should be made into a meaningful objective or removed.

Confusing info

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

The owning or containing object IS NOT NECESSARILY the context. You can call it in a different context using obj1.foo().call(someOtherObject).

I'm not quite sure how to reword this. It's not WRONG, but many people get confused and think that because an object contains a function, this is set to that object when it is written, not at runtime, and I want to be extremely clear about how that's not the case.

WAT

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

let deathstar = {};
goBoom.call(deathstar);
// this === deathstar

Context: this refers to the passed object. Here you would say "the
object receives the method".

@gaand -- Why isn't it " Here you would say the method call receives the object as an argument"?

function takes event param but doesn't use it, isn't that an error?

var user = {
  data: [
          { name:"T. Woods", age:37 },
          { name:"P. Mickelson", age:43 }
        ],
  clickHandler: function(event){
    var 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);
  }
}
​```

Remove code samples from README

Move them to files in lib and reference them in the README. This minimizes our updates and maintains a single source of truth.

Remove jquery notes from repo

Inside a jQuery method, $(this) refers to the document element that was selected. Note that this is not a variable. It is a keyword. You cannot change the value of this.

In the summary

Is the function called with new (new binding)? If so, this is the newly constructed object. let bar = new foo()
SHOULD BE:
Is the function ... bar = new Foo()

Use node template

Originally "Change examples in lab to run in node instead of the browser"

Review scope

Some people have forgotten the scope lesson from the first unit at this point. Starting the talk with a short review of scope might be helpful.

Add annotations to code (on solution branch?)

thanks @raq929

Rachel Stevens [12:10]
Annotated:

var xwing = {
    //set pilot to null
    pilot: null,
    //an anonymous funtion
    setPilot: function(pilot) {
        //this.pilot refers to the key pilot in the xwing object
        //this points to an object, but we don't know which one yet
        //pilot (on the right) is a reference to the first argument that is being passed to (anonymous) function that is assigned to the setPilot property
        this.pilot = pilot;
        //call update on this object
        this.update();
    },
    //update is a method that takes no arguments. It prints something to the console.
    update: function() {
        console.log('This X-Wing has changed!');
    }
};

//When I call setPilot on xwing, JavaScript looks at the xwing object to find the setPilot keyword
//The CALLING CONTEXT of this line of code is xwing.
//We get a reference to a function back, then we invoke it, because it is followed by parens. with the params in the parentheses.
//WHEN THE FUNCTION IS CALLED, 'this' now points to xwing
//before assignment, pilot is null, after the function call, it is set to "Luke Skywalker"
//then the update method is called from within the setPilot function. this is bound to xwing, so this#update looks in the xwing object for an the update keyword. It finds a function and executes it using the variables in the parens (none in this case).
xwing.setPilot("Luke Skywalker");
// >> "This X-Wing has changed!"

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

//Because xwing is the calling context, javascript looks for setPilot on xwing. It finds it, and executes it, because there are perentheses.

👍2

Rachel Stevens [12:21]

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

//creates an empty deathstar object
var deathstar = {};
//one method on functions is #call
//the CONTEXT (what 'this' points to) is the object that is passed in as the FIRST ARGUMENT to #call
//we TAKE the function goBoom, we BIND IT to deathstar, and then we CALL it on deathstar
goBoom.call(deathstar);
// this === deathstar```
:boom:2
Antony Donovan and You reacted with :boom:


Rachel Stevens [12:27] 
```function Deathstar() {
    console.log(this);
}

var deathstar = new Deathstar();
// this === shiny new Deathstar instance


//this inside a constructor POINTS TO THE NEW OBJECT
//we use a constructor function to initialize data on new objects
var d = new Deathstar()
//first thing that new does is create an empty object
//second, it runs the constructor function ON the new object ('this'!)
//third, it sets constructor property on new object
//you will probably want to use a variable with new
d.constructor
//>> Deathstar

Address `'use strict';`

This shouldn't be an aside, it should be the focus:

const foo = function () {
  'use strict'
  console.log('this', this)
}
foo()

prints this undefined to the console, not the object global/window.

A sentence has two errors.

In README's Extra: Fat Arrow (New in ES6) section, below the first code snippet, there is a sentence that reads 'The above code won't return do what you want, can you think of a way to get the code to do what is expcted?'

A correction could be 'The above code won't return what you want, can you think of a way to get the code to do what is expected?' or 'The above code won't do what you want, can you think of a way to get the code to do what is expected?'

Thanks

Simplify Global object and scope section

The following should be removed as discussed with @jrhorn424 because it is out of scope of the lesson and adds confusion. Also, our browser-template and node-template break these expectations so it will not be useful information OTHER THAN for interviews.

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](https://nodejs.org/api/globals.html#globals_global).
-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);

Rework to follow `talk-template`?

This talk is a LOT of talking, then a mega lab. I'm not sure how much the lab helps at the end. I wonder if doing D, CA, L, D, CA, L, etc, (for each example) might be a better approach?

.age should be .handicap

The example data was changed because "age is a bad defined attribute," but the code to support it was never updated.

  $ ("input").val(this.data[randomNum].name + " " + this.data[randomNum].age);

SHOULD BE:

  $ ("input").val(this.data[randomNum].name + " " + this.data[randomNum].handicap);

StarWars analogy pushed too far?

I freakin love the star wars example, but I don't want it to be there at the expense of the lesson.

Maybe instead of creating deathstars it should be 'moons' and deathStar and endor should be the things we create. Maybe we just wax this example and think of something with more pedagogical value?

add a reference to the .call documentation?

What does .call take as an argument? An object. What is that object? The contextual object that we want to call the function on... I think taking a look at the documentation for .call may be helpful.

Fix this link

## 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]
+ [forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Using_thisArg)

global_function.js - variables needs to be vars

line 42: showFullName (); // Peter Ally
line 45: window.showFullName (); // Peter Ally

The comments that say Peter Ally suggests that that will be the output of those functions. However, that is untrue. The actual result is undefined undefined. At least half of the class was confused by those comments in WDI015.

Clarify examples

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

let deathstar = new Deathstar();
// this === shiny new Deathstar instance

Solution Code

Put this in the non-existant solution branch.

$('.current-time').each(function() {
   setInterval(function() {
     $(this).text(Date.now());
   }.bind(this), 1000);
 });

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.