参考:Vue 源码解析系列课程
源码:https://gitee.com/szluyu99/vue-source-learn/tree/master/Data_Reactive_Study
课程目的:彻底弄懂 Vue2 的数据更新原理
MVVM 模式:
侵入式 和 非侵入式:
参考文档:Object.defineProperty() - JavaScript | MDN (mozilla.org)
Object.defineProperty()
用于数据劫持 / 数据代理,利用 JavaScript 引擎赋予的功能,检测对象属性变化。
该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
var obj = {}
Object.defineProperty(obj, 'a', {
value: 3, // 值
writable: false, // 只读
enumerable: true, // 可枚举
})
console.log(obj.a) // 3
obj.a++ // 只读的,不能修改
console.log(obj.a); // 3
该方法的 getter / setter 需要变量周转才能工作:
var obj = {}
var temp // 临时变量
Object.defineProperty(obj, 'a', {
get() {
console.log('get a');
return temp
},
set(newVal) {
console.log('set a', newVal);
temp = newVal
}
})
obj.a = 1
obj.a++
console.log(obj.a) // 2
自定义一个 defineReactive
函数,使用闭包,就不需要设置临时变量:
export default function defineReactive(obj, key, val) {
if (arguments.length == 2) {
val = obj[key]
}
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get() {
console.log('get', key);
return val
},
set(newVal) {
console.log('set', key, newVal);
if (val === newVal) return
val = newVal
}
})
}
let obj = {}
// 实现了数据响应式
defineReactive(obj, 'a', 1)
console.log(obj.a); // 1
obj.a = 10
console.log(obj.a); // 10
对于如下对象,使用上面的 defineReactive
是无法监听 obj.a.m.n
的属性的(只能监听一层,无法监听多层)
let obj = {
a: {
m: {
n: 1
}
},
b: 1
}
想要达到的效果:通过 observe
使 obj 所有属性都变成响应式的
observe(obj)
obj.b++ // 响应式
obj.a.m.n++ // 响应式
程序流程图:(通过各级函数之间的调用实现了递归的效果)
observe.js
:
/**
* 将 obj 所有属性变为响应式
*/
export default function observe(obj) {
if (!obj || typeof obj !== 'object') return
var ob;
// 判断是否已经是响应式
if (typeof obj.__ob__ != 'undefined') {
ob = obj.__ob__
} else {
ob = new Observer(obj)
}
return ob
}
Observer.js
:
/**
* 将一个正常的 object 转换成每个层级的属性都是响应式的 object
*/
export default class Observer {
constructor(obj) {
console.log('Observer constructor', obj);
// 构造函数中的 this 不是类本身,而是表示实例
def(obj, '__ob__', this, false)
// 将 object 中的属性转换成响应式的属性
this.walk(obj)
}
// 遍历 object 的属性,将其转换成响应式的属性
walk(obj) {
console.log('walk', obj);
for (let k in obj) {
defineReactive(obj, k)
}
}
}
/**
* 对 Object.defineProperty 的封装
*/
export const def = function (obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true
})
}
defineReactive.js
:
/**
* 将数据变为响应式
*/
export default function defineReactive(obj, key, val) {
console.log('defineReactive', key);
if (arguments.length == 2) {
val = obj[key]
}
// 子元素要进行 observe,至此形成递归
// 这个递归是多个函数、类循环调用
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get() {
// console.log('get', key);
return val
},
set(newVal) {
console.log('set', key, newVal);
if (val === newVal) return
val = newVal
// 当设置了新值,这个新值也要被 observe
childOb = observe(newVal)
}
})
}
效果:
let obj = {
a: {
m: {
n: 1
}
},
b: 1
}
observe(obj)
obj.b++ // 响应式
obj.a.m.n++ // 响应式
对于数组对象,以上实现是无法监听其 push
、pop
等元素修改方法的:
let obj = {
c: [1, 2, 3, 4]
}
改写 7 个方法:push
、pop
、shift
、unshift
、splice
、sort
、reverse
array.js
:
// 数组的原型
const arrayPrototype = Array.prototype
// 以 Array.prototype 为原型创建 arrayMethods 对象,并暴露
export const arrayMethods = Object.create(arrayPrototype)
// 要被改写的 7 个数组方法
const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
// 数组方法实际是自己写的,自己写的再调用真实的数组方法,中间可以拦截数据
methodsNeedChange.forEach(methodName => {
// 备份原来的方法
const originMethod = arrayPrototype[methodName]
// 给原型定义新的方法
def(arrayMethods, methodName, function () {
console.log('arrayMethods', methodName);
// 执行原来的函数
const result = originMethod.apply(this, arguments)
// arguments 是伪数组对象,转成数组对象
const args = [...arguments]
// 把数组身上的 __ob__ 取出来
const ob = this.__ob__
// 有三种方法 push / unshift / splice 可以插入新项
// 要将插入的新项也变为 observe 的
let inserted = []
switch (methodName) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
// splice(下标, 数量, 插入的新项)
inserted = args.slice(2)
break;
}
// 判断有没有要插入的新项,让新项也变为响应的
if (inserted) ob.observeArray(inserted)
return result
}, false)
})
修改 Observer.js
中构造 Observer 的代码:
/**
* 将一个正常的 object 转换成每个层级的属性都是响应式的 object
*/
export default class Observer {
constructor(obj) {
console.log('Observer constructor', obj);
// 构造函数中的 this 不是类本身,而是表示实例
def(obj, '__ob__', this, false)
// 将 object 中的属性转换成响应式的属性
if (Array.isArray(obj)) {
// 将数组的原型指向 arrayMethods
Object.setPrototypeOf(obj, arrayMethods)
// 让数组变的 observe
this.observeArray(obj)
} else {
this.walk(obj)
}
}
// 遍历 object 的属性,将其转换成响应式的属性
walk(obj) {
console.log('walk');
for (let k in obj) {
defineReactive(obj, k)
}
}
// 数组的特殊遍历
observeArray(arr) {
for (let i = 0, l = arr.length; i < l; i++) {
// 逐项进行 observe
observe(arr[i])
}
}
}
使用效果:
let obj = {
c: [1, 2, 3, 4]
}
observe(obj)
obj.c.push(55, 66, 77)
obj.c.splice(1, 1, [1, 2])
console.log(obj.c);
什么是依赖?需要用到数据的地方,称为依赖
Dep 类 和 Watcher 类:
参考文章:Vue深入响应式原理
使用效果:
let obj = {
a: 1,
b: {
m: {
n: 1
}
},
c: [1, 2, 3, 4]
}
observe(obj)
// 监控依赖
new Watcher(obj, 'b.m.n', val => {
console.log('Watcher 在监控 b.m.n', val)
})
obj.b.m.n++
console.log(obj)
Dep.js
:
/**
* 全局唯一的 依赖收集器
*/
export default class Dep {
constructor() {
// console.log('Dep constructor');
this.id = uid++
// 用数组存储自己的订阅者,这个数组中存放 Watcher 实例
this.subs = [] // subscribers
}
// 添加订阅
addSub(sub) {
this.subs.push(sub)
}
// 添加依赖
depend() {
// Dep.target 就是自己指定的全局的位置(window.targte 也可以,全局唯一即可)
if (Dep.target) {
this.addSub(Dep.target)
}
}
// 通知更新
notify() {
console.log('Dep notify');
// 浅克隆一份
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Watcher.js
:
var uid = 0
export default class Watcher {
// 监听 target 对象的 expression 属性,执行 callback 回调
constructor(target, expression, callback) {
// console.log('Watcher constructor');
this.id = uid++
this.target = target
this.getter = parsePath(expression) // 解析 expression 为一个函数
this.callback = callback
this.value = this.get()
}
update() {
// console.log('Watcher update');
this.run()
}
get() {
// 进入依赖收集阶段,让全局 Dep.tartget 设置为 Watcher 本身
Dep.target = this
const obj = this.target
// 只要没找到,就一直找
let value
try {
value = this.getter(obj)
} finally {
Dep.target = null
}
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)
}
}
}
// 返回一个可以解析 "a.b.c" 格式的函数
// let fn = parsePath('a.b.c')
// fn({ a: { b: { c: 1 } } })
function parsePath(str) {
var segments = str.split('.');
return obj => {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]];
}
return obj
}
}
其余文件代码省略…
完整源码参考:https://gitee.com/szluyu99/vue-source-learn/tree/master/Data_Reactive_Study