Context
When a function is invoked, it receives the parameters that were passed in as well two other objects
- arguments- an array-like object which lists all the parameters which were pass into function
- this- is an object that refers to the current execution context
What the actual object this refers to will depend on how the function was called.
constructor
A function called using new operator is called a constructor. It creates a new object inheriting from
its prototype and sets the value of this to the new object.
  var Car = function(color){
    this.color = color;
  }
  var myCar = new Car('red');
  console.log(myCar.color) // red
method
A function which is a property of an object is called a method. When a function is invoked as a method this will refer to the parent object.
  var library = {
    numberOfBooks: 0,
    addBooks: function(books){
      this.numberOfBooks += books;
    }
  }
  library.addBooks(7);
  console.log(library.numberOfBooks) //7
function
A function which does not belong to a method will have its context set to the global object, which is window in the browser and global in Node.
That causes several issues.
- Inner functions cannot see the context of parent methods - var library = { numberOfBooks: 0, addBooks: function(books){ this.numberOfBooks += books; function double(){ this.numberOfBooks *= 2; } double(); } } library.addBooks(5)// 5 console.log(library.numberOfBooks);
This is because double is a function and it gets passed the global object as the value of this.
- When a method is called a function we lose our original scope because - thisbecomes- windowrather than the parent object.- var library = { numberOfBooks: 0, addBooks: function(books){ this.numberOfBooks += books; } } var detached = library.addBooks; detached(7); console.log(library.numberOfBooks) // 0
Also, your global object has now acquired a numberOfBooks property which is not ideal :(
We can resolve this in two ways.
- Use a temporary variable to capture - thiswhich will remain visible to the inner function- var library = { numberOfBooks: 0, addBooks: function(books){ var that = this; this.numberOfBooks += books; function double(){ that.numberOfBooks += books; } double(); } } library.addBooks(5) // 10 console.log(library.numberOfBooks);
- bindthe value of- thisso it is retained.- bindis a method of- functionwhich returns a function that has- thisset to the value passed in as a param.- var library = { numberOfBooks: 0, addBooks: function(books){ this.numberOfBooks += books; } } var detached = library.addBooks; detached = detached.bind(library); detached(7); console.log(library.numberOfBooks) // 7
apply / call
Another way to invoke functions is using apply or call methods. They both accept the context, the value that will become this as their first param. The difference is that apply takes the params that will be passed into the method as an array, whereas call takes them as individual params.
  var book = {
    author: 'JK Rowling'
  }
  function addTitle(title){
    this.title = title;
  }
  addTitle.call(book, 'Harry Potter');
  console.log(book.title) // Harry Potter
Exercise
Above we solved the issue 1. with internal method
doubleusing tempory variablethatand we solved issue 2. with detached context usingbind. Please solve issue 1. usingbindinstead and issue 2. using temporary variablethat.Below we turn the
pushmethod of the arrayfilmsinto a standalone function and remove thepushmethod fromfilms. However, we still have both the arrayfilmsandpush, they are just separate entities now. InsideaddMissionImpossibleCallingpush('Mission Impossible')throws an error because it no longer has a reference to its context. Also, callingfilms.push('Mission Impossible')beacuse films no longer has the methodpush. Usecallorapplyto invoke to functionpushwith the contextfilmsto insert another film title into the array films.function addMissionImpossible(films, push){ // push('Mission Impossible'); // throws error // Array.prototype.push called on null or undefined // films.push('Mission Impossible'); // throws error // films.push is not a function } var films = ['Batman', 'Wonder Woman']; var push = films.push; films.push = null; addMissionImpossible(films, push); console.log(films);