《深入浅出VUE》笔记(一)Object的变化侦测

本文的记录主要基于《深入浅出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。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值