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 const
are 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]]
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.
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();