/ javascript

[JavaScript] Scope and Context

Scope and Context

Javascript: Scope and Context

  console.log('Hello world!');

If you are working with Javascript, have you ever heard about scope and context? Understand them in JS is very helpful.

** I've updated the article to apply some ES6 syntax, just want to practice myself =)!

Scope

  • Scope is function-based.
  • Scope is variables, functions which are defined inside a Function, and includes its closure.
  • The Global Scope can be accessed everywhere.
  • Closure Scope is...

"Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created." (Source: Mozilla developer)

  • ES6 fulfilled JS's scope with block-scoped.
 function Func() {
    var name = ''; // scope variable
    iAm = 'The law!'; // global variable
    var setName = (value)=> {
      // name is accessed via closure scope
      name = value;
    }
    var getName = ()=> {
      // name is accessed via closure scope 
      return name;
    }

    return { setName, getName};
  }
  function whoAmI() {
    console.log(iAm);
  }

Now we run it and the result is:

  let inst = new Func()
  , inputName = 'Kai';

  inst.setName(inputName);  
  inst.getName(); // 'Kai'
  whoAmI(); // 'The law!';
  • name is variables in Func's scope.
  • setName is the function in Func's scope.
  • iAm is global variable because it wasn't begun with var or let.
  • The argument value of inputName is set to value of variable name in Func()'s scope by function setName().
  • getName() return value of local variable name.
  • name is in local scope of Func() and is in closure scope of getName(), setName().
  • when Func was executed, it created global variable iAm, so that whoAmI() can console log the value of iAm.
  • Sometimes people misunderstand about var a = b = 1 is shortcut of creating two local scope variables a and b, but in this case it will create local variable a and global variable b. The correct way is:
  var a, b;
  a = b = 1; // two local variables, yay!!

block-scoped is new scopes in ES6, it helps us implement some logics more convenient than before. It was defined by let instead of var.

E.g:

  // Block-scoped variable
  // ES5
  // i is local scope
  for (var i = 0; i < 5; i++) {
    setTimeOut(function() {
      console.log(i);
    }, i*1000);
  }
  // result : 5 - 5 - 5 - 5 - 5

   //ES6
   // i is block-scoped
   for (let i = 0; i < 5; i++) {
     setTimeOut(function() {
       console.log(i);
     }, i*1000);
   }
   // result : 1 - 2 - 3 - 4 - 5

   // Block-scoped function example:
   {
    function foo () { return 1 };
    foo() === 1; // true
      {
        function foo () { return 2 };
        foo() === 2; // true
      }
      foo() === 1;  // true
    }

Now cast a little magic here:

  /* Check this yourself :D */
  let a = true;
  if(a) {
     let b = 'hello';
     console.log(b);
  }
  console.log(b);

Context

  • Context is object-based.
  • Context is the instance of a class function.
  • Context is accessed by variable this, which is a reference to the object that “owns” the currently executing function ( the environment that function was called in).
  • If a function returns an object, the Context will be referenced to that object.

There are two many ways to implement Context instance:

1/. Add variables, functions into Context by this keyword:

  function Func() {  
    this.name = 'Kai'; // this is context variable
    this.getName = function() {
      return this.name;
    }
  }

When creating a new instance from Func, it returns this as default.

2/. Return the object instance:

  function Func() {  
    let name = 'Kai';
    let getName = function() {
      return this.name;
    }

    return { name, getName };
  }

The this will reference to the return object.

3/. One of features of ES6 is Class Definition:

class Func {
 constructor() {
   this.name = 'Kai';
 }
 getName() {
  return this.name;
 }
}

Now we run it:

  let instance = new Func();   // { name : 'Kai', getName : Function}
  instance.getName(); // return 'Kai'
  • The variable name is set into context when Fun() be executed, can be access via instance.name.
  • instance.getName() access its Context (instance) via this and return instance.name.
  • If you don't want to create any instance, we can implement singleton or object instance.
  // Singleton
  (function(self) {  
    let name = 'Kai';
    let getName = function() {
      return this.name;
    }

    self.instance = { name, getName };
  })(this);

  // Simple object instance
  var instance = {
     name: 'Kai',
     getName : function() {
      return this.name;
    }
  } 

One important change of ES6 is arrow function ()=>. It was born for a purpose that we no need to reference the context of environment or their context into a variable to access if needs - which was very popular in ajax callback function.

 //ES5 
 function user() {
   this.name = "Kai";
   this.getPosition = function(){
     var self = this
     $.get('/position', function(result) {
       // this is context of callback function, so that:
          console.log('position of ' + this.name + ' is ' + result.position);  // position of undefined is....
      //  self is context of original environment that call the `$.get`, and can be accessible via its closure.
                    console.log('position of ' + self + ' is ' + result.position); // position of Kai is....
     }); 
   }
 }

//ES6 
function user(){
  this.name = "Kai";
  this.getPosition = function() {
    $.get('/position',(result) => {
      console.log('position of ' + this.name + ' is ' + result.position); // position of Kai is....
    });
  }
}

So, arrow function ()=> isn't shortcut of function(){}, please use it responsively.

Scope vs. Context

Now we have this function:

  function Func() {  
    let age = 26; // variable in scope (1)

    /* Functions access SCOPE */
   let getScopeAge = ()=> age;

   let setScopeAge = (value)=> age = value;

    /* Functions access CONTEXT */
    let setContextAge = function(value) {
      this.age = value;
    }; 

    let getContextAge = function() { 
      return this.age;
    };

    /* Return object context */
    return {
      age,  // variable in context (2)
      getContextAge, setContextAge,
      setScopeAge, getScopeAge 
   };
}

In those line of codes above, we have 2 pairs of getter/setter function: 1 access context value and other access scope value.

  let inst = new Func();  

  inst.age; //  return: 26 
  inst.setScopeAge(35);  
  inst.getScopeAge(); // return: 35  
  // try getAge
  inst.getContextAge(); // return: 26, not 35??!
  • The function setScopeAge is set value to age of function Func's scope (1).
  • The function getScopeAge is get value of function Func's scope.
  • The function getContextAge is get value of age in Context, so that it's 26 (2).
  • The age in the function Func is private, only function setScopeAge and getScopeAge can access/modify.

Then, we run those functions which access context:

  let inst = new Func();

  inst.age; // 26  
  inst.setContextAge(30);
  inst.getContextAge(); // access age in context, return 30

  inst.age; // changed to 30  
  inst.getScopeAge(); // access age in scope, return 26
  • The inst.age return value this.age, which was set to be equal to age of Scope.
  • The inst.setAge is set the value to this.age.
  • The inst.getAge return the value of this.age.
  • Moreover the inst.age is public because it was defined in context (2), so that it could be set directly (e.g: inst.age = 40 and inst.getContextAge() === 40);
  • One of new feature of ES6 is lamda arrow =>:it's not a shortcut of function, it self-binds the context of environment into function and it makes my example go wrong.
  function Func() {  
    let age = 26; // variable in scope (1)
    this.age = 99;
    let setContextAge = (value)=> this.age = value;
    let getContextAge =()=> this.age;
    return { age, setContextAge, getContextAge };
  }
  
  let instance = new Func();
  instance.getContextAge(); //99 >> WTF?;
  instance.setContextAge(30);
  instance.getContextAge(); //30;
  instance.age; // 26 >> wait, what??!

this is no longer reference to context of Func(), it's belong to environment of Func() which has this.age = 99. So, please remember that lamda arrow function ()=> is not shortcut of function(), it's more than that.

Apply & Call & Bind

  • Both apply and call are used to run/call a function with other context, which will be accessed by this.

  • apply uses a single array of arguments, while call uses a list of arguments.

  • It sounds complicated, I'll try to describe it by this example:

  1. There are two classrooms (classA and classB), each of them have a student named Christ.
  1. If we want to call Christ of each class, we must have two teachers in both classes to do the same job callChrist('get out!').
  2. But, it's more convenient if we have a supervisor outside two classes, and he will go into each class to do callChrist('get out!').
  3. So, how can he do it? He uses Apply or Call in each class.
  let classA = {
    christ: 'Christ',
    peter: 'Peter',
  }

  let classB = {
    christ: 'Christ',
    tom: 'Tom',
  }

  let supervisor = {
   callChrist: function(message){ console.log('Hey %s, %s', this.christ || 'nobody', message)
    }
  }

  // apply
  supervisor.callChrist.apply(classA, ['get out!']);
  // call
  supervisor.callChrist.call(classB, 'get out!');

Now let see what we can do with apply and call:

I have a function which use to console log name and English name of all people has Lastname is Nguyen ( this should be helpful because everybody in Vietnam is Nguyen, you know).

  function Func(fName) {  
    let lastName = 'Nguyen'
        , firstName = fName;

    function callName() {
        console.log('My name is %s %s', firstName, this.lastName);
    };

    function callEnglishName(eName) {
        console.log('The English name is %s %s', eName, this.lastName);
    };

    return { lastName, callName, callEnglishName };
  }

I create an instance with my name and call function callName and callEnglishName normally:

  let caller = new Func('Hung');  
  caller.callName();  
  /* Result :
  * console.log: 'My name is Hung Nguyen'
  */

  caller.callEnglishName('Kai');
  /* Result :
  * console.log: 'The English name is Kai Nguyen'
  */

Another guy has the same name as me but his lastName is Wu so that he can use my instance caller by set the

  caller.lastName = 'Wu'

  caller.lastName = 'Wu';

  caller.callName();  
  /* Result :
  * console.log: 'My name is Hung Wu'
  */

  caller.callEnglishName('Sun');
  /* Result :
  * console.log: 'The English name is Sun Wu'
  */

But if I want to call my name again, I need to reset the lastName to Nguyen. It's ridiculous! caller's my instance!
Mr.Wu can create his own brand new instance and set the lastName for him, but it doubles the usage of memory (one instance for me, one instance for him). In this case, I'll create an object named wuLastName:

  let wuLasName = {  
    lastName : 'Wu'
  };

And use it as a context to apply/call the function caller.callName() :

  // Let me try to change my last name
  caller.callName.call(wuLasName);  
  /* Result :
  * console.log: 'My name is Hung Wu'
  */

  // 
  caller.callEnglishName.apply(wuLasName,['Sun']);
  /* Result :
  * console.log: 'The English name is Sun Wu'
  */

Or if I want to change the lastName in my English name to Zigg, but keep the lastName of caller.callName(), I could do the same way.
Expected:

  /* Result of callName function :
  * console.log: 'My name is Hung Nguyen'
  *
  *
  * Result of callEnglishName with Name 'Kai' and new 
  LastName 'Zigg';
  * console.log: 'The English name is Kai Zigg'
  */

I'll create an object named changeLastName:

  let changeLastName = {  
    lastName : 'Zigg'
  };

I use changeLastName as context:

  caller.callName();  
  /* Result :
  * console.log: 'My name is Hung Nguyen'
  */

  caller.callEnglishName.apply(changeLastName,['Kai']);  
  // or
  caller.callEnglishName.call(changeLastName, 'Kai');  
  /* Both above has same result:
  * console.log: 'The English name is Kai Zigg'
  */

Hope I didn't make you forget the last one: bind.

  • bind's usage is same with call and apply.
  • Instead of instantly execute the Func, bind return another function which Func was bound in a specific context and we can call it later.
  • bind is same with call: use a list of arguments.
  function callsomeOne(message) {
    console.log('hey %s, %s', this.name, message);
  }

  let personA = { name : 'Tom'}
  , personB = { name : 'Christ'};
  
  let callTom = callsomeOne.bind(personA, 'god bless you')
  , callChrist = callsomeOne.bind(personB, 'bless Tom please');
  callTom(); // 'hey Tom, god bless you'
  callChrist(); // 'hey Christ, bless Tom please'

Conclusion

  • Understand scope and context is a very first step if you want to dig deep into Javascript, besides class - instance - closure - prototype in javascript (The blog about them is in progress).
  • With call, apply and bind, you can use only one function to implement different context(s) instead of creating functions for each one.
  • ES6 need you to understand context and scope to avoid making any stupid mistakes (like me).

All feedback and contribution are welcome, please comment your idea below. Thanks for paying your time for this article.