Objects Orientated in JavaScript
this
- Robyn's BEAST Article
- this references the execution context.
- JavaScript Interpreter begins execution -> Global Execution Context
- When a function is invoked -> Function Execution Context
- A
this-aware
function can have a different context each time it's called, which makes it more flexible & reusable. - Essentially the
this
keyword is for providing something with context, which makes it unique for a given context. - The
this
keyword can't be set how you'd think in an arrow function. It will just behave like a normal variable.
var teacher = "Kyle";
function ask(question) {
console.log(this.teacher, question);
}
function otherClass() {
var myContext = {
teacher: "Suzy"
};
ask.call(myContext, "Why?"); // Suzy Why?
}
otherClass()
Global Context
- globally
this
will reference the window in browser and global in node. - You can always access the global object using
globalThis
.
Function Context
- When a function is invoked it creates a new function context.
- The value of
this
will depends on how the function is invoked.- See below mental model.
- If we invoke a function the following way
someObject.function()
,this
will be bound to whatever is on the left of the.
operator.
const boundGreetMeAncientOne = greetMe.bind(user1); // Returns a function with 'this' explicitly bound to the 'user1' object
Using new
- When we use the new operator the function's
this
is then bound to a new empty object. - What
new
actually does:- Creates a brand new empty object
- Links to that object to another object
- Call function with
this
set to the new object - If function does not return an object, assume the return of
this
.
var favouriteThings = ["Sadness","Goths"];
function listMyFavouriteThings(myFaves) {
this.favouriteThings = myFaves;
console.log(this.favouriteThings);
}
// Invoking `listMyFavouriteThings` function using the `new` operator
// `this` === `{}` (new, empty object)
new listMyFavouriteThings( ["JavaScript", "Linux", "Pastries"]); //Logs: ["JavaScript", "Linux", "Pastries"]
console.log(this.favouriteThings) // Logs: ["Sadness","Goths"];
// Invoking `listMyFavouriteThings` function without the `new` operator
// `this` === `globalThis`
listMyFavouriteThings(["JavaScript", "Linux", "Pastries"]); //Logs: ["JavaScript", "Linux", "Pastries"]
console.log(this.favouriteThings) //Logs: ["JavaScript", "Linux", "Pastries"]
Useful Mental Models
-
What is
this
?- Is it an arrow function?
- Lexical Binding
- Is there a value to the left hand side of the function call?
- Implicit Binding
- Is the function invoked using
call
orapply
?- Explicit Binding
- Was the function created using
bind
?- Explicit Binding
- Was the function called with the
new
operator?new
binding (empty object)
- Window Binding
- Is it an arrow function?
Determination on a function call / order of precedence
- Is the function called by new
- Is the function called by call() or apply()
- bind() essentially uses apply()
- Is the function called on a context object?
- DEFAULT: to the global object (unless in strict mode)
CAUTION: Objects are not a Scope, this can trip you up when dealing with arrow functions in objects.
- Take the example below, the this keyword will just behave as a normal variable in an arrow function, when it looks for a variable it won't see the one in the object, that is not a scope... it will look in global where there isn't anything, hence undefined.
var workshop = {
teacher: "Kyle",
ask: (question) => {
console.log(this.teacher, question);
},
};
workshop.ask("What happened to 'this'?"); // undefined What happened to 'this'?
workshop.ask.call(workshop, "Still no 'this'?"); // undefined Still no 'this'?
- ONLY use arrow functions when you need lexical this
- NOTE: When creating an object class you only need to hard bind methods that use the this keyword
{
function0() {
arr.sort(this.function1.bind(this))
arr.Map(this.function2)
}
function1(){
return this.variable
},
function2() {
return 3 + 5
}
}
- Key thing to notice is when using these methods, I only had to hard bind the method that actually used a this reference inside of it.
- In other words, we only need to hard bind this-aware functions.
Bindings:
- Implicit Binding:
workshop.ask("Why?");
, calling a method from an object (or NamespaceType, etc) - Dynamic Binding: can be called the same as above, but the value will be determined at runtime as it can be dynamically changed for different contexts.
- Explicit Binding:
function.call(context, argumentToPassIn)
- Hard Binding:
workshop.ask.bind(workshop)
- Default Binding:
workshop()
, just a basic function call, will throw a TypeError if function needs athis
input for context. - This point is demonstrated here:
var teacher = "Kyle";
function ask(question) {
console.log(this.teacher, question);
}
function askAgain(question) {
"use strict";
console.log(this.teacher, question);
}
ask("Why?"); // Kyle Why?
askAgain("Why again?") // TypeError
class
- The class feature looks like so:
class Workshop {
constructor (teacher) {
this.teacher = teacher;
}
ask(question) {
console.log(this.teacher, question);
}
}
- If you have a child class, you can refer to a method in the parent with the super keyword
- This is also known as relative polymorphism.
class AnotherWorkshop extends Workshop {
ask(msg) {
super.ask(msg.toUpperCase())
}
}
Prototypes
- A "constructor call" makes an object linked to its own prototype.
- When a class instance is created, it has a link with the class at creation but after created they are separate things.
- Think of relationship between house and house plan (architect design), if you remove a wall on the design, the built house won't lose a wall will it?
- Prototype class, under syntactic sugar:
function Workshop(teacher) { this.teacher = teacher; } Workshop.prototype.ask = function(question) { console.log(this.teacher, question); } var deepJS = new Workshop("Kyle"); var reactJS = new Workshop("Dempsey"); deepJS.ask("Why?"); // Kyle Why? reactJS.ask("Why?"); // Dempsey Why?
- Looking at the above code, when we call ask what JS is going to do is that it will first check the respective objects (deepJS and reactJS) for an ask method... it doesn't have one. So we then check up the prototype chain and find the ask method in Workshop's prototype.
- You can link objects together like so:
function Workshop(teacher) {
this.teacher = teacher;
}
Workshop.prototype.ask = function(question) {
console.log(this.teacher, question);
}
function AnotherWorkshop(teacher) {
Workshop.call(this, teacher);
}
AnotherWorkshop.prototype = Object.create(Workshop.prototype); // Creates the link to the Workshop object.
// If the above line wasn't there, AnotherWorkshop prototype wouldn't be linked to Workshop, it'd just be linked to Object.prototype
AnotherWorkshop.prototype.speakUp = function(msg){
this.ask(msg.toUpperCase());
};
var newJS = new AnotherWorkshop("Kyle");
newJS.speakUp("this is wild"); // Kyle THIS IS WILD
- This linkage happens through the prototype linkage chain, which is a really effective way to link objects... via their prototypes.
- A big takeaway to know what this is referring to, LOOK AT THE CALL SITE.
Inheritance vs Behavior Delegation (OO vs OLOO)
-
Delegation is the pattern of design for what we have just been talking about with prototypes, it's different to classes.
- JavaScripts prototype system is a delegation system, not a class system.
- You can implements a class in a prototype system, but you can't do prototypes in a class based system.
-
OLOO Objects Linked to Other Objects
- Delegated objects looks like the following:
var Workshop = { setTeacher(teacher) { this.teacher = teacher; }, ask(question) { console.log(this.teacher, question); } }; var AnotherWorkshop = Object.assign( Object.create(Workshop), { speakUp(msg) { this.ask(msg.toUpperCase()); } } ); var JSRecentParts = Object.create(AnotherWorkshop); JSRecentParts.setTeacher("Kyle"); JSRecentParts.speakUp("But isn't this cleaner?"); // Kyle BUT ISN"T THIS CLEANER?
Object.create
andObject.assign
are the magic parts here to link the objects together.