vue-源码剖析-双向绑定

栏目: JavaScript · 发布时间: 6年前

内容简介:拉到vue的代码之后,首先来看一下项目目录,因为本文讲的是双向绑定,所以这里主要看双向绑定这块的代码。从入口开始:

拉到vue的代码之后,首先来看一下项目目录,因为本文讲的是双向绑定,所以这里主要看双向绑定这块的代码。

vue-源码剖析-双向绑定

入口

从入口开始: src/core/index.js

index.js 比较简单,第一句就引用了 Vue 进来,看下 Vue 是啥

import Vue from './instance/index'
复制代码

Vue构造函数

来到 src/core/instance/index.js

一进来,嗯,没错,定义了 Vue 构造函数,然后调用了好几个方法,这里我们看第一个 initMixin

import { initMixin } from './init'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}
initMixin(Vue)
...
export default Vue
复制代码

初始化

来到 src/core/instance/init.js

这个给 Vue 构造函数定义了 _init 方法,每次 new Vue 初始化实例时都会调用该方法。

然后看到 _init 中间的代码,调用了好多初始化的函数,这里我们只关注 data 的走向,所以这里看一下 initState

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    ...
    initState(vm)
    ...
  }
}
复制代码

来到 src/core/instance/state.js

initState 调用了 initDatainitData 调用了 observe ,然后我们再往下找 observe

import { observe } from '../observer/index'
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  ...
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  ...
}

function initData (vm: Component) {
  let data = vm.$options.data
  ...
  observe(data, true /* asRootData */)
}
复制代码

Observer(观察者)

来到 src/core/observer/index.js

这里,实例化 Observer 对象

首先, new Observer 实例化一个对象,参数为 data

export function observe (value: any, asRootData: ?boolean): Observer | void {
  let ob: Observer | void
  ob = new Observer(value)
  return ob
}
复制代码

然后我们来看下 Observer 构造函数里面写了什么,这里给每个对象加了 value 和实例化了一个 Dep ,然后 data 为数组的话则递归,否则执行 walk

walk 这里是对对象遍历执行 defineReactive

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    if (Array.isArray(value)) {
      ...
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
复制代码

然后,我们来看 defineReactive 做了什么,嗯,这里就是 Observer 的核心。

Object.defineProperty 对对象进行配置,重写 get&set

get :对原来 get 执行,然后执行 dep.depend 添加一个订阅者

set :对原来 set 执行,然后执行 dep.notify 通知订阅者

Dep 是干啥的呢? Dep 其实是一个订阅者的管理中心,管理着所有的订阅者

import Dep from './dep'
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  const getter = property && property.get
  const setter = property && property.set

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      dep.notify()
    }
  })
}
复制代码

Dep(订阅者管理中心)

那么,到这里了,疑问的是什么时候会触发 Observerget 方法来添加一个订阅者呢?

这里的条件是有 Dep.target 的时候,那么我们找一下代码中哪里会对 Dep.target 赋值,找到了 Dep 定义的地方

来到 src/core/observer/dep.js

pushTarget 就对 Dep.target 赋值了,然后来看一下到底是哪里调用了 pushTarget

export default class Dep {
  ...
}
Dep.target = null
const targetStack = []

export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}
复制代码

然后,找到了 Watcher 调用了 pushTarget ,那么我们来看一下 Watcher 的实现

来到 src/core/observer/watcher.js

这里可以看到每次 new Watcher 时,就会调用 get 方法

这里执行两步操作

第一:调用 pushTarget

第二:调用 getter 方法触发 Observerget 方法将自己加入订阅者

export default class Watcher {
  vm: Component;
  constructor (
    vm: Component
  ) {
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    }
    return value
  }
}
复制代码

接着, Dep.target 有了之后,接下来就要看一下 dep.depend() 这个方法,所以还是要到 Dep 来看下这里的实现。 来到 src/core/observer/dep.js

这里调用了 Dep.target.addDep 的方法,参数是 Dep 的实例对象,那么我们看下 addDep

export default class Dep {
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
}
复制代码

Watcher(观察者)

又来到 src/core/observer/watcher.js

到这, addDep 其实又调用时 Dep 实例的 addSub 方法,参数也是把 Watcher 实例传递过去

然后,我们看上面的 addSub ,这里就是把 Watcher 实例 pushdepsubs 数组中保存起来

到这里,就完成了把 Watcher 加入到 Dep 这里订阅器管理中心这里,后面的管理就由 Dep 来统一管理

export default class Watcher {
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }
}
复制代码

走完了添加订阅器,接着再来看下 Observerset 方法,这里调用了 dep.notify ,我们来看一下这个方法

来到 src/core/observer/dep.js

这里,就是对 subs 中的所有 Watcher ,调用其 update 方法来更新数据

export default class Dep {
 notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
复制代码

这里,我们就来看看 Watcher 是怎么更新的 又来到 src/core/observer/watcher.js

update 调用的是 run 方法, run 方法这里先用 get 拿到新的值,然后把新&旧值做为参数给 cb 调用

export default class Watcher {
  update () {
    this.run()
  }
  
  run () {
    if (this.active) {
      const value = this.get()
      const oldValue = this.value
      this.value = value
      this.cb.call(this.vm, value, oldValue)
    }
  }
}
复制代码

这里的 cb 其实是实例化的时候传进来的,这里我们看一下什么时候会实例化 Watcher

回到一开始的 initState:src/core/instance/state.js

initState 的最后还调用了 initWatch ,然后再 createWatcher ,最后 $watch 的时候就实例化了 Watcher 对象,这里就把 cb 传到了 Watcher 实例中,当监听的数据改变的时候就会触发 cb 函数

import Watcher from '../observer/watcher'
export function initState (vm: Component) {
  ...
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    createWatcher(vm, key, handler)
  }
}

function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  return vm.$watch(expOrFn, handler, options)
}

Vue.prototype.$watch = function (
  expOrFn: string | Function,
  cb: any,
  options?: Object
): Function {
  const vm: Component = this
  const watcher = new Watcher(vm, expOrFn, cb, options)
}
复制代码

写在最后

这里的思路,其实就是翻着源码走的,写的时候都是按自己的理解思路来的,存在问题的话欢迎指出~


以上所述就是小编给大家介绍的《vue-源码剖析-双向绑定》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

正当法律程序简史

正当法律程序简史

(美)约翰·V.奥尔特 / 杨明成、陈霜玲 / 商务印书馆 / 2006-8 / 14.00元

本书的主题——正当法律程序,是英美法的核心概念,它使诸如法治、经济自由、个人自治以及免于政府专断行为的侵害等价值观念具体化,因而是法学领域一个永恒的主题,数百年以来一直是法学家、法官及律师关注的重点。本书以极为简洁、精确的语言总结了五百年法律发展的恢弘历史,为人们描述了正当法律程序观念发展演变的清晰轨迹。而沿着这条轨迹,人们可以准确地了解正当法律程序这一重要概念所包含的广泛的问题。 作为一本......一起来看看 《正当法律程序简史》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具