
<div id="app"> <div>Price: ${{ price }}</div> <div>Total: ${{ price*quantity }}</div> <div>Taxes: ${{ totalPriceWithTax }}</div> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> var vm = new Vue({ el: '#app', data: { price: 5.00, quantity: 2 }, computed: { totalPriceWithTax() { return this.price * this.quantity * 1.03 } } }) </script> price changes, the engine needs to perform three actions:price value on the webpage.price is multiplied by quantity , and output the resulting value to the page.totalPriceWithTax and, again, put what it returns on the page.
price changes, and how the engine tracks what happens on the page. What can be observed here is not similar to the work of a regular JS application. let price = 5 let quantity = 2 let total = price * quantity // 10 price = 20; console.log(`total is ${total}`) 10 .
total recalculated when the price or quantity variables change. That is, if the system of reactivity were applied when executing the above described code, not 10, but 40 would be output to the console:
price would not bring the number 40 to the console. In order for the total figure to be recalculated when the price or quantity changes, we will need to create a reactivity system on our own and thereby achieve the behavior we need. The path to this goal we will break into several small steps.total indicator is calculated, which will allow us to perform its recalculation when the price or quantity variable values change.price or quantity indicators have changed, you will need to call the saved code to recalculate the total . It looks like this:
total , and also create a mechanism for storing functions that we may need later. let price = 5 let quantity = 2 let total = 0 let target = null target = function () { total = price * quantity } record() // , target() // target variable, and then call the record function. We will talk about it below. I would also like to note that the target function, using the syntax of ES6 arrow functions, can be rewritten as follows: target = () => { total = price * quantity } record function and the data structure used to store the functions: let storage = [] // target function record () { // target = () => { total = price * quantity } storage.push(target) } record function, we store the target function (in our case, it is { total = price * quantity } ) in the storage array, which allows us to call this function later, possibly using the replay function, the code of which is shown below. This will allow us to call all the functions stored in storage . function replay () { storage.forEach(run => run()) } storage array and execute each of them. price = 20 console.log(total) // 10 replay() console.log(total) // 40 let price = 5 let quantity = 2 let total = 0 let target = null let storage = [] function record () { storage.push(target) } function replay () { storage.forEach(run => run()) } target = () => { total = price * quantity } record() target() price = 20 console.log(total) // 10 replay() console.log(total) // 40 
target variable, and which receives notifications in case we need to re-execute these functions. class Dep { // Dep - Dependency constructor () { this.subscribers = [] // , // notify() } depend () { // record if (target && !this.subscribers.includes(target)){ // target // this.subscribers.push(target) } } notify () { // replay this.subscribers.forEach(sub => sub()) // - } } storage array, we now store our anonymous functions in the subscribers array. Instead of the record function, the depend method is now called. Also here, instead of the replay function, the notify function is notify . Here's how to run our code using the Dep class: const dep = new Dep() let price = 5 let quantity = 2 let total = 0 let target = () => { total = price * quantity } dep.depend() // target target() // total console.log(total) // 10 - price = 20 console.log(total) // 10 - , dep.notify() // - console.log(total) // 40 - target .Dep object for each variable. In addition, it would be good to encapsulate the behavior of creating anonymous functions somewhere, which should be called when updating the relevant data. Perhaps this will help us an additional function, which we call watcher . This will lead us to replace this function from the previous example with a new function: let target = () => { total = price * quantity } dep.depend() target() watcher function, replacing this code, will look like this: watcher(() => { total = price * quantity }) watcher function, the code of which is presented below, we can perform several simple actions: function watcher(myFunc) { target = myFunc // target myFunc dep.depend() // target target() // target = null // target } watcher function accepts, as an argument, the myFunc function, writes it to the global target variable, calls dep.depend() to add this function to the list of subscribers, calls this function and resets the target variable. price = 20 console.log(total) dep.notify() console.log(total) target as a global variable, instead of passing this variable to our functions, if necessary. We have good reason to do just that, you will understand later.Dep . What if we need each of our variables to have its own class object Dep ? Before we continue, let's move the data we are working with into object properties: let data = { price: 5, quantity: 2 } price and quantity ) has its own internal object of class Dep .
watcher function like this: watcher(() => { total = data.price * data.quantity }) data.price property, we need the Dep object of the price property to put the anonymous function (stored in the target ) in its subscriber array (by calling dep.depend() ). In addition, since we also work with data.quantity , we need the object Dep quantity property to put an anonymous function (again, stored in the target ) into its array of subscribers.
data.price property, then the corresponding anonymous function should only go into the repository of the Dep object of this property.
dep.notify() for functions that are subscribed to price property changes? This will be needed when changing the price . This means that when our example is completely ready, the following code should work for us.
price or quantity ). This will allow, when this happens, to store the target function in an array of subscribers, and, when the corresponding variable changes, to execute the function stored in this array. let data = { price: 5, quantity: 2 } Object.defineProperty(data, 'price', { // price get() { // console.log(`I was accessed`) }, set(newVal) { // console.log(`I was changed`) } }) data.price // data.price = 20 // 
internalValue , which we will use to store the current price value. let data = { price: 5, quantity: 2 } let internalValue = data.price // Object.defineProperty(data, 'price', { // price get() { // console.log(`Getting price: ${internalValue}`) return internalValue }, set(newVal) { console.log(`Setting price to: ${newVal}`) internalValue = newVal } }) total = data.price * data.quantity // data.price = 20 // 
data object with getters and setters. Here we will use the Object.keys() method, which returns an array of keys of the object passed to it. let data = { price: 5, quantity: 2 } Object.keys(data).forEach(key => { // data let internalValue = data[key] Object.defineProperty(data, key, { get() { console.log(`Getting ${key}: ${internalValue}`) return internalValue }, set(newVal) { console.log(`Setting ${key} to: ${newVal}`) internalValue = newVal } }) }) let total = data.price * data.quantity data.price = 20 data object have getters and setters. This is what will appear in the console after running this code.
total = data.price * data.quantity and it retrieves the value of the price property, we need the price property to “remember” the corresponding anonymous function ( target in our case). As a result, if the price property is changed, that is, it is set to a new value, this will result in calling this function to repeat the operations performed by it, since it knows that a certain line of code depends on it. As a result, operations performed in getters and setters can be thought of as follows:Dep class you already know in this description, you get the following:dep.depend() is called to save the current target function.dep.notify() is called to rerun all saved functions. let data = { price: 5, quantity: 2 } let target = null // - , class Dep { constructor () { this.subscribers = [] } depend () { if (target && !this.subscribers.includes(target)){ this.subscribers.push(target) } } notify () { this.subscribers.forEach(sub => sub()) } } // , // Object.keys(data).forEach(key => { let internalValue = data[key] // // Dep const dep = new Dep() Object.defineProperty(data, key, { get() { dep.depend() // target return internalValue }, set(newVal) { internalValue = newVal dep.notify() // } }) }) // watcher dep.depend(), // function watcher(myFunc){ target = myFunc target() target = null } watcher(() => { data.total = data.price * data.quantity }) 
price and quantity properties have become reactive! All code that is responsible for forming a total , when the price or quantity changes, is re-executed.
is written, containing getters and setters? Now he should be familiar to you. Each instance of the component has an instance of the observer method (blue circle), which collects dependencies on getters (red line). When, later, the setter is called, it notifies the observer method, which causes the component to be re-rendered. Here is the same scheme, provided with explanations relating it to our development.
Dep class that collects functions using the depend method, and, if necessary, calls them again using the notify method.watcher function that allows you to control the code that we run (this is the target function) that you may need to save in a Dep object.Object.defineProperty() method to create getters and setters.Source: https://habr.com/ru/post/418633/
All Articles