That shouldn’t work! Oh, Vue binds all methods?

Jason
6 min readMay 21, 2019

--

During a conversation with a fellow developer, I saw some code that I didn’t think should work based on the fundamental this scoping/binding rules.

What I didn’t realise was I had hand-waved my understanding of Vue components, their methods, and how they work.

So, given I saw something I didn’t understand, I decide I should do some googling and check stack overflow (you know, the standard investigation process the every developer does).

But alas, no answer! So I dove into VueJS source code and eventually found the function that explained my WTF moment, and as always, it seems so obvious in retrospect.

To fully appreciate it, I’ll replicate the behaviour in the form of a magic trick. Let’s start by revisiting some of the fundamental rules of JS, and slowly move into Vue-land.

“Behold! The mysterious floating orb”. Just like Vue, it’s all amazing magic.. until it’s not.

A simple JS example.

Consider the following code:

`changeMessage` is called on the `thing` object, what is the result?

What is the result of console.log(thing.message)?

As most would expect, the code prints “changed!”. (See example on CodePen).

Easy. Let’s move on!

No worries!

Knowing “this”.

Now, what happens if we create a reference to the method and call it like so:

`changeMessage` is called by a reference to the method, what is the result?

In this case, what is the result of console.log(thing.message)?

The code prints “unchanged”. (See example on CodePen).

If you didn’t expect that result, you might want to review the rules around how this works when methods are called, and to do that, I highly recommend Kyle Simpson’s “You don’t know JS” book series.

Effectively, changeMessage was detached from thing and invoked at the global scope. So, during the execution of detachedChangeMessage (the detached reference), this will point to the window object.

To verify this, you can type window.message into the console, and you’ll see that window now has an attribute of message set to the string “changed!”.

Still with me? Fundamental rules still in tact? Great!

We’re all in this together! We got this!

Into Vue-land!

Now, let’s see what happens when we write similar code again, but in Vue:

What does `console.log(this.message)` print when we call a reference to `exampleMethod`?

How about the log statement in this code above? We’re calling a reference, right? So what does the log message say?

It prints “changed!”. (See example on Codepen).

Okay, so it wasn’t a great magic trick. But did you expect that? Or were you like me and this dog?

That… doesn’t seem right,

Uhh… Vue? What are you doing?

Following from our previous rules, this.message should still have the value of “unnchanged”, right?

Well, you’re correct, but Vue adds a little bit of magic to make this possible. I’ll show you where Vue does this, but first, I’ll explain what it’s doing.

It turns out: during component instantiation, Vue binds the component object to all declared methods.

If you don’t understand what that means, have you heard of the bind() function? It’s very powerful with many other uses, but we only need to consider one particular usage.

A little bit of a .bind()

Consider anotherFunction below:

const anotherFunction = someFunction.bind(someObject); 

In the code above, anotherFunction will effectively be the same function as someFunction, but whenever the this keyword is used within anotherFunction, it will point at someObject, due to our bind() call.

Okay, so where does Vue do this binding magic?
As I mentioned earlier, during component instantiation.

Into the core

As a side-note, never be afraid of looking into how frameworks work and fit together. You’ll always find something interesting, even if it’s just more questions.

It’s just code. Take a look around!

Our quick journey starts in src/core/index.js where we export the Vue object. This is the object that is used when you run the familiar new Vue({…}) line.

But it actually pulls the Vue object from another file:

import Vue from ‘./instance/index’

So, let’s look into instance/index.js. Here we find that Vue is a very simple function that first ensures we checks we wrote new Vue(…) instead of just Vue(…) (I’ll explain why that’s important later), and then just calls this._init(options).

That seems simple enough, and I promise you, we aren’t far from getting to the point of this article.

Your time is important to us… so hurry up and keep reading!

Where _init is added?

So, where does the_init function from this._init(options) come from?

It is added by the initMixin(Vue) line that sits below the Vue() function, and that initMixin function comes from the import at the top of the file:

import { initMixin } from './init'

Alright, so taking a look at initMixin in core/instance/init.js, it looks like the main point of that function is to add the _init function we saw earlier. As you can see, _init is quite large, so below I’ve created a slimmed down version with only the parts we actually care about.

The parts of `initMixin` that we care about. Type declarations have also been removed.

Basically, we only care about the initState method. But it’s very helpful to know what the vm is.

vm can be thought of as your component instance. When we write new in front of our Vue() component instantiation, we create an empty object, and Vue adds on all the attributes needed to make that object into a component.

So, what does initState (from core/instance/state.js) do to the vm object?

Once again, trimmed to only show the things we care about.

It retreives an $options object from the vm, checks if it has a methods attribute, and if so, passes it along with our vm to initMethods.

But what is $options?

What is $options and why should I care?

vm.$options

vm.$options is whatever you passed to your new Vue() call.

So, for example, if you write:

var app = new Vue({
el: '#app',
data: { message: 'Hello Vue!' },
methods: {
printLog() { console.log("Hi"); }
},
})

$options will be the following object

{
el: '#app',
data: { message: 'Hello Vue!' },
methods: {
printLog() { console.log("Hi"); }
},
}

So when we write $options.methods we’re actually getting the object that we declared our methods inside, ie. the following object:

{
printLog() { console.log("Hi"); }
}

Everything still making sense? Great. Back to the Vue code!

initMethods

So vm (the component) and our methods were being passed to initMethods. Once again I’ll remove logging, so initMethods actually becomes very short.

`initMethods` simplified.

All the code does is loop over each attribute of your methods object, and if they’re actually functions, it will bind the vm (the component) to that method, and then add the method onto the vm object.

Did you catch that?

Yep, that’s where the magic happens. Because that function has the vm bound to it, we’re able to later detach the method from the component, and call it with the binding still there.

So our earlier example:

Again

… will print “changed!”, without us doing any binding ourselves.

This all seems super obvious to me in retrospect, but if I had this gap in my knowledge around how Vue makes its component work, I know many others will probably have the same gap.

Please note that I am not suggesting you should or will ever need to detach methods in this way, but as we’ve seen, it’s definitely possible.

So now, go forth, and use this knowledge only for good!

Thanks for reading!

I can’t believe it’s been 7 whole months since my last technical blog post. I ran into the issue described in this post at the end of last year, but other life events have popped up since then which kept pushing it to the bottom of my todo list.

Speaking of things that have been taking up my time, I’ve spent the past four months learning React Native and have just launched my first app 😮 into the App Store + Google Play Store.

My app “Potent Playbooks” is a simple tool to help keep you on track with any set of tasks you might have, whether it’s your work todo list, your morning routine, a workout, or for parents to help keep their kids get ready.

Potent Playbooks @ potentapps.com — Medium made this image blurry. 😕

Give it a try, and I’d love to hear some feedback (via the app or twitter @potentapps).

Anyhoo, glad I’ve got it out now, and I hope it can help you be more productive! 🔥

If you’re interested in React Native let me know! I’m still pretty new to it but I might write be able to write something about it if given the right topic. Cheers!

--

--