前言
Vue的一大特点就是数据双向绑定,当数据发生变化时,也可以同时触发界面的变化,即数据驱动视图。要想实现数据驱动视图,那么有这么几个步骤:
1.收集我们需要监听的数据,并给他配置个监听器,当数据状态发生变化时触发监听器,然后判断是否需要重新渲染;
2.数据变更时重新构建虚拟DOM;
3.虚拟DOM渲染成为真实的DOM;
在这里本文主要分析其中的第一步——数据监听机制,也就是Data变化到watcher监听到的这一过程。
正文
一、Object的数据响应机制
Vue的数据响应主要是采用观察者模式(Observer Pattern)。所谓观察者模式,即定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在Vue中,当数据发生变化时,就会通知视图中所有和这个数据有关的地方都进行更新。
Vue首先通过定义一个Observer类,并在其内部方法中将对象所有的属性通过Object.defineProperty来添加get和set方法,从而保证所有的属性都变成了可观测的对象。
下面是具体的源码解读:
//src\core\observer\index.js
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
... //当value是数组时候的处理
} else {
//仅当value为object的时候,调用walk去解析
this.walk(value)
}
}
/**
* 遍历obj的所有属性,调用defineReactive为其添加get、set,使该属性变为可观测的
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
上面这段代码主要就是遍历Observer的构造函数的入参value,当为对象的时候则遍历其对象的属性,并在后续调用defineReactive方法为其添加get、set方法。defineReactive源码如下:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
//如果对象属性的configurable为false时,代表该属性无法被修改,即无法为其增加get/set,直接return
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
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
//如果value是一个obj,这里会递归的处理,给value的属性添加get/set
let childOb = !shallow && observe(val)
//给key属性增加get/set方法
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)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, new