Easy to understand vue two-way binding principle and implementation

Source: Internet
Author: User
1. Preface

When asked about the two-way data binding principle of Vue, you may blurt out:Object.definePropertyMethod property interception methoddataRead/write of each data in the object is convertedgetter/setterTo notify the view update when the data changes. Although the general principle is summarized in one sentence, the internal implementation method is worth further research. This article analyzes the implementation process of Vue's internal bidirectional binding principle in a simple and easy-to-understand way.

2. Train of Thought Analysis

The so-called mvvm data two-way binding mainly involves updating views of data changes and updating data of views. For example:

To implement these two processes, the key point is how to update the view based on data changes. We can use event monitoring to update data based on view changes. Therefore, we will focus on how to update the view based on data changes.

The key point of Data Change update view is how we know that the data has changed. As long as we know when the data has changed, the problem will be solved, we only need to notify the view update when the data changes.

3. Make the data object "observability"

Each read and write of data can be seen by us, that is, we can know when the data is read or when it is rewritten, we call it "observability" of data changes '.

To change the data to 'observability ', we need to useObject.definePropertyMethod. MDN introduces this method as follows:

The object. defineproperty () method defines a new property directly on an object, or modifies an existing property of an object and returns this object.

In this article, we use this method to make the data "observability ".

First, we define a Data Objectcar:

let car = {        'brand':'BMW',        'price':3000    }

We have defined thiscarBrandbrandYesBMW, PricepriceIt is 3000. Now we can usecar.brandAndcar.priceDirectly read and write thiscarAttribute Value. However, whencarWhen the attribute is read or modified, we do not know. So how can we makecarLet us know that its attributes have been modified?

Next, we useObject.defineProperty()Rewrite the example above:

Let Car = {} let val = 3000 object. defineproperty (CAR, 'price', {Get () {console. log ('price attribute read ') return Val}, set (newval) {console. log ('price Attribute Modified ') val = newval }})

PassObject.defineProperty()MethodcarDefinespriceAnd use the Read and Write attributes respectively.get()Andset()Intercept. Every time this attribute is read or written, it will startget()Andset(). For example:

As you can see,carWe can actively tell us the Read and Write status of its attributes, which also means that thiscarThe data object is "observability.

TocarAll attributes of are become observability. We can write the following two functions:

/*** Converts each item of an object into an observed object * @ Param {object} OBJ object */function observable (OBJ) {If (! OBJ | typeof OBJ! = 'Object') {return;} Let keys = object. keys (OBJ); keys. foreach (key) =>{ definereactive (OBJ, key, OBJ [Key])}) return OBJ ;} /*** convert an object to an object that can be observed * @ Param {object} OBJ object * @ Param {string} key of the object * @ Param {Any} Val a key of the object */function definereactive (OBJ, key, Val) {object. defineproperty (OBJ, key, {Get () {console. log ('$ {key} attribute read'); Return Val ;}, set (newval) {console. log ('$ {key} Attribute Modified'); val = newval ;}})}

Now, we can definecar:

let car = observable({        'brand':'BMW',        'price':3000    })

carThe two attributes of are become observability.

4. Dependency collection

After the data is 'observability ', we know when the data is read or written, we can notify those views that depend on the data when the data is read or written. For convenience, we need to collect all dependencies first. Once the data changes, unified notification updates. In fact, this is a typical "Publish subscriber" mode. The data changes to "publisher" and the dependent object is "subscriber ".

Now, we need to create a dependency collection container, that is, the message subscriber DEP, to accommodate all the "subscribers ". The DEP subscriber is mainly responsible for collecting subscribers. When data changes, the subscriber executes the UPDATE function.

Create the message subscriber Dep:

Class Dep {Constructor () {This. subs = []}, // Add subscriber addsub (sub) {This. subs. push (sub) ;}, // determine whether to add the subscriber depend () {If (dep.tar get) {this.addsub(dep.tar get) }}, // notify the subscriber to update notify () {This. subs. foreach (sub) => {sub. update ()} dep.tar get = NULL;

With the subscriberdefineReactiveTransform the function to implant the subscriber to it:

Function definereactive (OBJ, key, Val) {Let Dep = new Dep (); object. defineproperty (OBJ, key, {Get () {dep. depend (); console. log ('$ {key} attribute read'); Return Val ;}, set (newval) {val = newval; console. log ('$ {key} Attribute Modified'); dep. notify () // notify all subscribers of data changes }})}

From the code point of view, we have designed a Dep class for the subscriber, which defines some attributes and Methods. Here, we need to note that it has a static attribute.target, Which is globally uniqueWatcherThis is a very clever design, because at the same time there can only be one globalWatcherIs calculated, and its own attributessubsYesWatcher.

The DEP subscriber Operation is designed ingetterThis is to makeWatcherIt is triggered during initialization, so you need to determine whether to add a subscriber. InsetterIn the function, if the data changes, all subscribers will be notified, and the subscribers will execute the corresponding updated function.

At this point, the DEP design of the subscriber is complete. Next, we will design the subscriber watcher.

5. subscriber watcher

SubscriberWatcherYou need to add yourself to the subscriber during initialization.Dep, How to add it? We already know the listenerObserverYesgetThe function is executed to add a subscriber.WatherSo we only needWatcherThe correspondinggetFunction to add a subscriber, how to triggergetThe function is no longer simple, as long as the corresponding attribute value is obtained, it can be triggered, the core reason is that we useObject.defineProperty( )For data monitoring. Here is another detail that needs to be processed. We only needWatcherThe subscriber needs to be added only during initialization, so a judgment operation is required. Therefore, you can perform the following operations on the subscriber:Dep.targetSubscriber in the cache. After adding the subscriber, remove it. SubscriberWatcherThe implementation is as follows:

Class watcher {Constructor (Vm, exp, CB) {This. vm = VM; this. exp = exp; this. CB = CB; this. value = This. get (); // Add yourself to the subscription operator}, update () {Let value = This. VM. data [this. exp]; let oldval = This. value; If (value! = Oldval) {This. value = value; this. CB. call (this. VM, value, oldval) ;}, get () {dep.tar get = This; // cache your own LET value = This. VM. data [this. exp] // force execute the get function dep.tar get = NULL in the listener; // release your own return value ;}}

Process Analysis:

SubscriberWatcherIs a class. In its constructor, some attributes are defined:

  • VM:A vue instance object;
  • Exp:YesnodeNodev-modelOrv-on:clickAnd other command attribute values. For examplev-model="name",expYesname;
  • CB:YesWatcherBound update functions;

When we instantiate a renderingwatcherFirstwatcherThe constructor logic will execute itsthis.get()Method, entergetThe function will first execute:

Dep.tar get = This; // cache yourself

In factDep.targetAssign a value to the current RenderingwatcherAnd then execute:

Let value = This. VM. Data [This. Exp] // force the get function in the listener

In this processvmTo trigger Data Objectgetter.

For each object ValuegetterBoth hold onedep, In the triggergetterWill calldep.depend()Method.this.addSub(Dep.target), That is, the currentwatcherSubscribed to this datadepOfsubsThe purpose is to notify future data changes.subsPrepare.

In this way, a dependency collection process has been completed. Is it over now? Actually not. After dependency collection is completed, you needDep.targetRestore to the previous status, that is:

Dep.tar get = NULL; // release yourself

Because the currentvmThe data dependency collection has been completed, then the corresponding RenderingDep.targetIt also needs to be changed.

Whileupdate()A function is called when data changes.WatcherUpdate functions. First passlet value = this.vm.data[this.exp];Obtain the latest data andget()Compare the obtained old data. If they are different, call the UPDATE function.cb.

Now, a simple subscriberWatcherThe design is complete.

6. Test

After the above work is completed, we can perform a real test.

Index.html

<! Doctype HTML> <HTML lang = "en"> 

Observer. js

/*** Converts each item of an object into an observed object * @ Param {object} OBJ object */function observable (OBJ) {If (! OBJ | typeof OBJ! = 'Object') {return;} Let keys = object. keys (OBJ); keys. foreach (key) =>{ definereactive (OBJ, key, OBJ [Key])}) return OBJ ;} /*** convert an object to an object that can be observed * @ Param {object} OBJ object * @ Param {string} key of the object * @ Param {Any} Val a key of the object */function definereactive (OBJ, key, Val) {Let Dep = new Dep (); object. defineproperty (OBJ, key, {Get () {dep. depend (); console. log ('$ {key} attribute read'); Return Val ;}, set (newval) {val = newval; console. log ('$ {key} Attribute Modified'); dep. notify () // notify all subscribers of data changes})} class Dep {Constructor () {This. subs = []} // Add subscriber addsub (sub) {This. subs. push (sub) ;}// determine whether to add the subscriber depend () {If (dep.tar get) {this.addsub(dep.tar get) }}// notify the subscriber to update notify () {This. subs. foreach (sub) => {sub. update ()} dep.tar get = NULL;

Watcher. js

Class watcher {Constructor (Vm, exp, CB) {This. vm = VM; this. exp = exp; this. CB = CB; this. value = This. get (); // Add yourself to the subscription operator} Get () {dep.tar get = This; // cache your own LET value = This. VM. data [this. exp] // force execute the get function dep.tar get = NULL in the listener; // release your own return value;} Update () {Let value = This. VM. data [this. exp]; let oldval = This. value; If (value! = Oldval) {This. value = value; this. CB. Call (this. VM, value, oldval );}}}

Effect:

7. Summary

Summary:

To bind data in two directions, we must first hijack and listen to the data, so we need to set a listener.ObserverTo listen to all attributes. If the attributes change, you need to tell the subscriberWatcherCheck whether updates are required. Because there are many subscribers, we need a message subscriber.DepTo specifically collect these subscribers, and then in the listenerObserverAnd subscriberWatcher.

(End)

Easy to understand vue two-way binding principle and implementation

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.