Vue的响应式由Observer
、Dep
和Watcher
三个核心部分组成。
这里我把Vue的响应式分为三个阶段
数据劫持
依赖收集
派发更新
我们先准备好需要用的工具函数
js
export function def( obj, key, value, enumerable){
Object.defineProperty( obj, key,{
value,
enumerable,
writable:true,
configurable:true
})
}
数据劫持
通俗的来说就为属性转为响应式 我们需要一个observe
函数,注意与Observer
类区别
js
//observe函数 判断一个值是否需要转为响应式,并触发 Observer 的创建。
export default function observe(value){
//如果value不是对象,则什么都不做
if(typeof value !== 'object') return
//如果value是对象,则new一个Observer对象
var ob
if(typeof value.__ob__ !== 'undefined'){ //存在__ob__代表他已经被响应式处理过了
ob = value.__ob__
}else{
ob = new Observer(value)
}
return ob
}
除此之外我们还需要一个方法用来真正调用Object.defineproperty
这个方法就是defineReactive
js
import observe from './observe'
export default function defineReactive(data, key, value){
if(arguments.length == 2) {
value = data[key]
}
// 子元素要进行observe
let childOb = observe(value)
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get(){
console.log("🚀 您正在访问" + key)
return value
},
set(newValue){
console.log("🚀 您正在设置" + key + '=' + newValue)
if(newValue === value) return
value = newValue
// 新值也要观察
childOb = observe(newValue)
}
})
}
js
let obj = {
a:4,
b:{
m:1,
d:{
n:2
}
}
}
observe(obj)
console.log("🚀 ~ obj:", obj)
obj.b.d.n = 3
也就是说我们已经成功的为obj的属性包括子对象都完成了响应式处理 每个__ob__都是Observer类的实例
这里给出Observer类的代码
文章开头给出的工具函数def
此时就派上用场了,我们通过def 将__ob__设置为不可枚举
js
export default class Observer{
constructor(data){
// 保存数据引用
this.data = data
// 给数据添加__ob__属性,值为this,表示数据已经被观察,并且这个属性不可枚举 所以我们通过工具函数def来做
// this 代表创建的实例
def(data,'__ob__',this, false)
this.walk(data)
}
// 添加walk方法
walk(data) {
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
}
对数组的处理
由于Object.defineProperty
对数组不能够很好的监听比如说 a[1] = 10
这种方式不能够监听到,所以我们需要对数组特殊处理 我们将对数组的处理放在另一个js文件里
js
array.js
//数组检测不到 需要改写方法
import { def } from "./utils"
//保存一下原型
const arrayPrototype = Array.prototype
export const arrayMethods = Object.create(arrayPrototype)
const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsNeedChange.forEach(method => {
const original = arrayPrototype[method]
def( arrayMethods, method, function(...args){
const result = original.apply(this, args) //先保存一下执行结果
const ob = this.__ob__
let inserted = []
//push,unshfit splice func can add or delete element of array ,need new element to observe
//实际上这里我们只针对 push splice unshfit进行特殊处理
switch(method){
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
// splice(下标,删除的数量,插入的项)
//所以 从2 开始 获取插入的项
inserted = args.slice(2)
break;
}
//判断有没有要插入的项 ,有的话也要变成响应式
if(inserted.length) {
ob.observeArray(inserted)
}
//obj.b.push(4)
//这里this取决于 也就是数组
console.log('啦啦啦')
return result
},false)
})
除此之外Observer
类也要做出改变
js
export default class Observer{
constructor(data){
//每个Observer实例都有一个dep
this.dep = new Dep()
// 保存数据引用
this.data = data
// 给数据添加__ob__属性,值为this,表示数据已经被观察,并且这个属性不可枚举 所以我们通过工具函数def来做
// this 代表创建的实例
def(data,'__ob__',this, false)
// 遍历对象的所有属性,使其变为响应式
if(Array.isArray(data)){
//如果是数组,则重写数组方法
Object.setPrototypeOf(data,arrayMethods)
this.observeArray(data) //代理数组
}else {
this.walk(data)
}
}
// 建议添加walk方法
walk(data) {
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
observeArray(data){
data.forEach(item => {
observe(item)
})
}
}
总结
通过observe判断数据是否需要转为响应式,并触发 Observer
类的创建 ,Observer类 的walk方法遍历对象属性,调用defineReactive转为响应式。defineReactive就是利用Object.defineProperty
定义属性的 getter/setter , 为了处理递归子对象 defineReactive也会调用一次observe 这样就形成了 三个函数互相调用完成 数据劫持。
针对数组的特殊处理,重写 push unshift splice。在重写的数组方法中,先调用原始方法如果有新元素插入,使用 observe() 将其转换为响应式调用 dep.notify() 通知视图更新
依赖收集
每个响应式属性对应一个 Dep
实例,负责管理依赖(即 Watcher
)。只有Watcher
触发的 getter
才会收集依赖,哪个Watcher
触发了getter
,就把哪个Watcher
收集到Dep
中
这里给出Dep
类代码
我们用一个数组来存储订阅者watcher
另外这个Dep.target
实际上是一个全局变量,当然你也可以用window.target
这里不影响
js
var uid = 0
export default class Dep {
constructor(){
console.log('Dep构造器');
//存储自己的订阅者也就是watcher实例
this.id = uid++
this.subs = []
}
//添加订阅者
addSub(watcher){
this.subs.push(watcher)
}
depend(){
// Dep.target 是一个全局变量,用于存储当前正在执行的watcher实例
if(Dep.target){
console.log("🚀 ~ Dep ~ depend ~ Dep.target:", Dep.target,this)
// this.addSub(Dep.target)
Dep.target.addDep(this)
}
}
//通知所有订阅者更新
notify(){
console.log('通知所有订阅者更新');
const subs = this.subs.slice()
for(let i = 0; i < subs.length; i++) {
subs[i].update()
}
}
}
// 添加targetStack来处理嵌套的Watcher
Dep.target = null
const targetStack = []
export function pushTarget(target) {
targetStack.push(target)
Dep.target = target
}
export function popTarget() {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
Watcher
类代码 这里我们需要一个工具函数
js
//得到对象的某个属性的值
function parsePath(str) {
const segments = str.split('.')
return (obj) => {
for(let i = 0; i < segments.length; i++) {
if(!obj) return
obj = obj[segments[i]]
}
return obj
}
}
//比如一个对象 { a: { b: { c: 1 } } }
//parsePath('a.b.c') 返回一个函数,这个函数接收一个对象,返回对象的c属性
//parsePath('a.b') 返回一个函数,这个函数接收一个对象,返回对象的b属性
//parsePath('a') 返回一个函数,这个函数接收一个对象,返回对象的a属性
我们可以把watcher
想象成监听器watch
js
let obj = {
a:1,
b:{
c:5
}
}
new Watcher( obj, 'a.b.c', function(){
console.log('当c的值发生变化的时候,触发该回调函数')
})
js
import Dep, { pushTarget, popTarget } from './Dep'
var uid = 0
export default class Watcher {
constructor(target, expression, callback){
console.log('Watcher构造器');
//通过target 配合expression 就可以获取watcher依赖的数据
this.id = uid++
this.target = target
this.getter = parsePath(expression)
this.callback = callback
this.value = this.get() //watcher初始化就会读取值
}
addDep(dep) {
dep.addSub(this)
}
update(){
this.run()
}
get(){
pushTarget(this) // 将当前watcher实例压入栈
const obj = this.target
var value
try {
value = this.getter(obj) //这里watcher读取值 触发 defineReactive中的getter 也就是我们在defineReative中就可以收集依赖了
} finally {
popTarget() // 恢复上一个watcher
}
return value
}
run() {
this.getAndInvoke(this.callback)
}
getAndInvoke(cb){
const value = this.get()
if(value !== this.value || typeof value === 'object'){
//新的值 与旧值不一致 就会触发回调
const oldValue = this.value
this.value = value
cb.call(this.target, value, oldValue)
}
}
}
这里给出defineReactive
完整代码
js
import observe from './observe'
import Dep from './Dep'
export default function defineReactive(data, key, value){
const dep = new Dep() //这里new一个Dep 我认为是为了方便下面调用方法
if(arguments.length == 2) {
value = data[key]
}
// 子元素要进行observe
let childOb = observe(value)
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get(){
console.log("🚀 您正在访问" + key)
if(Dep.target){ //当watcher初始化就会触发getter,这里Dep.target就存储了当前的watcher实例
dep.depend()
if(childOb){
childOb.dep.depend()
// 如果是数组,需要对每个元素进行依赖收集
if(Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set(newValue){
console.log("🚀 您正在设置" + key + '=' + newValue)
if(newValue === value) return
value = newValue
// 新值也要观察
childOb = observe(newValue)
// 发布订阅模式
dep.notify()
}
})
}
// 处理数组的依赖收集
function dependArray(array) {
for(let e of array) {
e && e.__ob__ && e.__ob__.dep.depend()
if(Array.isArray(e)) {
dependArray(e)
}
}
}
派发更新
修改属性值时,setter 会调用 dep.notify()
,通知所有订阅的 Watcher
执行 update()
。