Observe 数据监听

简介

为了减少手动更新的频率,系统框架都需要使用代理监听数组或对象的变化而修改DOM,ES6推出了Proxy方法打开了数据监听思路,目前Proxy是当前主流的监听方法。AXUI中的axObserve监听函数可实现数据监听,支持劫持和深度监听。

基本用法

该函数完整写法为axObserve(obj, options),参数说明如下

  • obj:Array数组或Object对象
  • options:监听参数,支持多个参数:
    • deep:是否为深度监听,也就是每个数组和对象都监听,默认为false只监听第一层数组或对象
    • accept:监听到数据变化是否直接给原数组或对象赋值,默认true,可选择false,为false则需要在回调里手动赋值,例如Reflect.set(target, key, value)
    • once:是否为只监听一次complete回调,默认为false每次数据操作都执行一次complete回调,可使用true,以此避免反复操作回调
    • methods:代理使用的监听方法,是数组,一共有三个值get/set/deleteProperty,默认['set','deleteProperty']即只监听设置和删除两个行为不监听get行为,如果是深度监听而且数据较大建议取消get监听,以提高运行效率
    • callback:回调函数,支持一个参数,该参数是一个对象包含的key如下:(注意不同监听类型数据结构可能会不一样)
      • target:代理的原数组或对象
      • key:监听到的属性
      • value:将赋予的新值
      • raw:原旧值
      • type:监听类型,包含add、edit、delete、get和complete五种
      • proxy:当前代理对象

插件实例最终可返回{proxy:'',instance:''}:

  • new axObserve(...).proxy:返回原数据的代理对象,也就是对该proxy操作才会触发监听
  • new axObserve(...).instance:返回监听实例,可以使用实例方法on

add类型监听

对数组增加新项或者对对象增加新的键值会触发add类型监听。

例如obj.x = 1,会触发一次add类型监听和一次complete类型监听。

  •                                             <a href="###" class="ax-btn" id="btnAdd">试一试</a>
                                            
  •                                             let dataAdd = { a: 1, b: 2, c: 3 },
                                                    proxyAdd = new axObserve(dataAdd, {
                                                        callback: (data) => {
                                                            console.log(data.type + '监听:', data);
                                                            //complete类型中没有data.key和data.value
                                                        }
                                                    }),
                                                    btnAdd = document.querySelector('#btnAdd');
                                                btnAdd.onclick = () => {
                                                    proxyAdd.proxy.d = 100;
                                                }
                                            

edit类型监听

对数组和对象的已有key进行改写会触发edit类型监听,但是改写的value与原value一致的话不会触发edit类型监听。

例如arr[2] = 1,会触发一次edit类型监听和一次complete类型监听。

  •                                             <a href="###" class="ax-btn" id="btnEdit">试一试</a>
                                            
  •                                             let dataEdit = [9, 6, 8, 1, 3, 5],
                                                    proxyEdit = new axObserve(dataEdit, {
                                                        callback: (data) => {
                                                            console.log(data.type + '监听:', data);
                                                            //complete类型中没有data.key和data.value
                                                        }
                                                    }),
                                                    btnEdit = document.querySelector('#btnEdit');
                                                btnEdit.onclick = () => {
                                                    proxyEdit.proxy[2] = 100;
                                                }
                                            

数组length监听

有一类特别的edit类型监听即对数组length变化监听。对数组的pop()、shift()、unshift()、push()、splice()操作相当于对数组进行剪切,这几个数组方法都会改变数组的length。

比如pop()方法会触发三次get监听,一次delete监听,一次edit监听和一次complete监听。

  •                                             <a href="###" class="ax-btn" id="btnLength">试一试</a>
                                            
  •                                             let dataLength = [9, 6, 8, 1, 3, 5],
                                                    proxyLength = new axObserve(dataLength, {
                                                        callback: (data) => {
                                                            console.log(data.type + '监听:', data);
                                                            //get和delete类型中没有data.value
                                                            //complete类型中没有data.key和data.value
                                                        }
                                                    }),
                                                    btnLength = document.querySelector('#btnLength');
                                                btnLength.onclick = () => {
                                                    proxyLength.proxy.pop();
                                                }
                                            

delete类型监听

删除数组项或删除对象的key会触发delete类型监听。

例如delete obj.a,会触发一次delete类型监听和一次complete类型监听。

  •                                             <a href="###" class="ax-btn" id="btnDelete">试一试</a>
                                            
  •                                             let dataDelete = { a: 1, b: 2, c: 3 },
                                                    proxyDelete = new axObserve(dataDelete, {
                                                        callback: (data) => {
                                                            console.log(data.type + '监听:',data);
                                                            //delete类型中没有data.proxy
                                                        }
                                                    }),
                                                    btnDelete = document.querySelector('#btnDelete');
                                                btnDelete.onclick = () => {
                                                    delete proxyDelete.proxy.a;
                                                }
                                            

get类型监听

读取数组下标或者对象key时会触发get类型监听。

  •                                             <a href="###" class="ax-btn" id="btnGet">试一试</a>
                                            
  •                                             let dataGet = { a: 1, b: 2, c: 3 },
                                                    proxyGet = new axObserve(dataGet, {
                                                        methods:['get'],
                                                        callback: (data) => {
                                                            console.log(data.type + '监听:', data);
                                                            //get类型中没有data.value
                                                        }
                                                    }),
                                                    btnGet = document.querySelector('#btnGet');
                                                btnGet.onclick = () => {
                                                    proxyGet.proxy.a;
                                                }
                                            

complete类型监听

add、edit和delete之后最终都会触发complete类型监听,complete类型监听在频繁修改数据的时候很有用。频繁修改数据当中会产生很多的监听,但是只有数据全部处理完毕才有必要进行下一步业务。多次数据同步操作最终只会产生一次complete类型监听。

  •                                             <a href="###" class="ax-btn" id="btnComplete">试一试</a>
                                            
  •                                             let dataComplete = [9, 6, 8, 1, 3, 5],
                                                proxyComplete = new axObserve(dataComplete, {
                                                    callback: (data) => {
                                                        console.log(data.type + '监听:', data);
                                                    }
                                                }),
                                                btnComplete = document.querySelector('#btnComplete');
                                                btnComplete.onclick = () => {
                                                    proxyComplete.proxy.unshift(100);
                                                    proxyComplete.proxy.push(200);
                                                    proxyComplete.proxy[2] = 300;
                                                    proxyComplete.proxy.pop();
                                                }
                                            

深度监听

本插件默认只监听数组或对象的第一层,如果需要监听子数组或子对象,需要对options开启deep:true。如果子数组length未知或者子对象的keys未知,会带来性能开销,不建议开启。

  •                                             <a href="###" class="ax-btn" id="btnComplete">试一试</a>
                                            
  •                                             let dataDeep = {
                                                    fruit: {a: 1, b: 2, c: 3},
                                                    food: { x: 1, y: 2, z: 3 }
                                                },
                                                    proxyDeep = new axObserve(dataDeep, {
                                                        deep: true, 
                                                        callback: (data) => {
                                                            console.log(data.type + '监听:', data);
                                                        }
                                                    }),
                                                    btnDeep = document.querySelector('#btnDeep');
                                                btnDeep.onclick = () => {
                                                    proxyDeep.proxy.food.x = 100;
                                                    proxyDeep.proxy.fruit.d = { apple: 1, banana: 2 };
                                                    proxyDeep.proxy.fruit.d.apple = 200;
                                                }
                                            

once监听

创建一个Proxy对象,每次同步或异步任务下(如反复点击按钮)都会产生一次complete监听;但是有时候我们需要在Proxy在产生一次complete监听之后就不需要在异步任务中(如反复点击按钮)产生新的complete监听,此时需要设置onece:true

  •                                             <a href="###" class="ax-btn" id="btnOnce">试一试</a>
                                            
  •                                             let dataOnce = [9, 6, 8, 1, 3, 5],
                                                    proxyOnce = new axObserve(dataOnce, {
                                                        once: true,
                                                        callback: (data) => {
                                                            console.log(data.type + '监听:', data);
                                                        }
                                                    }),
                                                    btnOnce = document.querySelector('#btnOnce');
                                                btnOnce.onclick = () => {
                                                    proxyOnce.proxy.push(200);
                                                }
                                            

数据劫持和手动赋值

当修改值时会比对旧值,如果不一致将自动用新值替代旧值,如果需要劫持数据并手动赋值,需要对options设置accept:false,如果需要赋值需要在回调里使用Reflect.set方法

  •                                             <a href="###" class="ax-btn" id="btnAccept">试一试</a>
                                            
  •                                             let dataAccept = { a: 1, b: 2, c: 3 },
                                                    proxyAccept = new axObserve(dataAccept, {
                                                        accept: false,
                                                        callback: (data) => {
                                                            if (key === 'a') {
                                                                Reflect.set(data.target, key, data.value);
                                                            }
                                                            if (key === 'c') {
                                                                Reflect.deleteProperty(data.target, key);
                                                            }
                                                            if (key === 'complete') {
                                                                console.log('最终值:', data.target);
                                                            }
                                                        }
                                                    }),
                                                    btnAccept = document.querySelector('#btnAccept');
                                                btnAccept.onclick = () => {
                                                    proxyAccept.proxy.a = 100;
                                                    proxyAccept.proxy.b = 200;
                                                    delete proxyAccept.proxy.c;
                                                }
                                            

on语法

callback语法需要与new实例时同时使用,而on语法可与new实例分步进行。其基本语法为let ob=new axObserve(...);ob.instance.on(key,callback)。key为监听到的属性,callback的参数与options.callback参数相同。

  •                                             <a href="###" class="ax-btn" id="btnAccept">试一试</a>
                                            
  •                                             let dataOn = { a: 1, b: 2, c: 3 },
                                                    proxyOn = new axObserve(dataOn),
                                                    btnOn = document.querySelector('#btnOn');
                                                    proxyOn.instance.on('a',(data)=>{
                                                        console.log(data);
                                                    });
                                                    proxyOn.instance.on('complete',(data)=>{
                                                        console.log(data);
                                                    });
                                                btnOn.onclick = () => {
                                                    proxyOn.proxy.a = 100;
                                                    proxyOn.proxy.b = 200;
                                                    delete proxyOn.proxy.a;
                                                }