VUE双向绑定原理

目录

响应式原理简析

初始化——init

数据劫持——Observer

         订阅者——Watcher

         解析器——Compile

         MVVM总结


响应式原理简析

Vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。

VUE双向绑定原理_第1张图片

 

初始化——init

根据Vue的生命周期我们知道,Vue首先进行初始化操作;源码在src/core/instance/init.js中

/*初始化生命周期*/
initLifecycle(vm)
/*初始化事件*/
initEvents(vm)
/*初始化render*/
initRender(vm)
/*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
/*初始化props、methods、data、computed与watch*/
initState(vm)
initProvide(vm) // resolve provide after data/props
/*调用created钩子函数并且触发created钩子事件*/
callHook(vm, 'created')

以上代码展示了初始化所需要完成的工作,其中 initState(vm) 初始化props,methods,data,computed和watch

/*初始化props、methods、data、computed与watch*/
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  /*初始化props*/
  if (opts.props) initProps(vm, opts.props)
  /*初始化方法*/
  if (opts.methods) initMethods(vm, opts.methods)
  /*初始化data*/
  if (opts.data) {
    initData(vm)
  } else {
    /*该组件没有data的时候绑定一个空对象*/
    observe(vm._data = {}, true /* asRootData */)
  }
  /*初始化computed*/
  if (opts.computed) initComputed(vm, opts.computed)
  /*初始化watchers*/
  if (opts.watch) initWatch(vm, opts.watch)
}
...
/*初始化data*/
function initData (vm: Component) {
  /*得到data数据*/
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}defi
  ...
  //遍历data中的数据
  while (i--) {
    /*保证data中的key不与props中的key重复,props优先,如果有冲突会产生warning*/
    if (props && hasOwn(props, keys[i])) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${keys[i]}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(keys[i])) {
      /*判断是否是保留字段*/
      /*这里是我们前面讲过的代理,将data上面的属性代理到了vm实例上*/
      proxy(vm, `_data`, keys[i])
    }
  }
  // observe data
  /*这里通过observe实例化Observe对象,开始对数据进行绑定,asRootData用来根数据,用来计算实例化根数据的个数,下面会进行递归observe进行对深层对象的绑定。则asRootData为非true*/
  observe(data, true /* asRootData */)
}

重点分析:initData主要做了两件事,一是将_data上面的数据代理到vm上;二是通过执行 Observe将data内的属性递归变成可观察的,即对data定义的每个属性进行getter/setter操作,这里就是Vue实现响应式的基础;

数据劫持——Observer

Observer是一个数据监听器,其实现核心方法就是Object.defineProperty( )。如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理。

function Observer(data) {
    this.data = data;
    this.walk(data);
}

Observer.prototype = {
    walk: function(data) {
        var self = this;
        //这里是通过对一个对象进行遍历,对这个对象的所有属性都进行监听
        Object.keys(data).forEach(function(key) {
            self.defineReactive(data, key, data[key]);
        });
    },
    defineReactive: function(data, key, val) {
        var dep = new Dep();
      // 递归遍历所有子属性
        var childObj = observe(val);
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get: function getter () {
                if (Dep.target) {
                  // 在这里添加一个订阅者
                  console.log(Dep.target)
                    dep.addSub(Dep.target);
                }
                return val;
            },
           // setter,如果对一个对象属性值改变,就会触发setter中的dep.notify(),通知watcher(订阅者)数据变更,执行对应订阅者的更新函数,来更新视图。
            set: function setter (newVal) {
                if (newVal === val) {
                    return;
                }
                val = newVal;
              // 新的值是object的话,进行监听
                childObj = observe(newVal);
                dep.notify();
            }
        });
    }
};

function observe(value, vm) {
    if (!value || typeof value !== 'object') {
        return;
    }
    return new Observer(value);
};

// 消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后在属性变化的时候执行对应订阅者的更新函数
function Dep () {
    this.subs = [];
}
Dep.prototype = {
  /**
   * [订阅器添加订阅者]
   * @param  {[Watcher]} sub [订阅者]
   */
    addSub: function(sub) {
        this.subs.push(sub);
    },
  // 通知订阅者数据变更
    notify: function() {
        this.subs.forEach(function(sub) {
            sub.update();
        });
    }
};
Dep.target = null;

分析:在Observer中,在拦截getter内添加订阅器的时候出现了Dep.target不知道哪里冒出来的??当写到Watcher的时候,你就会发现,这个Dep.target是来源于Watcher,因为每一个Watcher强制执行Observer内的getter方法获取属性值用于渲染。

订阅者——Watcher

Watcher就是一个订阅者。用于将Observer发来的update消息处理,执行Watcher绑定的更新函数。

function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    this.value = this.get();  // 将自己添加到订阅器的操作
}

Watcher.prototype = {
    update: function() {
        this.run();
    },
    run: function() {
        var value = this.vm.data[this.exp];
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal);
        }
    },
    get: function() {
        Dep.target = this;  // 缓存自己
        var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
        Dep.target = null;  // 释放自己
        return value;
    }
};

分析:vm就是SelfValue对象,相当于Vue中的new Vue的一个对象。exp是node节点的v-model或v-on:click等指令的属性值。如v-model="name",exp就是"name"。cb,就是Watcher绑定的更新函数。
在Watcher的getter函数中,Dep.target指向了自己,也就是Watcher对象。在getter函数中

var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数。

//这里获取vm.data[this.exp] 时,会调用Observer中Object.defineProperty中的get函数
get: function getter () {
     if (Dep.target) {
        // 在这里添加一个订阅者
        console.log(Dep.target)
        dep.addSub(Dep.target);
     }
     return val;
}

解析器——Compile

Compile主要的作用是把new SelfVue 绑定的dom节点,(也就是el标签绑定的id)遍历该节点的所有子节点,找出其中所有的v-指令和" { {}} ".

1.如果子节点含有v-指令,即是元素节点,则对这个元素添加监听事件。(如果是v-on,则node.addEventListener('click'),如果是v-model,则node.addEventListener('input'))。接着初始化模板元素,创建一个Watcher绑定这个元素节点。

2.如果子节点是文本节点,即" { { data }} ",则用正则表达式取出" { { data }} "中的data,然后var initText = this.vm[exp],用initText去替代其中的data

MVVM总结

Vue是通过ObserverDepWatcherCompile等实现MVVM

a、Observer实现对数据劫持;主要是通过Object.defineProterty()对data内的属性进行递归拦截。在getter方法中将订阅器添加至订阅器的数组中;在setter方法中对新值进行再次拦截,并通知订阅者进行更新。
b、订阅器Dep包涵订阅器的收集数组,添加订阅者的方法addsub()和通知方法notify()。
c、订阅者Watcher 包涵获取属性值和数据更新update方法。在接收Observer通过dep传递过来的数据变化通知Compile进行view update。

d、Compile实现指令解析,初始化视图,并订阅数据变化,绑定好更新函数。

 

摘于文章:

https://funteas.com/topic/5a809f5847dc830a0e4690c2

https://www.jianshu.com/p/f194619f6f26

 

你可能感兴趣的:(Vue,Vue,双向数据绑定,MVVM,响应式原理)