前言
通过珠峰课程的学习来理解 Vue 源码的响应式原理 。
响应式原理
1.核心点: Object.defineProperty
2.默认 Vue 在初始化数据时,会给 data 中的属性使用 Object.defineProperty 重新定义所有属性,当页面获取到对应属性时。会进行依赖收集(收集当前组件的watcher) 如果属性发生变化会通知相关依赖进行更新操作。
什么叫依赖收集 ?
通过Object.defineProperty
在重新定义data属性的时候,进行拦截,再进行实际渲染 ; 那实际渲染之前的一系列处理逻辑就是依赖收集上边有说,会在依赖收集的时候为每一个属性创建一个watcher,如果属性发生变化,则通知对应的 watcher 更新视图
。
来看看源码
1,首先从构造函数初始化看起 ,src/core/instance/index.js
由于我们主要分享响应式数据原理,也就是初始化Vue数据是如何渲染并建立监听的,主要看 stateMixin 模块
2,进入stateMixin
模块 , 我们直接看向 initData
函数
function initData (vm: Component) { // 初始化data
let data = vm.$options.data // 获取到用户传入的data数据
data = vm._data = typeof data === 'function' // 模板语法与标准语法区分获取data数据
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key) // es6 proxy 代理
}
}
/*
中间我就跳过了,看意思是非生产环境下,对 props , methods 的一些定义,声明做的判断,不允许重复声明
另外就是添加了 proxy , es6 新增代理属性 , 包含所有 Object.defineProperty 的功能, 重要的一点是解决 了不能对象监听的问题等。
*/
// observe data
observe(data, true /* asRootData */) // 重点在这儿,为每一个data属性创建一个watcher
}
重点 : observe(data, true /* asRootData */) // 重点在这儿,为每一个data属性创建一个wathcer
接下来我们走进 observer 类
3, src/core/observer/index.js
37 行
就是如下这个 Observer 类,具体做了些什么呢,看代码
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have 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)) { // 是数组
if (hasProto) {
protoAugment(value, arrayMethods) // 改写数组原型方法
} else {
copyAugment(value, arrayMethods, arrayKeys) // 复制数组已有方法
}
this.observeArray(value) // 深度观察数组中的每一项 , 下边方法
} else {
this.walk(value) // 重新定义对象类型数据 下边方法
}
}
/**
* Walk through all properties 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]); // 定义响应式数据,这里可以看到 defineReactive 方法
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) { // 遍历数组
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // 观测数组中的每一项
}
}
}
通过上边的代码,我们看到了主要做了一件事,就是区分数组和对象,并对数据和对象遍历,创建观察者 watcher
=> defineReactive 方法
上边有说到数组会走到 observeArray
方法 , 而方法就是遍历调用 observer
,进行一些数据类型判断和是否被监听过,没有被监听到的则回调回去继续创建观测。
src/core/observer/index.js
113 行
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
/*
不是对象不进行观测,如:不管是模板语法还是标准语法data均是一个对象
data () { 模板语法返回一个对象
return {}
}
new Vue ({ 标准语法 data 也是一个对象
data:{}
})
*/
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { // 已经被监听的,不会重复监听
ob = value.__b__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && // 是否可扩展
!value._isVue
) {
ob = new Observer(value) //重点,重点,重点, 回调回去,观测对象类型
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
如果不是数组呢,则直接走到 defineReactive
方法
src/core/observer/index.js
148 行 defineReactive 响应式数据绑定关键方法
也就是我们常常说到的 Object.defineProperty() 应用的地方 ;所有的初始化数据都会走到这里 。
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new 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
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && 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() // 收集依赖 watcher
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, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify() // 触发数据对应的依赖进行更新 , 重点,重点,往下看
}
})
}
Dep 所有观察者的集合,也就是 wathcer 的集合,在创建观测的时候为每一个data属性创建了watcher
观察者(Object.defineProperty方法的 get 里边
),那么触发数据更新的 set
方法会调用 dep.notify()
,看代码
src/core/observer/dep.js
13行,Dep 类看以看见有构造函数,添加watcher ,删除watcher 等方法,那么我看来看 更新方法notify()
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) {
Dep.target.addDep(this)
}
}
notify () { // 通知存储的依赖更新
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 依赖中对应修改属性的update方法
}
}
}
update ()
方法在 src/core/observer/wachter.js
166行 ,其实这里是定义 wachter
观察者类,里边有各种操作 wachter
观察者的方法,如:增加,修改,清除等 。
总结
好了,分析到这里,其实就已经很明了,针对数据响应式原理,总体的过程就是: