vue 响应式原理

 

 

Vue 采用声明式编程替代过去的类 Jquery 的命令式编程,而且可以侦测数据的变化,更新视图。这使得咱们能够只关注数据自己,而不用手动处理数据到视图的渲染,避免了繁琐的 DOM 操做,提升了开发效率。不过理解其工做原理一样重要,这样能够回避一些常见的问题,下面咱们来介绍一下 Vue 是如何侦测数据并响应视图的。vue

 

Object.definePropertynode

Vue 数据响应核心就是使用了 Object.defineProperty 方法( IE9 + ) 。编程

var obj = {};dom

Object.defineProperty(obj,'msg', {函数

  get () {this

    console.log('get');双向绑定

  },代理

  set (newVal) {server

    console.log('set', newVal);对象

  }

});

obj.msg // get

obj.msg = 'helloworld' // set hello world

 

取 obj 对象中 msg 的值时会调用 get 方法,给 msg 赋值时会调用 set 方法,并接收新值做为其参数。

这里提一句,在 Vue 中咱们调用数据是直接 this.xxx ,而数据实际上是 this.data.xxx,原来 Vue 在初始化数据的时候会遍历 data 并代理这些数据。

 

Object.keys(this.data).forEach((key) => {

    this.proxyKeys(key);

});

 

proxyKeys (key) {

    Object.defineProperty(this, key, {

        enumerable: false,

        configurable: true,

        get() {

            return this.data[key];

        },

        set(newVal) {

            this.data[key]= newVal;

        }

    });

}

                   更多精彩内容,就在QQ群434623999

上面能够看到,取 this.key 的值实际上是取 this.data.key 的值,赋值同理。

如今,咱们已经知道如何去检测数据的变化,而且作出一些响应了。

观察者模式 ( 发布者-订阅者模式 )

vue 的响应式系统依赖于三个重要的类:Dep 类、Watcher 类、Observer 类。

Dep 类做为发布者的角色,Watcher 类做为订阅者的角色,Observer 类则是链接发布者和订阅者的纽带,决定订阅和发布的时机。

咱们先看下面的代码,来对发布者和订阅者有个初步的了解。

 

class Dep {

    constructor() {

        this.subs= [];

    }

 

    addSub(watcher) {

        this.subs.push(watcher);

    }

 

    notify() {

        this.subs.forEach(watcher=> {

            watcher.update();

        });

    }

}

 

class Watcher {

    constructor() {

    }

 

    update() {

        // 接收通知后的处理方法

    }

}

 

const dep = new Dep(); // 发布者 dep

const watcher1 = new Watcher(); // 订阅者1 watcher1

const watcher2 = new Watcher(); // 订阅者2 watcher2

dep.addSub(watcher1);// watcher1 订阅 dep

dep.addSub(watcher2);// watcher2 订阅 dep

dep.notify(); // dep 发送通知

 

上面咱们定义了一个发布者 dep,两个订阅者 watcher一、watcher2。让 watcher一、watcher2 都订阅 dep,当 dep 发送通知时,watcher一、watcher2 都能作出各自的响应。

如今咱们已经了解了发布者和订阅者的关系,那么剩下的就是订阅和发布的时机。何时订阅?何时发布?想到上面提到的Object.defineProperty ,想必你已经有了答案。

咱们来看 Observer 类的实现:

 

class Observer {

    constructor(data) {

        this.data= data;

        this.walk();

    }

 

    walk() {

        Object.keys(this.data).forEach(key=> {

            this.defineReactive(this.data, key, this.data[key]);

        });

    }

 

    defineReactive(data, key, value) {

        const dep = newDep();

 

        if (value && typeof value === 'object' ) {

            newObserver(value);

        }

 

        Object.defineProperty(data, key, {

            enumerable: true,

            configurable: true,

            get() {

                if(Dep.target) {

                    dep.addSub(Dep.target); // 订阅者订阅 Dep.target 即当前 Watcher 类的实例(订阅者)

                }

                returnvalue;

            },

            set(newVal) {

                if(newVal === value) {

                    return false;

                }

                value = newVal;

                dep.notify(); // 发布者发送通知

            }

        });

    }

}

 

在 Observer 类中,为 data 的每一个属性都实例化一个 Dep 类,即发布者。而且在取值时让订阅者(有多个,由于 data 中的每一个属性均可以被应用在多个地方)订阅,在赋值时发布者发布通知,让订阅者作出各自的响应。

这里须要提的是 Dep.target,这实际上是 Watcher 类的实例,咱们能够看看 Watcher 的详细代码:

 

class Watcher {

    constructor(vm, exp, cb) {

        this.vm =vm;

        this.exp =exp; // data 属性名

        this.cb =cb; // 回调函数

 

        // 将本身添加到订阅器

        this.value= this.getValue();

    }

 

    update() {

        const value = this.vm.data[this.exp];

        const oldValue = this.value;

        if(value !== oldValue) {

            this.value= value;

            this.cb.call(this.vm, value, oldValue); // 执行回调函数

        }

    }

 

    getValue() {

        Dep.target = this; // 将本身赋值给 Dep.target

        const value = this.vm.data[this.exp]; // 取值操做触发订阅者订阅

        Dep.target = null;

        returnvalue;

    }

}

 

Watcher 类在构造函数中执行了一个 getValue 方法,将本身赋值给 Dep.target ,而且执行了取值操做,这样就成功的完成了订阅操做。一旦数据发生变化,即有了赋值操做,发布者就会发送通知,订阅者就会执行本身的 update 方法来响应此次数据变化。

数据的双向绑定

数据的双向绑定即数据和视图之间的同步,视图随着数据变化而变化,反之亦然。咱们知道 Vue 是支持数据的双向绑定的,主要应用于表单,是经过 v-model 指令来实现的。而经过上面介绍的知识咱们是能够知道如何实现视图随着数据变化的,那么如何让数据也随着视图变化而变化呢?其实也很简单,只要给有 v-model 指令的节点监听相应的事件便可,在事件回调中来改变相应的数据。这一切都Compile 类中完成,假设有一个 input 标签应用了 v-model 指令,在开始编译模板时,遇到 v-model 指令时会执行:更新 dom 节点的值,订阅者订阅,事件监听。

 

compileModel(node, vm, exp) {

    let val = vm[exp];

 

    // 更新内容

    this.modelUpdater(node, val);

 

    // 添加订阅

    new Watcher(vm, exp, (value) => {

        // 数据改变时的回调函数

        this.modelUpdater(node,value);

    });

 

    // 事件监听

    node.addEventListener('input', (e) => {

        const newValue = e.target.value;

        if (val=== newValue) {

            return false;

        }

        vm[exp] = newValue;

        val = newValue;

    });

}

当咱们在文本框中输入数据时,会给原有 data 中的某个属性 a 赋值,这时候会触发发布者发起通知,那么全部属性 a 的订阅者都可以同步到最新的数据。 更多精彩内容,就在QQ群434623999

相关文章
相关标签/搜索