vue中的数据绑定原理的实现
本文主要介绍了vue中的数据绑定原理的实现,分享给大家,也给自己留个笔记,具体如下:
vue中的响应式数据绑定是通过数据劫持和观察者模式来实现的。当前学习源码为
源码关键目录
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和观察者模式。
- 在我们新建一个vue实例的时候,做一系列的初始化工作,这部分的逻辑集中在src文件夹下的core文件夹下的instance和observer文件夹内
- 响应式数据绑定是在状态的初始化阶段完成的,在initstate方法中的initdata中进行data的数据绑定。
- 在initdata中调用observe方法,为该data新建一个observer类,然后最终调用为data中的每一个成员调用walk方法,在walk中通过definereactive方法劫持当前数据
- 在definereactive中通过object.defineproperty去修改数据的getter和setter
- 在页面渲染的时候,页面上每一个用到data的地方都会生成一个watcher,并将它保存到全局变量dep.target中,watcher改变每一个观察者,dep用来管理观察者。
- 然后在data的getter中将调用dep的depend方法,将dep.target中的watcher添加到此data对应的dep中,完成依赖收集
- 在data被修改的时候,对应data的setter方法就会被出动,会调用dep.notify()方法发布通知,调用每个watcher的uptade方法进行更新。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。