What is two-way data binding?

Data changes update views, and views change update data




Network map (Invasion and deletion)

When the content of the input box changes, the data in data changes synchronously view => model

When the data in data changes, the content of the text node changes synchronously model => view

Design idea: Observer mode

Vue’s bidirectional data binding is designed with the observer model in mind. Dep objects: Dependency Dependency – short for Dependency Dependency, including three main attributes ID, subs, target and four main functions addSub, removeSub, Depend, notify. Use notify() to trigger the subscription list saved under subs, updating the data and DOM in turn.

An Observer object contains two main attributes: value, dep. The idea is to override the default values and assignments with getter/setter methods, encapsulate the object as a reactive object, update the dependency list with each invocation, and trigger the subscriber when the value is updated. Binds to the object’s __ob__ stereotype chain property.

new Vue({ 
    el: '#app'.data: { count: 100},... });Copy the code

How to initialize the above code in vue source code

Init function: initMixin:

    Vue.prototype._init = function (options) {... var vm =this; . initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm,'beforeCreate');
        // initState is the initialization Vue parameter we will follow up with
        initState(vm); 
        initInjections(vm); 
        callHook(vm, 'created'); . };Copy the code

Initialization parameter initState:

function initState (vm) { 
    vm._watchers = []; 
    var opts = vm.$options; 
    if (opts.props) { initProps(vm, opts.props); } 
    if (opts.methods) { initMethods(vm, opts.methods); } 
    // Our count is initialized here
    if (opts.data) { 
        initData(vm); 
    } else { 
        observe(vm._data = {}, true /* asRootData */); 
    }
    if (opts.computed) { 
        initComputed(vm, opts.computed); 
    } 
    if(opts.watch) { initWatch(vm, opts.watch); }}Copy the code
InitData:
function initData (vm) { 
    var data = vm.$options.data; 
    data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}; 
    if(! isPlainObject(data)) { data = {}; }...// observe data 
    observe(data, true /* asRootData */);

Copy the code

Set the data parameter to reactive:

/*** Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */
function observe(value, asRootData) {
    if(! isObject(value)) {return } 
    var ob; 
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) 
        { ob = value.__ob__; } 
    else if 
    /* In case value is not a pure object but a Regexp or function or vm instance or is not extensible */( observerState.shouldConvert && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && 
        Object.isExtensible(value) && ! value._isVue ) { ob =new Observer(value); 
    }
    if (asRootData && ob) { 
        ob.vmCount++; 
    }
    return ob 
}

Copy the code

The Observer:

/*** * Observer class that are attached to each observed * object. Once attached, the observer converts target * object's property keys into getter/setters that * collect dependencies and dispatches updates. */
var Observer = function Observer(value) {
    this.value = value; 
    this.dep = new Dep(); 
    this.vmCount = 0; 
    The def function is a simple wrapper around defineProperty
    def(value, '__ob__'.this);
    if (Array.isArray(value)) {
        // In ES5 and lower versions of JS, arrays cannot be perfectly inherited, so check and select the appropriate function // protoAugment function uses the prototype augment chain inheritance, while copyAugment function uses the prototype augment chain definition (i.e. each array defineProperty).
        var augment = hasProto ? protoAugment : copyAugment; 
        augment(value, arrayMethods, arrayKeys); 
        this.observeArray(value);
    } else { 
        this.walk(value); }};Copy the code

ObserverArray:

/*** Observe a list of Array items. */ 
Observer.prototype.observeArray = function observeArray (items) { 
    for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); }};Copy the code

Dep class:

/** * A dep is an observable that can have multiple * directives subscribing to it. */ 
 var Dep = function Dep () { 
    this.id = uid$1+ +;this.subs = []; 
 };
Copy the code

Walk function:

/** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. */
Observer.prototype.walk = function walk(obj) { 
    var keys = Object.keys(obj); 
    for (var i = 0; i < keys.length; i++) { 
        defineReactive?1(obj, keys[i], obj[keys[i]]); }};Copy the code

DefineReactive:

/** * Define a reactive property on an Object. */
function defineReactive? 1(obj, key, val, customSetter) {
    var dep = new Dep();
    var property = Object.getOwnPropertyDescriptor(obj, key);
    if (property && property.configurable === false) { return }
    // cater for pre-defined getter/setters
    var getter = property && property.get;
    var setter = property && property.set;
    var childOb = observe(val);
    Object.defineProperty(obj, key,
        {
            enumerable: true.configurable: true.get: function reactiveGetter() {
                var value = getter ? getter.call(obj) : val;
                if (Dep.target) {
                    dep.depend();
                    if (childOb) { childOb.dep.depend(); }
                    if (Array.isArray(value)) { dependArray(value); }}return value
            },
            set: function reactiveSetter(newVal) {
                var value = getter ? getter.call(obj) : val;
                // Dirty check, excluding NaN! == NaN effect
                if(newVal === value || (newVal ! == newVal && value ! == value)) {return }
                if (setter) {
                    setter.call(obj, newVal);
                } else{ val = newVal; } childOb = observe(newVal); dep.notify(); }}); }Copy the code

Dep. Target&depend () :

// the current target watcher being evaluated. 
// this is globally unique because there could be only one 
// watcher being evaluated at any time. 
Dep.target = null;
Dep.prototype.depend = function depend() {
    if (Dep.target) {
        Dep.target.addDep(this); }}; Dep.prototype.notify =function notify() {
    var subs = this.subs.slice();
    for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};Copy the code

AddDep () :

/** * Add a dependency to this directive. */
Watcher.prototype.addDep = function addDep(dep) {
    var id = dep.id; if (!this.newDepIds.has(id)) {
        this.newDepIds.add(id); this.newDeps.push(dep); if (!this.depIds.has(id)) {
            // Add a subscriber using the push() method
            dep.addSub(this); }}};Copy the code

DependArray () :

    /** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */
function dependArray(value) {
    for (var e = (void 0), i = 0, l = value.length; i < l; i++) {
        e = value[i];
        e && e.__ob__ && e.__ob__.dep.depend();
        if (Array.isArray(e)) { dependArray(e); }}}Copy the code

Array update detection:

/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype * /
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
['push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'].forEach(function (method) {
        // cache original method 
        var original = arrayProto[method];
        def(arrayMethods, method, function mutator() {
            var arguments$1 = arguments;
            // avoid leaking arguments: 
            // http://jsperf.com/closure-with-arguments 
            var i = arguments.length;
            var args = new Array(i);
            while (i--) {
                args[i] = arguments$1[i];
            }
            var result = original.apply(this, args);
            var ob = this.__ob__;
            var inserted;
            switch (method) {
                case 'push':
                    inserted = args;
                    break
                case 'unshift':
                    inserted = args;
                    break
                case 'splice':
                    inserted = args.slice(2);
                    break
            }
            if (inserted) { ob.observeArray(inserted); }
            // notify change 
            ob.dep.notify();
            return result
        });
    });

Copy the code

Conclusion:

From the above code, we can see how Vue designs two-way data binding step by step. The two main points are:

  • Reading and assigning values using getter/setter proxies allows us to control the flow of data.

  • The observer pattern is used to realize the dependency between instruction and data and trigger update.