Scope, this and Closure

Scope, this and Closure

ยท

4 min read

Scope

Scope is an "enclosed" part of your code that defines rules of visibility of variables and functions. Javascript has two scopes global scope code outside of any function or curly braces and local scope code inside a function.

var globalScope = 1;
function l() {
  var localScope = 1;
}

Scopes are hierarchical so you have access to variables of parent scope but not of child scope

var globalHi = 'Hi';
function l() {
  var localHi = "Hi";
  return globalHi;
}
console.log(l()); // Hi
console.log(localHi); // ReferenceError

var is scoped, but let and constare block scoped (curly braces scoped)

function test() {
  let num = 1;
  if (num === 1) {
    var hi = 'oh hi';
    let name = 'Mark';
    console.log(name);
  }
  console.log(hi);
  console.log(name);
}
test();
// Mark
// oh hi
// RefferenceError: name is not defined

block statement == curly braces

{
  let name = 'Mark';
  console.log(name);
}
console.log(name);
// Mark
// RefferenceError: name is not defined

this is not this

this keyword isn't referring to a current context of the scope like it does in other languages this is a variable that refers to an object you assign to it at run-time. So when you are writing functions like

function sayHi(name) {
  console.log(name);
}
sayHi('Mark')

they actually look more like this

function sayHi(obj, name) {
  this = obj || undefined; // windows in browser, undefined in strict mode
  console.log(name);
}
sayHi(, 'Mark')

There are three ways of assigning objects to this keyword, the most common way is calling a function on an object.

function sayHi() {
  console.log('oh Hi,', this.name);
}
const person1 = {
  name: 'John',
  sayHi
}
const person2 = {
  name: 'Mark',
  sayHi
}
person1.sayHi(); // oh Hi, John
person2.sayHi(); // oh Hi, Mark

Another way of assigning an object to this is by using bind, call or apply

function sayHi() {
  console.log('oh Hi,', this.name);
}
const person1 = {
  name: 'John',
  sayHi
}
const person2 = {
  name: 'Mark',
  sayHi
}
person1.sayHi.call(person2); // oh Hi, Mark
person1.sayHi.apply(person2); // oh Hi, Mark
let hardbindedThis = person1.sayHi.bind(person2);
hardbindedThis(); // oh Hi, Mark

Also, there is new keyword that provides object for this and its used mainly with classes

class Fun {
  constructor() {
    this.name = 'fun';
  }
  sayMyName() {
    console.log(this.name);
  }
}
var a = new Fun();
a.sayMyName();
// fun

but it also can be used without classes

function fun() {
  this.name = 'fun';
  console.log(this.name);
}
new fun();
// kind of equivalent to
fun.call({});

What does next code output?

const obj = {
  name: 'John',
  sayHi: function() {
    console.log(this);
    function fun() {
      console.log(this);
    }
    fun();
  }
}
obj.sayHi();

Correct answer

{name: 'John', sayHi: ฦ’}
window // or undefined in strict mode

this has nothing to do with scope its undefined when the function is nested because you called fun function directly without providing any object to be used as this. So if you what to assign this to second function you can pass it to it with call or apply

const obj = {
  name: 'John',
  sayHi: function() {
    console.log(this);
    function fun() {
      console.log(this);
    }
    fun.call(this);
  }
}
obj.sayHi();

Or you can use arrow function, because arrow function doesn't have this variable, so they treat it as any other variable and just go up in scope hierarchy to find it.

const obj = {
  name: 'John',
  sayHi: function() {
    console.log(this);
    const fun = () => {
      console.log(this);
    }
    fun.call(this);
  }
}
obj.sayHi();

Closure

Closure is a behavior of Javascript when a function preserves reference of its outer scopes. You can see what references your function holds in [[Scopes]] 2021-12-19_02-16.png This is important and powerful because you can return a function, it will come with references to all of its parent variables, so you can create a function with the state, something like "private static" properties in other languages, using closure. -YuK5UYoQ.png Use case examples

function nextNumber() {
  let number = 0;
  return function () {
    return number++;
  }
}
const next = nextNumber();
console.log(next()); // 0
console.log(next()); // 1
console.log(next()); // 2
function canBeInvokedMax(max) {
  let invokeCounter = 0;
  return function () {
    return invokeCounter++ < max ? '๐Ÿ˜ƒ' : '๐Ÿ˜ž';
  }
}
const canBeInvokedOnlyOnce = canBeInvokedMax(1);
console.log(canBeInvokedOnlyOnce()); // ๐Ÿ˜ƒ
console.log(canBeInvokedOnlyOnce()); // ๐Ÿ˜ž
console.log(canBeInvokedOnlyOnce()); // ๐Ÿ˜ž

Quicky about IIFE

IIFE (Immediately Invoked Function Expression) are used in two ways: first imidiatly invoking function

function fun() {
  console.log('Hi, ๐Ÿ‘‹๐Ÿป');
}
fun();
// can be shortened to
(function () {
  console.log('Hi, ๐Ÿ‘‹๐Ÿป');
})();

second Module pattern, basically, you can enclose your "module" do whatever and then return variables and methods that you want user to have access to.

var myModule = (function() {
  var name = 'John';
  function sayHi() {
    console.log('Hi, ' + name);
  }
  return {
    sayHi
  }
})();
myModule.sayHi();
ย