Recently, I've been working to understand JavaScript better with the goal of evolving how I write code, communicate ideas, and build stuff.
I've often heard something to the effect of, "JavaScript has classes, but it is not a class-based language". Along with the implication that, the use of classes "bends" the language rather than accepting it for what it is.
This felt like information that could be true, but that I didn't understand because...well JavaScript has classes. And it was my first language. I didn't come up learning Java or even complete a traditional computer science program at a four year institution.
Eventually, I was exposed to more information about JavaScript's Object.prototype
. This great, ancestral object sits at the top of the prototype chain passing on properties and characteristics to other objects. Things started to become a little clearer, but also still kind of confusing. Prototypal inheritance sounded cool, but it felt like a distinction without a difference.
Then, I watched this workshop by Kyle Simpson the author of the You Don't Know JavaScript series. He demonstrated a way of linking objects together without using the new
keyword or the syntactic sugar that is class
.
I was intrigued. I wanted to try it. I also wanted to apply more of what I've been learning. So I wrote some logic for generating different types of tasks using the factory pattern and linked objects.
var TaskType = {
TODO: "To Do",
IN_PROGRESS: "In Progress",
COMPLETED: "Completed"
}
var Task = {
logTaskInfo(){
console.log(`Title: ${this.title} | Details: ${this.details} | Status: ${this.status}`)
}
}
function TaskFactory(){
return createTask;
function createTask(type, title, details){
var isInvalidType = !Object.values(TaskType).includes(type);
if(isInvalidType){
throw new Error('Invalid Task Type')
}
return Object.assign(Object.create(Task), {
title,
details,
status: type
})
}
}
var factory = TaskFactory();
var task1 = factory(TaskType.TODO, "Wash Dishes", "Load the dishwasher and scrub large pots and pans.");
var task2 = factory(TaskType.IN_PROGRESS, "Write Code", "Apply newly acquired concepts like IFFE, Factory and OLOO");
var task3 = factory(TaskType.COMPLETED, "Walk Dog", "Take Soleil out for her morning walk.");
task1.logTaskInfo();
task2.logTaskInfo();
task3.logTaskInfo();
From TC39, Object.assign ( target, ...sources )
copies the values of all of the enumerable own properties from one or more source objects to a target object. And Object.create ( O, Properties )
creates a new object with a specified prototype.
So, with these lines of code:
Object.assign(Object.create(Task), {
title,
details,
status: type
})
Object.Create(Task)
creates a brand new object and links it to the existing Task
object. And that brand new object is assigned a title, details, and a status.
Now, when task1
invokes logTaskDetails
it works because logTaskDetails
exists on the object that task1 is linked to.
Okay, "pretty cool", I thought. You could make the argument that this approach is more explicit than using something like the new
keyword, but what's the big deal? But then things got really interesting.
This style becomes the foundation for behavior delegation or "dynamic composition". Where objects with different concerns are linked together and can share methods. It's less top-down and more peer-to-peer.
Here is an example Simpson presents with an AuthController and a LoginFormController:
var AuthController = {
authenticate(){
server.authenticate(
[this.username, this.password], this.handleResponse.bind(this)
)
},
handleResponse(resp){
if(!resp.OK){
this.displayError(resp.msg);
}
}
}
var LoginFormController = Object.assign(
Object.create(AuthController),
{
onSubmit(){
this.username = this.$username.val();
this.password = $this.$password.val();
this.authenticate()
},
displayError(msg){
alert(msg)
}
}
)
I thought this was a really cool example, although I had to read through the code a few times to understand what was happening.
Here, the same way that task1
was linked to the Task
object, LoginFormController
is linked to AuthController
. When an imaginary login form is submitted, LoginFormController's
onSubmit
method is called. Inside onSubmit
the authenticate()
method is called.
LoginFormController
doesn't have an authenticate method, but the object it is linked to (AuthController
) does. So, the method runs with this
still referring to LoginFormController
. Inside authenticate
, this.username
and this.password
are still referring to values on LoginFormController
and are passed to the server along with the callback handleResponse
. Now, handleResponse
is bound to LoginFormController
with .bind
as it is passed to the server. So whenever it is called, it will also be referring to LoginFormController
even though that isn't where it was defined. When handleResponse
is invoked, if the response was not okay, then it will call the display
method. display
has a this
that references LoginController
because that was the this
handleResponse
was bound to.
So, in summary, onSubmit
which is defined in LoginController
calls authenticate
a method defined in AuthController
. authenticate
passes a callback, handleResponse
, which is defined in Authcontroller
, but bound to LoginController
. And handleResponse
calls display
which is defined in LoginController.
This logic requires a strong understanding of this
(no pun intended). And even though I think it's pretty cool, given this task, I would not have thought to structure the code this way. It's a very different way of thinking than I'm used to.
A classic case of, "the spirit is willing, but the flesh is weak". I'm going to keep working at it though.
In the meantime, please let me know what you think. Is this a style you have used or are interested in using? How do you think the code fares as far as readability?
Image by Alltechbuzz_net from Pixabay
Top comments (0)