视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001 知道1 知道21 知道41 知道61 知道81 知道101 知道121 知道141 知道161 知道181 知道201 知道221 知道241 知道261 知道281
问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501
vue中的数据绑定原理的实现
2020-11-27 22:12:11 责编:小采
文档

本文主要介绍了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方法进行更新。

下载本文
显示全文
专题