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方法进行更新。

以上是 vue中的数据绑定原理的实现 的全部内容, 来源链接: utcz.com/z/344562.html

回到顶部