vue 响应式原理
响应式原理
简单点讲 vue 的响应式是通过 Object.defineProperty 和 观察者模式来实现的。
vue 初始化的时候 watcher 构造函数通过 Object.defineProperty 方法对 data 属性进行递归遍历,设置 get、set,初始化编译的时候会触发 getter 函数,进行依赖收集,将观察者 watcher 添加到目标对象 dep 中。改变数据的时候会触发 set, 执行 notify 方法,调用 dep 中 watcher 对象的 update 方法,update 方法将 watcher 添加到 watcher 队列中, 通过调用 nextTick 异步执行,触发更新。
至于实现的细节,现在细细道来...
我们需要分析的大致是以下源码文件:
- observer
- dep.js
- index.js
- scheduler.js
- watcher.js
先看 index.js
export class Observer { // 定义 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])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
...
...
...
/**
* Define a reactive property on an Object.
*/
export function defineReactive ( // 把对象的属性变成响应式
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key) // 获取obj对象key属性的数据属性
if (property && property.configurable === false) { // 如果该属性是不能修改或删除的,则直接返回
return
}
// cater for pre-defined getter/setters
const getter = property && property.get //尝试拿到该对象原生的get属性,保存到getter中
const setter = property && property.set //尝试拿到该对象原生的set属性,保存到setter中
if ((!getter || setter) && arguments.length === 2) { //如果getter不存在或者setter存在,且参数只有两个
val = obj[key] //则直接通过obj[ke]获取值,并保存到val中
}
let childOb = !shallow && observe(val) //递归调用observe:当某个对象的属性还是对象时会进入
Object.defineProperty(obj, key, { //调用Object.defineProperty设置obj对象的访问器属性
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() //调用depend()收集依赖,
if (childOb) { // 如果childOb存在
childOb.dep.depend() //则调用childOb.dep.depend()收集依赖
if (Array.isArray(value)) { // 如果 val 是数组则调用小下面的函数处理
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val //如果之前有定义getter,则调用getter获取值,否则就赋值为val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== 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) // newVal调用observe处理,newVal为数组或对象其属性也是响应式
dep.notify() // 通知订阅的 watcher 做更新
}
})
}
接下来看 dep.js
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 中存的 wather,循环执行 watcher 中的 update 方法
subs[i].update()
}
}
接着看 watcher.js
/** * Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this) // 执行 watcher 队列 queueWatcher 方法
}
}
scheduler.js
:
export function queueWatcher (watcher: Watcher) { const id = watcher.id
if (has[id] == null) { // 相同的 watcher 只添加进队列一次
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue) // 调用异步 nestTick 处理 watcher 队列
}
}
}
以上是 vue 响应式原理 的全部内容, 来源链接: utcz.com/z/379903.html