‘This’ That and Dispatch
There are numerous blogs and videos about Javascript’s this
keyword. In my opinion, virtually all of them fall short and
fail to even mention dispatch
or binding
.
As always, I encourage polyglot programming. So I’ll be showing examples is Javascript, C#, Go and Rust.
Back to the basics
First, let us cover some really basic but critical Javascript.
function greet(name) {
console.log(`hello, ${name}`);
}
function greet() {
console.log(`hello, stranger`);
}
greet("natescode");
Which function is called?
If you said the first function, that is incorrect.
Javascript ONLY cares about the function name, not the parameters. In the case of duplicate function names, the last one defined wins; just like CSS.
Methods vs Functions
Now, when we get to methods, somethings change. Let us make our previous example use methods.
const Alice = {
greet: function (name) {
console.log(`hello, ${name}`);
},
};
const Bob = {
greet: function greet() {
console.log(`hello, stranger`);
},
};
var person = Alice;
person.Greet("natescode");
Now, even though Alice
and Bob
both have greet
functions, there is no longer a name conflict! Hey, I though Javascript functions had to have unique names? They do, but not methods! Because methods are functions that are related to a specific function. They have their own scope / context.
Now let’s change the example to use the object’s context.
const Alice = {
name: "Alysan",
};
const Bob = {
name: "Robert",
};
function greet() {
console.log(`hello, stranger. I am ${this.name}`);
}
// they'll both use the same function as a method
Alice.greet = greet;
Bob.greet = greet;
Alice.greet("NatesCode");
You’ll see I changed the greet
methods back to a single function. I did this to highlight the fact that methods are just functions executed within an specific object’s context. Technically, ALL javascript functions are methods since everything is on the window
object. The plain greet
function will not return null
or undefined
because the name
exists as window.name
; it is usually an empty string though.
You’ll the two lines that associate the Greet
function with both the Alice
and Bob
objects.
When we call their respecitve greet
methods, the output changes based on the object the function was called on. Methods, unlike functions, have a special parameter called the receiver. This is really obvious in Go
where the receiver parameter comes before the method name, the same order we call it in.
// 'this' could be called anything since it is just a parameter.
// normally it would be called `person` by convention
func (this Person) greet(name string){
fmt.Println("hello, " + this.name)
}
Still unclear? Oh MY!
To clear up how this
works. I show you an example in English. Here
is a script for you to read.
Hello, my name is [your name], and today . . .
Did you say “your name” or did you say your actual name? “you” and “me” and “my” are relative! They don’t refer to the same person all the time. It depends WHO says it!
Let’s change the code example to read more like English and say “my name is Alice”
const Alice = {
name: "Alysan",
greet: function () {
const my = this;
console.log(`My name is ${my.name}`);
},
};
const Bob = {
name: "Robert",
greet: function () {
const my = this;
console.log(`My name is ${my.name}`);
},
};
Alice.greet(); // My name is Alysan
Bob.greet(); // My name is Robert
Dispatch
Ok. So hopefully now this
is starting to make sense. We are now going to take the minecar into the cave of deeper knowledge! Let’s talk about dispatch!
First a code example. Can you tell me which method will be run?
const Alice = {
name: "Alysan",
greet: function () {
const my = this;
console.log(`My name is ${my.name}`);
},
};
const Bob = {
name: "Robert",
greet: function () {
const my = this;
console.log(`My name is ${my.name}`);
},
};
// we are picking Alice or Bob at random.
// Nothing else important about this code
var friend = Math.random() > 0.5 ? Alice : Bob;
friend.greet();
Hopefully, you answered no, I cannot predict something inherently random!
. My point exactly. This is called dynamic dispatch
or late binding
. Those are just fancy terms for Let's figure out which method to call on which object when we RUN the code, not before
In statically typed languages, we know for 100% certainty which type friend
will be, before the code runs.
C# Dispatch
Let’s translate that example into C#
class Alice {
string name = "Alysan";
public Alice(){
}
public void greet(){
const my = this;
Console.Writeline($"My name is {my.name}")
}
}
class Bob {
string name = "Robert";
public Bob(){
}
public void greet(){
const my = this;
Console.Writeline($"My name is {my.name}")
}
}
// csharp does have VAR but that just infers the type
// it isn't dynamic
var friend = Math.random() > 0.5 ? Alice : Bob;
friend.greet();
This code won’t even compile. We’ll get an `` error. Because we don’t know which type friend is going to be. The most direct translation would change the last two lines to look like this.
Using dynamic
** NEVER DO THIS **
// NEVER, EVER, EVER, for the love of keeping your job
// and not being replaced by AI, NEVER EVER DO THIS!!!!
dynamic friend = Math.random() > 0.5 ? Alice : Bob;
friend.greet();
Now friend’s type is not set until we run the code. This will work. Just don’t do it! Promise? Okay.
Using interface
// we define an interface that has a greet method
interface IGreetable{
void greet();
}
class Alice: IGreetable {
string name = "Alysan";
public Alice(){
}
public void greet(){
const my = this;
Console.Writeline($"My name is {my.name}")
}
}
class Bob: IGreetable {
string name = "Robert";
public Bob(){
}
public void greet(){
const my = this;
Console.Writeline($"My name is {my.name}")
}
}
// csharp does have VAR but that just infers the type
// it isn't dynamic
IGreetable friend = Math.random() > 0.5 ? Alice : Bob;
friend.greet();
Now, friend is of a shared type iGreetable
. An interface is a type that defines which methods should exist on an object. Now we don’t care if we get an Alice
or Bob
, we are ONLY looking for any object that fits
the IGreetable
interface, in this case that means anything with a greet
method that take no parameters and returns nothing.
NOTE: statically typed languages do differentiate between methods/functions with different number of parameters, parameter types and return types.
greet
with a return type or parameter ofstring name
would be a different method all together
Using Inheritance
// we define an interface that has a greet method
class Person {
public void greet(){
const my = this;
Console.Writeline($"My name is {my.name}")
}
}
class Alice: Person {
string name = "Alysan";
public Alice(){
super();
}
}
class Bob: Person {
string name = "Robert";
public Bob(){
super();
}
}
// csharp does have VAR but that just infers the type
// it isn't dynamic
Person friend = Math.random() > 0.5 ? Alice : Bob;
friend.greet();
Inheritance look similar to interfaces. Bob
and Alice
both inherit from the Person
object. You can think of inheritance as a compiler-assisted copy-paste. So Alice doesn’t have her own greet
method anymore but that is okay because her parent does! Similar to have Javascript’s prototypal inheritance works.
Using Sum Types
I wasn’t going to include this one, but man it is too good to pass up. I’m going to write this example in language of the gods, Rust
.
// define a Person struct with a greet method
struct Person {
name: String,
}
impl Person {
fn greet(&self) {
println!("My name is {}", self.name);
}
}
// define an enum for friend type
enum Friend {
Alice,
Bob,
}
// create Alice and Bob structs
let alice = Person { name: String::from("Alysan") };
let bob = Person { name: String::from("Robert") };
// randomly choose between Alice and Bob
let friend = if rand::random() { Friend::Alice } else { Friend::Bob };
// call greet method on chosen friend
match friend {
Friend::Alice => alice.greet(),
Friend::Bob => bob.greet(),
}
** BTW the above Rust code was generated by Chat GPT. I had it translate the previous C# code into rust but use enums for the friend type. The FUUUUTUUURE! (INSERT Spongebob Squidward “future!” GIF)
You’ll see that Rust defines an enum type which is just a type that combines two more types, type addition aka Algebraic Data Types.
Friend is of the shared type. Then at runtime, we check which variant of Friend we have and call the approprait method. This isn’t dynmic dispatch because we KNOW the type of Friend before the code runs. There is some dynamic checking however which comes with a small performance overhead. The enum is like a C union but with a bit that tells us which variant we currently have.
Effectively, enums make types into values. Like a string name
in C# must always be a string but can change to different values.
And Beyond!
We could go further and start talking about VTABLES and FAT POINTERS etc but I think that is best saved for another blog post and video. I really hope this helps. Constructive feedback is encouraged and welcome.
Until next time
return 0;