阅读 1181

vue双向数据绑定的原理

什么是双向数据绑定?

数据变化更新视图,视图变化更新数据


网图 (侵删)

输入框内容变化时,data中的数据同步变化 view => model

data中的数据变化时,文本节点的内容同步变化 model => view

设计思想:观察者模式

Vue的双向数据绑定的设计思想为观察者模式。 Dep对象:Dependency依赖的简写,包含有三个主要属性id, subs, target和四个主要函数addSub, removeSub, depend, notify,是观察者的依赖集合,负责在数据发生改变时,使用notify()触发保存在 subs下的订阅列表,依次更新数据和DOM。

Observer对象:即观察者,包含两个主要属性value, dep。做法是使用getter/setter方法覆盖默认的 取值和赋值操作,将对象封装为响应式对象,每一次调用时更新依赖列表,更新值时触发订阅者。绑定 在对象的__ob__原型链属性上。

new Vue({ 
    el: '#app', 
    data: { count: 100 },
    ... 
});
复制代码

下面我们来看看vue源码中怎么初始化上面这段代码的

初始化函数:initMixin:

    Vue.prototype._init = function (options) { 
        ... 
        var vm = this; 
        ... 
        initLifecycle(vm); 
        initEvents(vm); 
        initRender(vm); 
        callHook(vm, 'beforeCreate');
        // initState就是我们接下来要跟进的初始化Vue参数 
        initState(vm); 
        initInjections(vm); 
        callHook(vm, 'created'); 
        ... 
    };
复制代码

初始化参数 initState:

function initState (vm) { 
    vm._watchers = []; 
    var opts = vm.$options; 
    if (opts.props) { initProps(vm, opts.props); } 
    if (opts.methods) { initMethods(vm, opts.methods); } 
    // 我们的count在这里初始化 
    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); 
    } 
    
}
复制代码
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 */);

复制代码

将data参数设置为响应式:

/*** 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 
    /* 为了防止value不是单纯的对象而是Regexp或者函数之类的,或者是vm实例再或者是不可扩展*/
    ( 
        observerState.shouldConvert && 
        !isServerRendering() && 
        (Array.isArray(value) || isPlainObject(value)) && 
        Object.isExtensible(value) && 
        !value._isVue 
    ) { 
        ob = new Observer(value); 
    }
    if (asRootData && ob) { 
        ob.vmCount++; 
    }
    return ob 
}

复制代码

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; 
    // def函数是defineProperty的简单封装 
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
        // 在es5及更低版本的js里,无法完美继承数组,这里检测并选取合适的函数 // protoAugment函数使用原型链继承,copyAugment函数使用原型链定义(即对每个数组 defineProperty)
        var augment = hasProto ? protoAugment : copyAugment; 
        augment(value, arrayMethods, arrayKeys); 
        this.observeArray(value);
    } else { 
        this.walk(value); 
    }
};
复制代码

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]); 
    }
};
复制代码

Dep类:

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

walk函数:

/**
 * 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]]); 
    } 
};
复制代码

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;
                // 脏检查,排除了NaN !== NaN的影响
                if (newVal === value || (newVal !== newVal && value !== value)) { return }
                if (setter) {
                    setter.call(obj, newVal);
                } else {
                    val = newVal;
                }
                childOb = observe(newVal); dep.notify();
            }
        });
}
复制代码

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();
    }
};
复制代码

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)) {
            // 使用push()方法添加一个订阅者   
            dep.addSub(this);
        }
    }
};

复制代码

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);
        }
    }
}

复制代码

数组的更新检测:

/*
* 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
        });
    });

复制代码

总结:

从上面的代码中我们可以一步步由深到浅的看到Vue是如何设计出双向数据绑定的,最主要的两点:

  • 使用getter/setter代理值的读取和赋值,使得我们可以控制数据的流向。

  • 使用观察者模式设计,实现了指令和数据的依赖关系以及触发更新。

文章分类
前端
文章标签