vue中的数据绑定原理的实现

作者:holyZhengs 时间:2024-05-05 09:09:34 

本文主要介绍了vue中的数据绑定原理的实现,分享给大家,也给自己留个笔记,具体如下:

vue中的数据绑定原理的实现

vue中的响应式数据绑定是通过数据劫持和观察者模式来实现的。当前学习源码为vue2.0

源码关键目录


src
|---core
|  |---instance
|     |---init.js
|     |---state.js
|  |---observer
|     |---dep.js
|     |---watcher.js

当我们实例化一个vue应用的时候,会伴随着各种的初始化工作,相关的初始化工作代码在init.js文件中


// src/core/instance/init.js

Vue.prototype._init = function (options?: Object) {
...
initLifecycle(vm)
initEvents(vm)
callHook(vm, 'beforeCreate')
initState(vm)
callHook(vm, 'created')
initRender(vm)
}

在这里可以看到对state的初始化工作initState()


// src/core/instance/state.js

export function initState (vm: Component) {
vm._watchers = []
initProps(vm)
initData(vm)
initComputed(vm)
initMethods(vm)
initWatch(vm)
}

可以看到这里有对各种sate的初始化工作,我们看initData()


// src/core/instance/state.js

function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
 ? data.call(vm)
 : data || {}
if (!isPlainObject(data)) {
 data = {}
 process.env.NODE_ENV !== 'production' && warn(
  'data functions should return an object.',
  vm
 )
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
while (i--) {
 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 {
  proxy(vm, keys[i])
 }
}
// observe data
observe(data)
data.__ob__ && data.__ob__.vmCount++
}

这里做了一点判断,判断data方法是否返回的是一个对象,以及props中是否有与data中重名的属性,最后会调用observe对data进行监听,看一下observe


// src/core/observer/index.js

export function observe (value: any): Observer | void {
if (!isObject(value)) {
 return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
 ob = value.__ob__
} else if (
 observerState.shouldConvert &&
 !config._isServer &&
 (Array.isArray(value) || isPlainObject(value)) &&
 Object.isExtensible(value) &&
 !value._isVue
) {
 ob = new Observer(value)
}
return ob
}

可已看到这里也是做了一点判断,如果有__ob__属性的话就用它,或者如果data是数组或对象或可扩展对象的话,就为它新建一个Observer,看一下Observer


// src/core/observer/index.js

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()
 this.vmCount = 0
 def(value, '__ob__', this)
 if (Array.isArray(value)) {
  const augment = hasProto
   ? protoAugment
   : copyAugment
  augment(value, arrayMethods, arrayKeys)
  this.observeArray(value)
 } else {
  this.walk(value)
 }
}

/**
 * Walk through each property and convert them into
 * getter/setters. This method should only be called when
 * value type is Object.
 */
walk (obj: Object) {
 const keys = Object.keys(obj)
 for (let i = 0; i < keys.length; i++) {
  defineReactive(obj, keys[i], obj[keys[i]])
 }
}

/**
 * Observe a list of Array items.
 */
observeArray (items: Array<any>) {
 for (let i = 0, l = items.length; i < l; i++) {
  observe(items[i])
 }
}
}

判断data是不是数组,如果是数组就对数组元素再去调用observe方法做同样的处理,如果不是,就调用walk去劫持该数据,对数据的劫持主要再defineReactive方法中,正如函数名,让数据变得响应式。看一下defineReactive方法


// src/core/observer/index.js

export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
const dep = new Dep()
// data中的每一个成员都有一个对应的Dep,在此闭包创建。

const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
 return
}

// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set

let childOb = 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() // 依赖收集
   if (childOb) {
    childOb.dep.depend()
   }
   if (Array.isArray(value)) {
    for (let e, i = 0, l = value.length; i < l; i++) {
     e = value[i]
     e && e.__ob__ && e.__ob__.dep.depend()
    }
   }
  }
  return value
 },
 set: function reactiveSetter (newVal) {
  const value = getter ? getter.call(obj) : val
  if (newVal === value) {
   return
  }
  if (process.env.NODE_ENV !== 'production' && customSetter) {
   customSetter()
  }
  if (setter) {
   setter.call(obj, newVal)
  } else {
   val = newVal
  }
  childOb = observe(newVal)
  dep.notify() // 发布通知
 }
})
}

遍历状态,修改状态的getter和setter,当页面上对应状态被首次渲染的时候,会为页面上每一个使用到data的地方新建一个watcher,并将当前watcher保存到全局变量Dep.target中,在对应data的getter中就会调用Dep.depend方法,将当前的watcher添加到当前的Dep中,一个Dep对应一个或多个watcher,着取决于,此状态被使用的数量。当data被修改时,对应的setter就会被触发,会调用对应的Dep中的notify方法,通知所有观察者,进行更新。

这里出现了两个定的类:Dep和Watcher,其中Dep管理观察者,Wathcer代表观察者

先看一下Dep


// src/core/observer/dep.js

export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;

constructor () {
 this.id = uid++
 this.subs = []
}

addSub (sub: Watcher) {
 this.subs.push(sub)
}

removeSub (sub: Watcher) {
 remove(this.subs, sub)
}

depend () {
 if (Dep.target) {
// 调用当前target,也就是正在处理的watcher的addDep方法,并把此Dep传进去
  Dep.target.addDep(this)
 }
}

notify () {
 // stablize the subscriber list first
 const subs = this.subs.slice()
 for (let i = 0, l = subs.length; i < l; i++) {
  subs[i].update()
 }
}
}

看一下watcher.js


// src/core/observer/watcher.js

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)) {
   // 将当前watcher添加到当前的Dep中
   dep.addSub(this)
  }
 }
}
...
}

总结

vue的响应式数据绑定主要依赖Object.defineProperty和观察者模式。

  1. 在我们新建一个vue实例的时候,做一系列的初始化工作,这部分的逻辑集中在src文件夹下的core文件夹下的instance和observer文件夹内

  2. 响应式数据绑定是在状态的初始化阶段完成的,在initState方法中的initData中进行data的数据绑定。

  3. 在initData中调用observe方法,为该data新建一个Observer类,然后最终调用为data中的每一个成员调用walk方法,在walk中通过defineReactive方法劫持当前数据

  4. 在defineReactive中通过Object.defineProperty去修改数据的getter和setter

  5. 在页面渲染的时候,页面上每一个用到data的地方都会生成一个watcher,并将它保存到全局变量Dep.target中,watcher改变每一个观察者,Dep用来管理观察者。

  6. 然后在data的getter中将调用Dep的depend方法,将Dep.target中的watcher添加到此data对应的Dep中,完成依赖收集

  7. 在data被修改的时候,对应data的setter方法就会被出动,会调用Dep.notify()方法发布通知,调用每个watcher的uptade方法进行更新。

来源:https://github.com/HolyZheng/holyZheng-blog/issues/13

标签:vue,数据绑定
0
投稿

猜你喜欢

  • 如何在Access数据库中立即得到所插入记录的自动编号?

    2010-06-17 12:45:00
  • python 多进程和多线程使用详解

    2021-08-12 19:50:43
  • PHP操作MySQL中BLOB字段的方法示例【存储文本与图片】

    2023-11-23 23:45:27
  • 如何查看python中安装库的文件位置

    2021-04-17 04:09:31
  • python被修饰的函数消失问题解决(基于wraps函数)

    2021-07-22 17:32:34
  • php封装json通信接口详解及实例

    2023-11-14 21:56:26
  • 分享JS代码实现鼠标放在输入框上输入框和图片同时更换样式

    2024-05-22 10:41:02
  • Python函数用法和底层原理分析

    2023-09-13 23:42:37
  • (X)HTML中最多余的元素标签

    2008-06-15 15:48:00
  • golang如何修改json文件内容的方法示例

    2024-04-26 17:32:44
  • PHP原型模式Prototype Pattern的使用介绍

    2023-05-25 01:21:04
  • 使用python无账号无限制获取企查查信息的实例代码

    2021-09-23 20:26:10
  • Python不支持 i ++ 语法的原因解析

    2022-02-07 12:16:49
  • Python脚本提取fasta文件单序列信息实现

    2023-03-06 12:55:02
  • Python基础之getpass模块详细介绍

    2021-03-06 13:47:13
  • Pytorch可视化的几种实现方法

    2023-06-11 17:44:57
  • 学习XHTML和HTML之间的区别

    2007-08-22 11:02:00
  • 使用Keras预训练模型ResNet50进行图像分类方式

    2022-04-02 22:57:21
  • Sql server数据库优化

    2010-04-06 19:17:00
  • thinkPHP引入类的方法详解

    2024-06-05 09:45:55
  • asp之家 网络编程 m.aspxhome.com