再聊一下用Proxy实现双向数据绑定(含数组)

上一篇的末尾,笔者简单介绍了Object.defineProperty在数组监控方面的不足以及其替代品Proxy。但是对于前者总感觉还少点什么,emmmmm...好像是demo,于是笔者精心准备了一下。所以本篇会主要会分成两大块:一是讲述如何弥补Object.defineProperty先天不足的情况下实现对数组的精准监控,二是着重用Proxy实现双向数据绑定。

Object.defineProperty

首先呢,对于 Object.defineProperty在数组监控方面的不足,我们不仅要知其然,更要知其所以然。因此先用栗子来证实下这个 不足。上代码:
let data = {
    list: []
}

Object.keys(data).forEach(function (key) {
    let value = data[key];
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get() {
            return value;
        },
        set(newValue) {
            console.log(`Setting`);
            value = newValue;
            return true;
        }
    })
})

data.list.push(1);                 //--->  A
// data.list = [1, 2, 3];                //--->  B
console.log(data.list);
事先我们需要明确下:所以对数组的监控,就是能监测到数组元素的 增加或者删除。因此按照‘理论’,上述代码如果向 list 中增加元素的时候,理应会有打印 Setting(笔者已经标记了最后的 A行 和 B行)

在只注释 B行情况下看运行结果:

/usr/local/bin/node --inspect-brk=17809 demo.js 
Debugger listening on ws://127.0.0.1:17809/48911764-8533-4c95-a501-20384c924f6a
Debugger attached.
Array(1) [1]

可以看出,虽然我们成功得向数组里增加了一个元素,但是并没有打印出 Setting,因此就是说并没有监测到数组的变化.

在只注释 A行情况下看运行结果:

/usr/local/bin/node --inspect-brk=27303 demo.js 
Debugger listening on ws://127.0.0.1:27303/2fac4f06-e775-4485-b70b-b2660a98c2b8
Debugger attached.
Setting
Array(3) [1, 2, 3]

我们成功得向数组里增加了一个元素也没有打印出 Setting,说明数组的变化被监测到了.源码

所以我们可以大胆得猜测:当监控 数组 数据对象的时候,实质上就是监控数组的 地址,地址不变也就不会被监测到,所以我们向 listpush 元素的时候并没有触发打印;当我们直接替换 list 对象的时候就触发了打印。所以这就是 Object.defineProperty在数组监控方面的不足。

下面就用例子介绍下如何弥补这方面的不足(当然,也是Vue的处理方式)

其核心思想就是 覆写 数组对象中的方法,在调用数组方法的同时能触发回调。下面是核心代码:
let arrayMethod = Object.create(Array.prototype);
    ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
        Object.defineProperty(arrayMethod, method, {
            enumerable: true,
            configurable: true,
            value: function () {
                let args = [...arguments]
                Array.prototype[method].apply(this, args);
                console.log(`operation: ${method}`)
                dep.notify();
            }
        })
    });
    
[Array Object].__proto__ = arrayMethod;
同时这样做的好处是,仅仅是数据中的数组对象的原型被修改掉了,并不会影响到全局的数组对象。

然后笔者也做了一个例子,如下:

再聊一下用Proxy实现双向数据绑定(含数组)_第1张图片

通过向 list 中添加随机数或者删除随机数,能同步渲染到页面上,源码在这

Proxy

Proxy 意为 代理,通俗来说就是在 目标数据对象 外围设置一层 拦截
举个形象的栗子:有一个 仓库,一开始大家可以任意得存放货物,后来老板请了一个 仓库管家,所有人想要存放货物的必须要经过管家的手。而且老板还给管家制定了一套 管理标准,管家对仓库的管理必须严格按照这套标准。

根据这个栗子,罗列了三个关键词:仓库仓库管家管理标准,下面用一段简单的代码来表述下, 如果想全面得学习Proxy, 请参考阮一峰老师的博客:

let dataProxy = new Proxy(data, {
    set() {

    },
    get() {

    }
})

那么代码与栗子的对于关系就出来了:

仓库 --- data
仓库管家 --- Proxy / dataProxy
管理标准 --- {set(),get()}

而且可以注意到 Proxy 其实是个构造函数,所有我们对原数据对象的操作都得通过构造函数new出来的对象 dataProxy就好像所有货物的存取都得通过管家一样

然后接下来笔者用 Proxy 写了一个栗子,用来 重现 上面对数组的监控,核心代码如下:

//数据源
let vm = {
    list: [1, 2, 3, 4]
}

let vmProxy = new Proxy(vm.list, {
        set(target, prop, value) {
            console.log(`Setting: ${value}`);
            Reflect.set(target, prop, value);
            dep.notify();
            return true;
        }
    })

我们可以注意到我们并没有直接将 vm 用Proxy进行“包装”,而是将vm.list进行“包装”。因为笔者在学习的时候发现如果直接用 vm 的话,当我们向 list 中添加元素的时候并不会被监测到,笔者猜测的原因和 Object.defineProperty 一样,栗子呈上。如果有知道原因的小伙伴,请多多指教。
篇幅有限,这个例子的代码直接上链接了,有兴趣的小伙伴可以直接down下来看。

数组说完了,剩下就普通类型的数据就是按照正常套路走就对了。另外,笔者这次吸取上篇博客的教训,将代码写得更详尽,实现方式更贴近框架级别。用Proxy实现数据的双向绑定,栗子的功能和上篇博客是一样的,只是内容更加饱满。该栗子中笔者简单实现了一个简单版的类Vue的框架 View,包含Html文档的解析、Watcher的构造、数据的绑定等,代码简单不复杂,所以这里就不赘述了,我觉得千言万语不敌一段代码,那么相关的代码就此呈上。

感谢小伙伴的捧场!我会坚持写博客,分享自己的见闻和工作中遇到的坑,共同学习,共同进步

你可能感兴趣的:(代理,es6)