A couple of months ago, I attended an in-person meetup at called . It was really awesome because we got to learn about from its creator, . Instead of just teaching us how to use Vue, he showed us how to actually implement a few parts of it. Reactivity was the part that interested me the most, so, after the class, I dug into to learn more about exactly how its reactivity system works. In this guide, I’ll explain how Vue’s reactivity system is implemented, and show how you to make your own working reactivity system. Frontend Masters Vue.js Advanced Features from the Ground Up Vue.js Evan You Vue’s source code The Problem of Reactivity What is reactivity? I really like how Evan explained it in his talk, so I’ll use his examples. Say you have a variable a . let a = 3 Now, let’s say you have another variable , such that . b b = a * 3 let b = a * 3console.log(b) // 9 That’s working just fine. But what happens if you need to change ? a a = 5console.log(b) // 9 Even though changed, stayed the same. Why? Because you never changed . If you want to make sure is still , you’d have to do it like this: a b b b a * 3 a = 5b = a * 3console.log(b) // 15 Now, this is working, but it would be annoying to have to type out every time changes. We could solve this problem by wrapping the update to in a function b = a * 3 a b let b;function onUpdate() {b = a * 3} let a = 3onUpdate()console.log(b) // 9 a = 5onUpdate()console.log(b) // 15 But, this still isn’t a very nice way of doing things. While it’s not too problematic in this example, imagine if we had 10 different variables, that all had a potentially complex relation to another variable or variables. We’d need a separate method for each variable. Instead of this awkward and imperative API, it’d be nice to have a simpler, more declarative API that just does what we want it to do. Evan compared it to a spreadsheet, where we can update one cell and know that any cell that depends on the one we updated will automatically update itself. onUpdate() Solutions The good thing is that people have already come up with a number of solutions to this reactivity problem. In fact, each of the three major web development frameworks provides a solution to reactivity: : Create a function , and use that whenever we need to update . Then, inside , call a render function that updates the view to display the proper value of . React’s State Management setState() a setState() b : Create a function , that runs through every property it’s tracking and checks if it’s changed since the last time it was checked. If it finds an updated property (e.g. ), then it updates every property that uses the updated property (e.g. ). Then, run the function every few milliseconds and whenever it’s logical. Angular’s Dirty Checking detectChanges() a b detectChanges() : Add ES5 getters and setters to each tracked property. Whenever a tracked property is accessed, mark the function that accessed the property as a “subscriber”. Whenever the property is changed, notify each subscriber of the change. Vue’s reactivity system We’re going to implement Vue’s reactivity system in pure JavaScript. Before we get into the code, let’s go into some more details about exactly what we’re building. What We’re Building Diagram of how Vue’s reactivity system works. Source: https://vuejs.org/v2/guide/reactivity.htm We’re going to create a class, called , that takes in two properties: a and a . The value getter can be any function that has a return value. Example: Watcher value getter callback let a = 3const getter = () => a * 3 The getter will probably have , or variables it depends on to get its value. In the example above, is the only dependency of the getter function. Getter functions can have multiple dependencies, though. For example, has both and as dependencies. dependencies a () => x * y x y Whenever a dependency of the getter function changes, we’ll automatically run the callback function, passing in the current value and the previous value. This callback can do anything, from just logging the value to displaying the value in a div. Finally, we’ll create a function, that adds change detection to a property on an object. We’ll also create a function that adds change detection to all properties on an object. This will allow properties of the object to be used as dependencies. We will implement this change detection the same way Vue does: by defining ES5 . While this approach does have some limitations (described in the ), it’s an extremely efficient and simple way of getting reactivity, since the JavaScript engine is ultimately providing the change detection. defineReactive() walk() getters and setters docs The unreleased instead of getters and setters, but I believe the rest of Vue’s reactivity system won’t change that much. Footnote: Vue 3 will use ES6 Proxies When we’re finished, we’ll have a general-purpose reactivity API. Here’s an example of its usage: Deps The first step is to implement the class. short for dependency, is a wrapper around a value. Our implementation will be directly based on . Each instance maintains a list of subscribers, or , that all want to know whenever the dep’s value changes. These subscribers are instances of the class, which we will implement in the next section. Each instance is responsible for calling each subscriber’s method whenever the dep’s value changes. Dep Dep, Vue’s class Dep Dep subs Watcher Dep update() Even though instances are responsible for alerting subscribers of changes to a value, each instance doesn’t actually know what value it’s watching. So, it each instance has a method, which lets it know when its value changes. We’ll talk more about who calls later, but for now just assume it gets called whenever the watched value changes. Here’s a working implementation: Dep Dep Dep notify() notify() Dep What is , and when does it get a value? is a instance that lets the instance know who’s using its value_._ This is necessary because the instance needs to register itself as a dependency of the target watcher. There are two functions, and , that manage the current . Here’s what these look like: Dep.target Dep.target Watcher Dep Dep pushTarget() popTarget() Dep.target We’ll discuss these methods more in the next section. Watchers According to the : source code A watcher parses an expression, collects dependencies, and fires callbacks when the expression value changes. This is used for both the $watch() api and directives. The class takes in a and a , and stores an array of dependencies of the value computed by the getter function. It tracks the values of the dependencies, and runs the callback function whenever any of these dependencies change. Here’s an example: Watcher getter function callback function let a = 5, b = 4 const getter = () => a + bconst callback = (val) => console.log(val) const watcher = new Watcher(getter, callback) a = 6 // 10 is logged to the console In Vue’s code, the class has several methods, but, for our purposes, we just need to implement three of them: Watcher This method calls the getter function supplied in the constructor to figure out what the initial value is. Before it calls the getter, it sets itself as the current target watcher, using the method. This makes it so all values used in the getter function will add the instance as a subscriber. This is important because the instance needs to be linked to its dependencies in some way so it can be notified whenever the value of one of its dependencies changes. get() . Dep pushTarget() Watcher Watcher _._This method adds itself as a subscriber to the given dependency. This method is called by the method, which we’ll discuss in more detail in the next section. addDep(dep) Dep#depend() This method calls the callback function supplied in the constructor with the old value and new value as arguments. It gets used when the method calls on each of its subscribers after its value changes. update() . Dep#notify() update Here’s the code for our class: Watcher defineReactive() According to the , “defines a reactive property on an Object”. This is done by adding getters and setters to a given property of a given object. Each property has a instance associated with it. Whenever a reactive object’s property is accessed, the getter calls which adds the current target as a subscriber to the property’s instance. Whenever the property is changed, the setter calls which calls the method of each of the subscribers to the property’s Here’s , based on the source code: source code defineReactive() Dep Dep#depend(), Watcher Dep Dep#notify() update() Dep . defineReactive() How it all works together Now we’ve written each part of our reactivity system, but how does it all work? Each part of the system is so interconnected with all the other parts that it can be difficult to understand. Let’s walk step by step through what happens in an example usage of our class. Watcher We’ll start by setting everything up: const foods = { apple: 5 } // make foods reactive, register deps for each propertywalk(foods) // Instantiate the watcher, which takes a getter and a callbackconst foodsWatcher = new Watcher(() => foods.apple,() => console.log('change')) First, the constructor for the class runs the following: Watcher this.value = this.get() Here’s : Watcher#get() pushTarget(this) // Imported from dep.jsconst value = this.getter()popTarget() // Imported from dep.js return value First, it calls the function, which assigns (the ) to . Then, it calls the first function passed to the constructor. The getter for the just returns the value of . Since was made reactive by it will also run the reactive getter: pushTarget() this foodsWatcher Dep.target this.getter() , Watcher foodsWatcher foods.apple foods.apple defineReactive() , // adds Dep.target as a subscriber to the property's dep instancedep.depend() return value This registers as a subscriber to the instance associated with . So there’s now a connection between the and . foodsWatcher Dep foods.apple foodsWatcher foods.apple How is this helpful? Let’s say we change . foods.apple foods.apple = 6 Doing this will call the setter on . The setter runs , which calls on each of the dep’s subscribers. Since the is a subscriber to the instance, the call will trigger the update method on . What does do? foods.apple dep.notify() update() foodsWatcher Dep dep.notify() foodsWatcher Watcher#update() update() {const value = this.get()const oldValue = this.valuethis.value = value this.cb(value, oldValue)} It updates its knowledge of the current value, and then calls the callback supplied in the constructor. Remember that the callback we specified was () => console.log('change') So, when we change , our reactivity system will let us know! The cool thing is that this happened without any dirty checking every millisecond. It happened without us having to explicitly set the state. It just , without us having to think about it at all, just like a spreadsheet. That’s what makes Vue’s reactivity system so incredible. **foods.apple** worked If you want to see all the code for our reactivity system in one place, I made a with a really cool demo. Thanks for reading! plunker Resources The I went to (if you have a Frontend Masters subscription) Frontend Masters course Vue’s docs on reactivity A in 2016 on reactivity talk Evan gave Vue’s (the ultimate reference) source code