博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
vue2 双向绑定实现原理
阅读量:6967 次
发布时间:2019-06-27

本文共 19099 字,大约阅读时间需要 63 分钟。

都知道vue中实现了数据和视图的双向绑定,但具体是如何实现的呢?

今天就说说 我阅读的vue2中的代码实现的个人所得,如果说错了,欢迎指正。

注:我阅读的vue2代码的版本是v2.2.6,且都是以单文件的方式展示列子, 可以结合下一篇续给出的实际例子结合起来看,更容易理解

 

话不多说,首先上结论,然后再通过图以及代码进行详细解释

结论:m->v: 利用观察者模式,当数据改变的时候通知其观察者进行更新,

     v->m: 利用事件绑定对数据进行设值

 

实现过程:通过Object.defineProperty的方法定义 get和set方法对数据的操作拥有代理权,并且每个属性都会有一个对应的依赖,

每个vm组件都会有一个或多个watcher,当获取vm或者data的数据的时候就会调用get方法,如果这个时候有watcher正在运行观察,

那么就把这个依赖和此watcher都加入到各自的对象下的相关队列中去

1)model到view:当设置vm或者data对象的数据的时候,就会调用set方法,

如果这次结果 和上次的value不同,就让此依赖中的所有watcher加入到scheduler(调度者)的更新队列中去,

等待nextTick更新scheduler的队列,调用watcher的update方法,然后运行getter方法比较value值和之前保存的value值是否相同,

如果不同,则调用cb(回调函数),对应vm来说getter是vm.update(vm.render()),当调用getter的时候,整个过程就完成了model到view的更新了

2:view到model:一般用v-model方法进行实现,默认的是绑定dom组件的input事件(这个看源码的实现,如果你设置option.model.event的值的话,

事件就会变成你设置的值),当触发此事件的时候,会调用你绑定的值进行设置,就又回到1)过程了,当然也可以自己绑定自己关心的事件实现设值的效果

上图:

 

可以从结论中看到,其中有3个比较重要的部分,data对应的dep和vm对应的watcher以及把它们进行关联的Object.defineProperty定义的get和set方法,

而实现这一切的基础就是调用Object.defineProperty对属性设置get和set代理方法,所以vue2只能支持ie8以上,因为ie8及一下没有这个方法,也没法用shim的方式来实现

 

详细解释

一. watcher

export default class Watcher {  vm: Component;  expression: string;  cb: Function;  id: number;  deep: boolean;  user: boolean;  lazy: boolean;  sync: boolean;  dirty: boolean;  active: boolean;  deps: Array
; newDeps: Array
; depIds: Set; newDepIds: Set; getter: Function; value: any;

  constructor (vm: Component, expOrFn: string | Function, cb: Function, options?: Object) {}

run(){}    get(){}    update(){}    addDep(){}    cleanupDeps{}    evaluate(){}    teardown(){}          }

注:为了避免太长不方便看,方法的具体实现就没有帖出来了,如果有兴趣的可以去源码看看

 

简称观察者,通常意义的解释,就是对某些事物感兴趣而去观察他的人,用在vue2里面就是对一些表达式结果感兴趣而进行观察的对象,

用来统计更新vm的updateComponent方法,computed属性或者watch属性中的expOrFn(表达式或者方法)

 

watcher对象有3个比较重要的属性:

  getter:关心的表达式或者方法

  value: 运行getter的结果,在new watcher的时候如果lazy为false就会调用getter一次来初始化这个值

  cb:当运行getter方法得到的value值和之前的value值不同的时候,会调用cb,对于vm组件来说就是更新组件

还有一些其他属性:

  id: watcher的唯一标示,当对一个周期内的所有watcher执行更新的时候,会先根据这个id 从小到大进行排序,而且父级的vm的对应的id总是小于子集

  lazy:是否懒更新, 也就是当它关注的表达式中有相关数据改变的时候,它才更新,不然就一直用value值,像computed中的watcher的lazy就是true

  dirty:是否已经脏了,如果脏了,当获取这个值的时候就重新调用getter获得新值,否则直接返回value的值,和lazy有关系

  user: cb回调函数是否是用户传进来的,如果是的话执行这个回调就会用try catch的方式,以避免出错

  active: 这个watcher是否是活动的,如果是活动才会去运行getter

  deps:对应的所有依赖,是一个数组

  newDeps:这次更新后,收集到的下一轮更新所相关的依赖

  depIds:所有依赖的id,是一个set对象

  newDepIds: 下一轮的依赖id所对应的set对象

 

注:这里面的deps的作用个人觉得是:当vm销毁的时候或者运行了一此get方法的时候,从相关的依赖中删除自己,当某些需要的时候,重新和依赖关系,

  目前只看到computed的watcher 在获取值的时候,如果watcher的dirty为true,就会重新和deps里面的依赖重新建立关系,其他地方就没看到有使用了- -!

 

先上一个例子,下面会用到:

注: 上述代码是为了展示 watcher而强行拼凑的代码,所以有重复的地方,不要在意这些细节

 

有3个地方会有对应的watcher

1.一个vm组件会对应一个watcher,这个watcher关注的是vm的_render方法,每个vue文件的template里面的内容都会被编译成一个_render方法,

  并且里面用到的属性或者computed都会转化成_vm.name 或者_vm.infoName 的代理形式,具体的转化过程在dep里面说到

2.computed对象里面的每个属性,都有一个对应的watcher,如上例:infoName,infoSex都会有一个对应的watcher,并且生成的watcher的lazy为true,

 即它们都是懒更新的,只有里面用到的相关数据出现变化的时候,这个watcher才会执行getter方法

3.watch对象里面的每个属性,都有一个对应的watcher,如上例:sex,name,"resultObj.allInfo"都会有一个对应的watcher

  "resultObj.allInfo" 这个对应的watcher需要特殊说明下它的getter,先看代码

if (typeof expOrFn === 'function') {    this.getter = expOrFn;  } else {    this.getter = parsePath(expOrFn);    if (!this.getter) {      this.getter = function () {};}var bailRE = /[^\w.$]/;function parsePath (path) {  if (bailRE.test(path)) {    return  }  var segments = path.split('.');  return function (obj) {    for (var i = 0; i < segments.length; i++) {      if (!obj) { return }      obj = obj[segments[i]];    }    return obj  }}/// function get(){
  if (this.user) {    try {      value = this.getter.call(vm, vm);    } catch (e) {    handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));   }  } else {    value = this.getter.call(vm, vm);  } }

如果初始化watcher的时候传的expOrFn 是function,那么这个getter就直接是这个function,否则就就行转化,运行parsePath方法得到一个function

当然这个function有一个判断,是路径形式的才行,当运行getter方法的时候,传入vm作为obj,那么其实resultObj.allInfo 得到的是 vm.resultObj.allInfo的值,

当然 不可能直接vm["resultObj.allInfo"]或者vm."resultObj.allInfo"这样得到,所以用了一个循环来实现

 

这3种对应的watcher创建的时候分别为:

1) 使用$mount方法的时候

Vue$3.prototype.$mount = function (el, hydrating) {    el = el && inBrowser ? query(el) : undefined;    return mountComponent(this, el, hydrating)};function mountComponent (vm,el,hydrating) {    var  updateComponent = function () {          vm._update(vm._render(), hydrating);       };    vm._watcher = new Watcher(vm, updateComponent, noop);}

注:1.有些地方我是从分模块实现的源码中拷贝过来的,有些是直接是为了方便查找,就直接在vue.common.js中拷贝过来,所以可能代码风格有些不一样,

  而且为了更方便理解,会对某些地方做小小的改动,会删除一些和本话题无关的代码实现过程

      2.hydrating 这个属性和vdom(虚拟dom)实现有关系,在这里暂时不去解释它的作用, 单词意思为:保湿

 

通过实现可以看出来,watcher的getter 就是vm的render方法(update是更新组件,和数据没有关系,所以把getter转化为render),每个vm都有自己独立对应的一个watcher,

这个watcher关心的是整个组件的渲染

上面说的是 在使用$mount方法的时候 会创建这个watcher,但是有时候,我们在创建组件的时候,没有使用$mount ,比如

const app = new Vue({  el:"#app"  router: router,  store:store,  render: h => h(App)});

这个时候就没有用$mount,那么它就不会有watcher了吗?

先上代码: 

function Vue (options) {  this._init(options)}Vue.prototype._init = function (options?: Object) {     //...其他初始化实现     if (vm.$options.el) {      vm.$mount(vm.$options.el)    }  }

注: 我吧_init方法里面不相关的代码都删除了,只留下了我们关心的地方

在new vue的时候,我们传入的对象有个el属性,关键就在这里,而在vm初始化的时候会判断传进来的对象有没有这个属性,如果有就会自动去调用$mount方法,而不需要手动

当然如果又没有el,又没有手动调用$mount方法,那么这个组件就不会出现在view上,也不会创建对应的watcher

 

2.解析computed属性的时候

Vue.prototype._init = function (options?: Object) {     initState(vm)}export function initState (vm: Component) {  vm._watchers = []  const opts = vm.$options  if (opts.props) initProps(vm, opts.props)  if (opts.methods) initMethods(vm, opts.methods)  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)}function initComputed (vm: Component, computed: Object) {  const watchers = vm._computedWatchers = Object.create(null)  for (const key in computed) {    const userDef = computed[key]    let getter = typeof userDef === 'function' ? userDef : userDef.get        // create internal watcher for the computed property.    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)  }}

注:1.把有些和此话题不相关的代码都删除了,所以帖出来的代码不是完整的

  2.在逛vue论坛的时候,遇到有人提问:在prop属性设置default为methods里面的方法为什么报空,大家看看initState的初始化顺序就明白了

  3.data,computed,watch等都会存储在vm.$option中

 

在解析computed属性的时候,会对里面的每个属性都会有创建一个对应的watcher,并且会用用这个属性的key作为_computedWatchers 对象的key来保存这个watcher

当你默认传一个function的时候,会把这个这个function当做watcher的getter,否则就会认为你传了一个包含了get属性的对象,并且是一个方法,

这个时候就会用这个get方法当做watcher的getter

 

3.使用$watch方法的时候

function initWatch (vm: Component, watch: Object) {  for (const key in watch) {    const handler = watch[key]    if (Array.isArray(handler)) {      for (let i = 0; i < handler.length; i++) {        createWatcher(vm, key, handler[i])      }    } else {      createWatcher(vm, key, handler)    }  }}function createWatcher (vm: Component, key: string, handler: any) {  let options  if (isPlainObject(handler)) {    options = handler    handler = handler.handler  }  if (typeof handler === 'string') {    handler = vm[handler]  }  vm.$watch(key, handler, options)}Vue.prototype.$watch = function (    expOrFn: string | Function,    cb: Function,    options?: Object  ): Function {    const vm: Component = this    options = options || {}    options.user = true    const watcher = new Watcher(vm, expOrFn, cb, options)    if (options.immediate) {      cb.call(vm, watcher.value)    }    return function unwatchFn () {      watcher.teardown()    }  }

和使用$mount方法一样,无论是你初始化的对象包含了watch属性或者使用$watch 都会创建相对于的watcher

使用$watch会返回一个方法,当你对观察的expOrFn不在感兴趣的时候,可以用来删除这个观察者

 

二.dep

export default class Dep {  static target: ?Watcher;  id: number;  subs: Array
; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }}

简称依赖,对vm中data返回的每个属性都会初始化一个依赖,用来收集对这个属性感兴趣的watcher,也就是watcher中运行getter方法会用到的相关属性,

那么这个属性对应的依赖就会把watcher加入到dep下的subs队列中去,也会把本身dep加入到watcher中的deps队列中去,

当这个属性的值改变的时候,通知watcher进行更新操作,也就是执行getter方法

 

属性解释:

  subs:对某个属性感兴趣的watcher队列

 

 

dep一般对应vm的中data方法返回的对象以及它的属性  

export default {  data(){    return {      name:"",       sex:1,       items:[]     }  }}

比如上面的 data会有一个dep,name和sex也分别有一个dep

 

dep的创建过程

function initState (vm) {  if (opts.data) {    initData(vm);  } else {    observe(vm._data = {}, true /* asRootData */);  }}function initData (vm) {  var data = vm.$options.data;  data = vm._data = typeof data === 'function'    ? getData(data, vm)    : data || {};  if (!isPlainObject(data)) {    data = {};  }    var keys = Object.keys(data);  var props = vm.$options.props;  var i = keys.length;  while (i--) {    if (props && hasOwn(props, keys[i])) {     ///warn    } else if (!isReserved(keys[i])) {      proxy(vm, "_data", keys[i]);    }  }  // observe data  observe(data, true /* asRootData */);}function observe (value, asRootData) {  if (!isObject(value)) {    return  }  var ob;  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {    ob = value.__ob__;  } else if (    observerState.shouldConvert &&    !isServerRendering() &&    (Array.isArray(value) || isPlainObject(value)) &&    Object.isExtensible(value) &&    !value._isVue  ) {    ob = new Observer(value);  }  return ob}var Observer = function Observer (value) {  this.value = value;  this.dep = new Dep();  this.vmCount = 0;  def(value, '__ob__', this);  if (Array.isArray(value)) {    var augment = hasProto      ? protoAugment      : copyAugment;    augment(value, arrayMethods, arrayKeys);    this.observeArray(value);  } else {    this.walk(value);  }};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]]);  }};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;      /* eslint-disable no-self-compare */      if (newVal === value || (newVal !== newVal && value !== value)) {        return      }      /* eslint-enable no-self-compare */      if (process.env.NODE_ENV !== 'production' && customSetter) {        customSetter();      }      if (setter) {        setter.call(obj, newVal);      } else {        val = newVal;      }      childOb = observe(newVal);      dep.notify();    }  });}Observer.prototype.observeArray = function observeArray (items) {  for (var i = 0, l = items.length; i < l; i++) {    observe(items[i]);  }};

注:vm初始化会调用initState方法,所以省略了init的过程

 

整个过程简而言之就是 

 

1).initData

  在初始化data的时候,是用一个三元运算符,判断data是否是一个function,如果是的话,就运行 getData方法

function getData (data, vm) {  try {    return data.call(vm)  } catch (e) {    handleError(e, vm, "data()");    return {}  }}

其实getData 也就是运行一次data方法而已,只是为了安全考虑,加了try catch

如果不是的话,就返回 data || {} 给它,这里有个小技巧就是,如果data不为空就返回data,否则就返回一个{},

然后 在对data里面的属性设置代理,让我们能够用vm.key的方式来获取或者设置data里面的数据,而不用vm._data.key的方式

在方法的最后面 就是调用observe 来生成一个observer(可以依赖的对象)

 

2).observe

 首先判断 是不是一个object,当然 这里的object 是一个泛型,是指 typeof 为 "object" 且 != null 的对象,也可以是Array,

function isObject (obj) {  return obj !== null && typeof obj === 'object'}

如果不是一个object 那么就会中断

再判断 是否已经包含了一个 observer 对象,如果有 则返回这个observer对象

再判断 

observerState.shouldConvert: 是可以转换的
!isServerRendering(): 不是服务器渲染
(Array.isArray(value) || isPlainObject(value)) 是 数组 或则 Object 类型
Object.isExtensible(value): 这个对象是可以添加属性的, 做这个判断是因为 会在Object上 新增一个__ob__属性来对应它的Observer对象 !value._isVue: 不能是 vm对象, vm组件 严格来说 也是Object对象,但是他不能被观察,所以有这个判断

注: 判断还挺多的。。。。

 

如果上述条件 任何一个不满足 就会中断,不过我们只需要关心的是。。我们传进来的数据满足是 数组 或者object对象就行了,其他的一般不会触及

好了 下一步就是肉戏来了,就是创建一个 observer 对象

3). new observer

保存value数据对象,然后最重要的相关核心实现就从这里开始了,

首先创建一个dep依赖(说了这么多终于说到它了),这个dep 就对应的这个value数据对象,

然后 会判断这个value是不是数组对象,

如果是的话, 先执行

augment(value, arrayMethods, arrayKeys) 而 augment 一般会对应这个方法
function protoAugment (target, src) {/* eslint-disable no-proto */target.__proto__ = src;/* eslint-enable no-proto */}

这一步比较关键,把这个数组对象默认的prototype对应的方法替换成arrayMethods 里面的方法,而arrayMethods 这个对象里面包含了哪些方法呢,请看代码

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

  

从代码看:包含了 'push','pop','shift','unshift','splice','sort','reverse' 方法,并且arrayMethods的prototype是Array的prototype,

具体作用在下面回和例子结合说到

然后,再执行 observeArray,

Observer.prototype.observeArray = function observeArray (items) {  for (var i = 0, l = items.length; i < l; i++) {    observe(items[i]);  }};

其实也就是对数据里面的每个元素再进行观察, 从observe的判断条件来看,普通的数据就不要想了,此元素 必须是一个object 或者array,才会生成一个observer对象(这点很重要)

好了,通过上面对数组的实现,可以解释一下 我们平时 在用数组稍不注意就会犯的一个错误, 就是改变一个数组的普通元素的值,但是 界面上却没有响应它的改动呢,

比如

  

上述代码,会在两秒之后改变界面的显示吗? 

不会,因为 this.ary[2] = 10, 这个只是普通的操作,vue2 没有对这个属性添加相关的dep(我觉得是为了性能考虑吧,不然的话 数组里面的数据大了,对每个数据都建立一个dep,那就太恐怖了)

这样的操作,是没有任何改变的,

而上面vue2 特意自己实现了array 的一些相关方法去实现这个效果,所以如果要改变普通元素的数据的时候我们可以这样,

mounted(){      setTimeout(()=>{        this.ary.splice(2,1,10);      }, 2000);    }

感兴趣的可以测试下,

起作用的原因在于arrayMethods这个对象,当我们操作splice的时候,不是操作的ary的原生的splice方法,而是操作的arrayMethods里面的splice方法,

而这个方法在执行了原生的对应的方法后,还会去调用ob.dep.notify()方法执行更新

 

以上是如果这个数据类型是数组的时候,当不是的时候,那么执行walk方法,

然后获取对象的keys 循环对key调用defineReactive方法,然后当这个数据 又是一个object或者array对象的时候,又再一次去观察它

可以说defineReactive 这个方法里面的实现,是实现双向绑定最最核心的一个方法了,其他的一系列都是在这基础上做扩展,或是为了健壮性或者为了解耦等等,

这个方法实现了对data的每个属性创建一个dep,并且对data的原生属性的操作利用Object.defineProperty方法定义一个set和get的代理,让data和dep产生关系,

而且 在set和get具体实现里,也实现了 让dep和watcher 产生关系,所以说这个是vue2实现双向绑定最最最核心的方法了,因为这么重要,说就单独在下一个部分来解释它

 

三 dep 和 watcher 是如何联系起来的

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;      /* eslint-disable no-self-compare */      if (newVal === value || (newVal !== newVal && value !== value)) {        return      }      /* eslint-enable no-self-compare */      if (process.env.NODE_ENV !== 'production' && customSetter) {        customSetter();      }      if (setter) {        setter.call(obj, newVal);      } else {        val = newVal;      }      childOb = observe(newVal);      dep.notify();    }  });}

 

下面就来看看 这个方法的具体实现:

 

首先定义了一个dep,然后在获取这个属性的描述对象,用这个对象来判断这个属性是不是可以配置的,如果为否,则退出,因为使用Object.defineProperty

这个方法是需要这个属性是可以配置的

 

然后获取这个属性的 get和set方法并缓存,因为Object.defineProperty会重定义这两个方法,会覆盖它们,缓存起来以便于调用

 

再然后 let childOb = observe(val)  这句话,就实现生成子对象的observer,这样就实现了循环调用,对所有子对象都能够生成observer以及子对象的属性值的dep

当然,这个子对象得是 Object 或者 array

 

请注意:核心来了,使用Object.defineProperty方法 定义set 和get方法

我们看set方法实现:

首先得到value值,这里用三元运算符来判断之前缓存的get方法是否存在,如果存在就调用这个get方法否则直接返回val值

然后再 判断 Dep.target 是否存在,我们看看源码里面的注释对这个属性的解释

// 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

这个属性表示当前正在计算的watcher, 并且在用一个时间内整个全局内只会同时存在一个

就是说:正在运行 get方法的watcher,而get是一个方法,而js是单线程,不会同一时间运行多个方法,那当运行这个get方法过程中又可能存在调用其他watcher的get方法的情况,

比如一个vm组件的render方法里面包含computed的属性的时候,这又是如何处理的呢,请看代码

const targetStack = []export function pushTarget (_target: Watcher) {  if (Dep.target) targetStack.push(Dep.target)  Dep.target = _target}export function popTarget () {  Dep.target = targetStack.pop()}

 

首先定义了一个target栈,是通过一个数组实现的,

运行watcher的get方法的时候 会先调用pushTarget方法,方法里面会 判断Dep.target,如果存在则把它压进栈里面,然后把新的taget赋值给Dep.target

watcher的个体方法后面会运行popTarget,把target栈的最后一个元素赋值给Dep.target,当然如果不存在,那么Dep.target为空

通过代码可以看出来,Dep永远是对应当前正在计算的watcher,如果还不懂的话也没关系,最后会通过一个例子把整个相关的流程都解释一遍,

 

我们回到set方法实现:

判断Dep.target是否存在,如果存在那么表示,我获取这个属性的值的是在运行watcher的get方法过程中(因为只有运行watcher的get方法,才会pushTarget,

而且在get方法后面还会运行popTarget把Dep.target重置),那么代表这个属性值是和这个watcher是有关联的,

好,既然是有关联的,那么dep 就和watcher要进行关联操作了,即执行dep.depend方法,这个方法实现了,把dep放进watcher的newDeps里面,为什么是newDeps

而不是deps呢,因为deps代表当前的依赖,而newDeps是表示新的依赖,即运行完这次更新之后,这个watcher的相关依赖,而运行get方法的时候会清楚当前的deps里面的依赖,

把newDeps里面的依赖复制到deps里面去

 

如果有childOb的话,也会把childOb同watcher关联,因为如果有childOb,那么这个value肯定是一个对象,操作这个对象有两个方式,要么获取value对象的子属性,要么设置

这个对象为新对象,这和childOb都有关系,所以需要把childOb同watcher关联

 

再判断value值是否为array,如果是的话,就去关联array下面的对象

 

以上就是整个dep 和watcher 关联的部分了

 

而set实现就是实现通知关联的watcher进行更新,因为只有数据改变的时候,才会需要更新,所以把通知更新实现放在set方法里面

 

set具体实现:

当改变一个属性对应的值的时候, 先获取现在的值并做比较,如果相同就退出

然后不同的话,那么就进行复制操作

然后再执行更新操作,而在watcher部分说过,watcher的getter有3个类型:1:vm的render方法,2:computed的属性,3:watch的属性

这个时候,大多数情况是执行vm。render进行更新,当然这个render只是返回一个vNode(虚拟节点)然后调用vm的update进行真正的更新,

这里为了简化理解,所以这样说

 

上面就是整个关联的部分,在总结一下过程就是:在创建watcher进行的时候,1和3方式的会立即去运行get方法,然后和相关运算用到的属性对应的dep建立关联,

而2对应的computed的属性因为是懒更新的,所以在创建watcher的时候并不会立即执行关联,而是在调用的时候才会用到,而一般是在vm.render方法里面会有调用

 

双向绑定到这里就解释完了,本来还想继续在这里用一个例子来说明整个过程,但是发现貌似这篇文字已经很多了,那就放在下一篇《双向绑定续》里面来解释

注:因为是第一次写博客,所以可能一些东西没考虑完善,或者错误的地方,请指正,谢谢

 

 

 

 

 

转载于:https://www.cnblogs.com/tanghansan/p/6756557.html

你可能感兴趣的文章
easyui的combobox根据后台数据实现自动输入提示功能
查看>>
ASP.NET MVC WEB API必知必会知识点总结
查看>>
Test2 unit6
查看>>
sql注入<二>
查看>>
26、OSPF配置实验之不规则区域虚链路
查看>>
[C++再学习系列] 引用和指针
查看>>
未能加载文件或程序集“********”或它的某一个依赖项。试图加载格式不正确的程序。...
查看>>
bootstrap4-图像
查看>>
Centos7 MariaDB10.1.22编译安装
查看>>
路由器配置基础(中)
查看>>
/etc/sudoers的配置
查看>>
菜鸟学Linux 第075篇笔记 mysql事务,视图
查看>>
Mysql + PHP
查看>>
jetty9请求form表单太小限制
查看>>
linux服务器优化1.0版
查看>>
从oracle到mysql,主从到分库,一个普通项目数据库架构的变迁
查看>>
ASP.NET 4.0请求验证报错 从客户端...中检测到有潜在危险的 Request.Form 值
查看>>
ASM
查看>>
mysql常用操作
查看>>
unit 9 文档练习
查看>>