Glossary
Lexical scope aka lexical context or scope.
Determines which variables and functions can be accessed by a part of a code. For example, const
variable, defined inside a code block can’t be accessed outside that block.
{
const blockScopedVar = 1
}
console.log(blockScopedVar) // Error. blockScopedVar is not defined
In this example, lexical scope of console.log()
does not contain blockScopedVar
.
Under the hood, lexical scope is defined by lexical Environment chain.
Execution context
In simplified terms, it is an environment, in which the current function is running. JavaScript code is executed by dividing the code into ‘pieces’ and running them sequentially. These ‘pieces’ are execution contexts. Execution context created for global context and per each function call. When a function being invoked, execution context is created. Execution context contains a value of this
, lexical scope, and other internal stuff required to run this function.
"this" keyword
this
keyword is usually used in methods or classes, making it easy to point the function to an object’s properties we want to use. this
in JS is just a reference to an object. This reference can be set:
- implicitly (when a new execution context is being created)
- explicitly (with
call()
,apply()
,bind()
methods).
The implicit value of this
is determined by where in the code this
is being used (in what context). This is causing most confusion and the way to sort this out is to know what this
contexts exist and how they affect the value of this
.
Implicit contexts
At the creation phase of execution context, the value of this
is assigned automatically, based on where this
in code is being used. Sometimes this is called this
context (not to be confused with execution context or lexical context)
function executionContext1 () {
console.log(this.a) // `this` is used in globally invoked function context
}
const obj1 = {
a: 1,
executionContext2() {console.log(this.a)} // `this` is used in method context
};
executionContext1() // execution context with `this` pointing to global object being created to execute this function
executionContext2() // execution context with `this` pointing to `obj1` being created to execute this function
this
contexts, determining its value:
- Global context (when this placed outside of functions or classes): this -> global object
- Function context (when this placed inside the function)
- Globally invoked function: this -> global object
- Method: this -> object, on which method being called
- Constructor function: this -> instance object
- Arrow function: Exception. Do not have this. this -> inherited from outer function scope
- Event handler context
- Class context
Global context
If this
is used outside of functions or classes, it will be pointing to the global environment (window
in a browser or global
in NodeJS)
console.log(this) // [object Window]
Function context
this
is determined by how the function is called, not how it is declared.
Except for an arrow function, when a value of this
is determined by where an arrow function is declared.
Globally invoked function
If a function is called In a global context, this
refers to the global object.
function printThis() {
console.log(this)
}
printThis() // Window // or undefined in strict mode
Notice that the value of this
in a globally invoked function can be altered by the "use strict"
directive.
Method
If a function is called as a method of an object, this
will refer to that object (on the left side of the dot before a method). For a.b.c.func()
this
will be c
const obj1 = {
a: 1,
log() {console.log(this.a)}
};
const obj2 = {
a: 2,
log: obj1.log
};
obj2.log(); // 2, because method is being called on `obj2`
Constructor Function
If this
is used inside a constructor function, it points to the created instance of this constructor.
function Person(name, birthYear) {
this.name = name // `this` will reference the instanse, so `name` will be added to `beatrix`
this.birthYear = birthYear // `this` will reference the instanse, so `birthYear` will be added to `beatrix`
this.describe = function() { // `describe()` will be added to `beatrix`
console.log(`${this.name} was born in ${this.birthYear}.`)
// `this` will be pointing to the object on which `describe` will be called (method context)
}
}
const beatrix = new Person('Beatrix', 2005)
beatrix.describe() // "Beatrix was born in 2005."
console.log(beatrix.name) // "Beatrix"
Arrow Function
Arrow functions are a special case, which makes this
more tricky. They do not have their own this
and inherit this
value from the closest enclosing function scope. this
value depends on how a function is declared, not how it is called (like with “normal” functions). In other words, this
in arrow functions will point to the ‘owner’ of a function, despite where this function is being invoked. It means, that calling arrow functions as methods might result in an unexpected behavior:
const obj = {
a: 1,
arrow: () => console.log(this.a), // there is no enclosing function, so this will point to global object
arrowNested:
function() {
return () => console.log(this.a) // will inherit the value of this from arrowNested() function, so it will point to `obj`
}
}
obj.arrow() // undefined
obj.arrowNested()() // 1
// value of `this` in arrow function is determined by where function being declared
In some cases, such a behavior of arrow functions might be handy, i.e. when used in event handlers.
Event Handler
In the browser, there is a special this
context for event handlers. In an event handler called by addEventListener
, this
will refer to the element, on which a listener is placed. In other words, this
is pointing to event.currentTarget
const button = document.createElement('button')
button.textContent = 'Click me'
document.body.append(button)
button.addEventListener('click', function(event) {
console.log(this) // <button>Click me</button>
})
Sometimes it is not convenient and arrow functions being used to ‘preserve’ this
value, no matter what ‘caller’ is:
const person = {
name: "Beatrix",
sayHello: function() {
document.getElementById("myButton").addEventListener("click", () => {
console.log("Hello, " + this.name); // `this` inherited from `sayHello` and always will point to `person`, no matter where it will be invoked (arrow function context)
});
}
};
person.sayHello(); // "Hello, Beatrix"
Class
When this
is used inside a class, its value depends on what type of methods and field initializer it’s being used in:
- In public and private methods and field initializers
this
behaves like in constructor functions: pointing to an instance - Static methods cannot be accessed via instance’s
this
. It must be called on class itself
class C {
instanceField = this;
static staticField = this;
}
const instance = new C();
console.log(instance.instanceField === instance); // true
console.log(instance.staticField); // undefined because static methods cannot be accessed via instance's `this`
console.log(C.staticField === C); // true
Explicit Context of this
The value of this
can be set manually by using special function methods. Note, that Arrow functions do not have their own this
, so these methods can’t be used with them.
.call()
and .apply()
are used to call the function with a specified value of this
:
const obj1 = {
a: 1,
log() {console.log(this.a)}
};
const obj2 = {
a: 2,
};
obj1.log.apply(obj2); // 2, because `this` explicitely set to `obj2`
obj1.log() // 1
.bind()
is used to create a constant binding of this
to a specified object:
const obj1 = {
a: 1,
log() {console.log(this.a)}
};
const obj2 = {
a: 2,
};
const getObj2Var = obj1.log.bind(obj2);
getObj2Var() // 2, because getObj2Var `this` binded to obj2
Self-check
const obj = {
a: 1,
b: function() {
console.log(this.a)
},
c: () => {
console.log(this.a)
},
d: function() {
return () => {
console.log(this.a);
}
}
}
// what the output will be?
console.log(obj.a)
obj.b()
const b = obj.b
b()
obj.b.apply({a: 2})
obj.c()
obj.c.apply({a:2})
obj.d()()
obj.d.call({a:2})()
obj.d().call({a:2})
Answer
console.log(obj.a) // 1
obj.b() // 1 // method context
const b = obj.b
b() // undefined // This is globally invoked function, not a method, so `this` is pointing to global object
obj.b.apply({a: 2}) // 2
obj.c() // undefined // Arrow function do not have outer function, so `this` reference global object
obj.c.apply({a:2}) // undefined // Arrow functions can't use .call() .apply() or .bind()
obj.d()() // 1 // first obj.d() being executed (method context)
// and returned arrow function. Then arrow function being executed in global context,
// but `this` in arrow function always points to the `this` of the encapsulating function.
// In this case `this` of `d()` pointing to `obj`, so `this` in arrow function
// will always reference `obj`, no matter where it called
obj.d.call({a:2})() // 2 // same as above, but here `this` of `d()` was explicitely set
obj.d().call({a:2}) // 1 // `call()` method was applied to arrow function, which is not making any sense.
References
Happy coding! Feedback is appreciated.
Top comments (0)