主要做了这么几件事:数据劫持、收集依赖、派发更新
1.数据劫持:new Vue的时候遍历data对象,用Object.defineProperty给所有属性加上了getter和setter
2.依赖的收集:render的过程,new Dep()依赖收集类 会触发数据的getter,在getter的时候把当前的watcher对象收集起来
3.派发更新:setter的时候,遍历这个数据的依赖对象(watcher对象),进行更新
数据劫持
通过Object.defineProperty劫持对象属性,让数据变为是可观察的
class Vue {
constructor(options) {
observer(options.data);
}
observer(obj) {
if (!obj || (typeof obj !== 'object')) {
return;
}
// 遍历data对象 逐个加上getter setter
// 1.获取对象中的所有key
// 2.遍历key组成的数组 处理函数是 传递 本对象 本key 本key对应的value
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}
defineReactive(obj, key, val) {
const dep = new Dep(); // Dep是依赖收集类
Object.defineProperty(obj, key, {
enumerable: true, // 属性可枚举
configurable: true //属性可被修改或删除
get() {
dep.addSub(Dep.target); // 依赖采集
return val;
},
set(newVal) {
if (newVal === val) return;
dep.notify(newVal); // 派发更新
}
})
}
}
可以看到主要就是这个defineReactive函数,他利用Object.defineProperty实现了数据劫持
依赖收集&派发更新
vue是怎么知道当数据改变的时候都要去通知谁呢?它用了一个订阅者Dep,用来存放我们的观察者对象,当数据发生改变,就通知观察者,观察者通过调用自己的update方法完成更新。
<!--订阅者Dep类-->
class Dep {
constructor () {
this.newDeps = [] // 用来存放我们的依赖对象(也即观察者)
}
addDep (watcher) {
this.newDeps.push(watcher) // 向队列里新加一下watcher对象
}
update () {
this.newDeps.forEach((sub) => {
sub.update(); // 遍历watcher进行更新
})
}
}
<!--观察者Watcher类-->
class Watcher {
constructor () {
Dep.target = this // new Watcher的时候把观察者存放到Dep.target里面
}
update () {
console.log("视图更新啦~"); // 更新视图
<!--queueWatcher(this) 异步更新策略 后面再写一篇介绍 -->
}
}
new一个Watcher对象,此时它会指向Dep.target,在render过程触发getter,把Dep.target添加到依赖队列。这样便完成了依赖的收集。数据改变,通知依赖进行update操作。
- 简易版
// 科隆数组原型
const arrYuanXing = Array.prototype
// 深拷贝一份
const keLongYuanXing = Object.create(arrYuanXing)
const methodsArr = ['push', 'pop', 'shift', 'unshift', 'reverse', ]
// 遍历修改克隆出来的数组原型方法 (不能直接修改 Array.prototype 上面的额方法)
methodsArr.forEach(method => {
keLongYuanXing[method] = function() {
// 渲染UI
renderView()
//method 是 methodsArr 中的item 方法名
arrYuanXing[method].call(this, ...arguments) // 等价与调用了 obj.push()
}
})
function renderView() {
console.log('view发生了改变');
}
// 1.怎么个知道obj.name 发生改变了?
// 答案: 我们要监听这个obj
//
function observe(target) {
// target 监听目标 可能是 null array object
// 判断 target是什么
// 1. 如果是string number null 就不监听了直接return
if (typeof target !== 'object' || target == 'null') {
return target
}
// 2. 如果是数组
if (Array.isArray(target)) {
// 让数组的原型 指向 克隆出来的原型
target.__proto__ = keLongYuanXing
}
// 3. 如果是object (对象中的每一个key都是响应式的 需要遍历监听)
for (const key in target) {
// 监听方法 监听对象的每一个key 和 value
let value = target[key]
/* 这里要继续监听value 是否是个对象 是的话就递归 但是假如有10000层 那就卡死了递归*/
observe(value)
/* vue 3.0 就改的是这里 但是2.0还是用的递归*/
// 重点:采用的是es5中的方法 object.defineProperty
Object.defineProperty(target, key, {
// 赋值
get() {
return value
},
// 赋新的值 (obj.key 只要改变就触发这个方法 我们刚好在这个方法里面更新ui)
set(newValue) {
observe(value)
renderView()
value = newValue
}
})
}
}
var obj = {
name: '小明',
arr: [1, 2, 3],
sifangqian: {
money: 100
}
}
// 调用监听
observe(obj)
// 响应式:obj.name 发生改变了就去更新ui
// obj.name = '中国加油'
// obj.sifangqian.money = '9994'
obj.arr.push(4)
// ! 总结 重点就是 object.defineProperty 对数据劫持(发生变化之前 先做一些事情)