本文的记录主要基于《深入浅出VUE》
一、Object的变化侦测
Object.defineProperty
*依赖:即把用到数据的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍
(1)检测数据单个属性值。按照笔者的思路,整理出以下代码:
新建 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Object的变化观测</title>
</head>
<body>
<script type="module" src="./bundle.js"></script>
</body>
</html>
新建 index.js
import Watcher from "./watcher.js";
import Dep from "./dep.js";
function defineReactive (data, key, val) {
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
dep.depend();
return val;
},
set: function(newVal) {
if (val === newVal) {
return;
}
val = newVal;
dep.notify();
}
})
}
window.testName = '';
defineReactive(window, 'testName', 'Tom')
new Watcher(window, 'testName', function (oldVal, newVal) {
console.log('===================>oldVal, newVal', oldVal, newVal)
})
新建 dev.js
function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
}
export default class Dep {
constructor() {
this.subs = [];
}
// 收集依赖
addSub(sub) {
this.subs.push(sub);
}
// 移除依赖
removeSub(sub) {
remove(this.subs, sub);
}
// 发起收集依赖
depend() {
if (window.target) {
this.addSub(window.target);
}
}
// 触发依赖
notify() {
const subs = this.subs.slice();
for (let i =0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}
新建 watcher.js
// 依赖
export default class Watcher {
constructor (vm, expOrFn, cb) {
this.vm = vm;
this.getter = parsePath(expOrFn);
this.cb = cb;
this.value = this.get();
}
get () {
window.target = this;
// 获取对象的值,自动触发defineReactive的get方法,此时会把依赖收集起来
let value = this.getter.call(this.vm, this.vm);
window.target = undefined;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue);
}
}
const bailRE = /[^\w.$]/;
export function parsePath(path) {
if (bailRE.test(path)) {
return;
}
const seqments = path.split('.');
return function (obj) {
for (let i = 0; i < seqments.length; i++) {
if (!obj) return;
obj = obj[seqments[i]]; // 通过循环对象的属性值,获取对象的值
}
return obj;
}
}
在console内改变属性值,会触发依赖的监控回调,运行结果如下:
完整代码:https://github.com/huangbixia/vue_test_code/tree/main/project_1
(2)检测数据的所有属性值。按照笔者的思路,整理出以下代码:
index.html和dep.js不变。
新建Observer.js
import Dep from "./dep";
function defineReactive (data, key, val) {
if (typeof val === 'object') {
new Observer(val);
}
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend();
return val;
},
set: function (newVal) {
if (val === newVal) {
return
}
val = newVal;
dep.notify();
}
})
}
/*
* Observer类会附加到每一个被侦测的object上
* 一旦被附加上,Observer会将object的所有属性转换为 getter/setter的形式
* 来收集属性的依赖,并且当属性发生变化时会通知这些依赖
*/
export default class Observer {
constructor (value) {
this.value = value;
if (!Array.isArray(value)) {
this.walk(value);
}
}
/*
* walk 会将每一个属性都转换成getter/setter的形式来侦测变化
* 这个方法只有在数据类型为Object时被调用
*/
walk (obj) {
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]]);
}
}
}
新建watcher.js
// 依赖
export default class Watcher {
constructor (vm, expOrFn, cb) {
this.vm = vm;
this.getter = parsePath(expOrFn);
this.cb = cb;
this.value = this.get();
}
get () {
window.target = this;
// 获取对象的值,自动触发defineReactive的get方法,此时会把依赖收集起来
let value = this.getter.call(this.vm, this.vm);
window.target = undefined;
return value;
}
update() {
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue);
}
}
// 不断递归获取子属性的值,自动触发defineReactive的get方法,收集依赖
function getFieldValue (obj) {
if (typeof obj === 'object') {
for (let key in obj) {
let filedValue = obj[key];
if (typeof filedValue === 'object') {
getFieldValue(filedValue);
}
}
}
return;
}
const bailRE = /[^\w.$]/;
export function parsePath(path) {
if (bailRE.test(path)) {
return;
}
const seqments = path.split('.');
return function (obj) {
for (let i = 0; i < seqments.length; i++) {
if (!obj) return;
obj = obj[seqments[i]]; // 通过循环对象的属性值,获取对象的值
getFieldValue(obj);
}
return obj;
}
}
新建index.js
import Watcher from "./watcher.js";
import Observer from "./Observer.js";
window.testObj = {
a: '1',
b: '2'
}
new Observer(window.testObj);
new Watcher(window, 'testObj', function (oldVal, newVal) {
// 如果是对象,不能捕获对象的旧值
console.log('===================>oldVal, newVal', oldVal, newVal)
})
new Watcher(window.testObj, 'a', function (oldVal, newVal) {
// 如果是原始值,可以捕获旧值
console.log('===================>oldVal, newVal', oldVal, newVal)
})
完整代码:https://github.com/huangbixia/vue_test_code/tree/main/project_2
注意点:
Object监测原理
使用Object.defineProperty无法监测到一个新属性被添加到对象中,也无法监测到属性被删除。为了解决这个问题,Vue提供了两个API,vm.$set和Vm.$delete。