zoukankan      html  css  js  c++  java
  • Vue中数组变动监听

    Vue中数组变动监听

    Vue的通过数据劫持的方式实现数据的双向绑定,即使用Object.defineProperty()来实现对属性的劫持,但是Object.defineProperty()中的setter是无法直接实现数组中值的改变的劫持行为的,想要实现对于数组下标直接访问的劫持需要使用索引对每一个值进行劫持,但是在Vue中考虑性能问题并未采用这种方式,所以需要特殊处理数组的变动。

    描述

    Vue是通过数据劫持的方式来实现数据双向数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,该方法允许精确地添加或修改对象的属性,对数据添加属性描述符中的gettersetter存取描述符实现劫持。

    var obj = { __x: 1 };
    Object.defineProperty(obj, "x", {
        set: function(x){ console.log("watch"); this.__x = x; },
        get: function(){ return this.__x; }
    });
    obj.x = 11; // watch
    console.log(obj.x); // 11
    

    而如果当劫持的值为数组且直接根据下标处理数组中的值时,Object.defineProperty()中的setter是无法直接实现数组中值的改变的劫持行为的,所以需要特殊处理数组的变动,当然我们可以对于数组中每一个值进行循环然后通过索引同样使用Object.defineProperty()进行劫持,但是在Vue中尤大解释说是由于性能代价和获得的用户体验收益不成正比,所以并没有使用这种方式使下标访问实现响应式,具体可以参阅githubVue源码的#8562

    var obj = { __x: [1, 2, 3] };
    Object.defineProperty(obj, "x", {
        set: function(x){ console.log("watch"); this.__x = x; },
        get: function(){ return this.__x; }
    });
    obj.x[0] = 11;
    console.log(obj.x); // [11, 2, 3]
    obj.x = [1, 2, 3, 4, 5, 6]; // watch
    console.log(obj.x); // [1, 2, 3, 4, 5, 6]
    obj.x.push(7);
    console.log(obj.x); // [1, 2, 3, 4, 5, 6, 7]
    
    // 通过下标对每一个值进行劫持
    var obj = { __x: [1, 2, 3] };
    Object.defineProperty(obj, "x", {
        set: function(x){ console.log("watch"); this.__x = x; },
        get: function(){ return this.__x; }
    });
    obj.x.forEach((v, i) => {
        Object.defineProperty(obj.x, i,{
            set:function(x) { console.log("watch"); v = x; },
            get: function(){ return v; }
        })
    })
    obj.x[0] = 11; // watch
    console.log(obj.x); // [11, 2, 3]
    

    Vue中对于数据是经过特殊处理的,对于下标直接访问的修改同样是不能触发setter,但是对于push等方法都进行了重写。

    <!DOCTYPE html>
    <html>
    <head>
        <title>Vue中数组变动监听</title>
    </head>
    <body>
        <div id="app"></div>
    </body>
    <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                msg: [1, 2, 3]
            },
            template:`
                <div>
                    <div v-for="item in msg" :key="item">{{item}}</div>
                    <button @click="subscript">subscript</button>
                    <button @click="push">push</button>
                </div>
            `,
            methods:{
                subscript: function(){
                    this.msg[0] = 11;
                    console.log(this.msg); // [11, 2, 3, __ob__: Observer]
                },
                push: function(){
                    this.msg.push(4, 5, 6);
                    console.log(this.msg); // [1, 2, 3, 4, 5, 6, __ob__: Observer]
                }
            }
        })
    </script>
    </html>
    

    Vue中具体的重写方案是通过原型链来完成的,具体是通过Object.create方法创建一个新对象,使用传入的对象来作为新创建的对象的__proto__,之后对于特定的方法去拦截对数组的操作,从而实现对操作数组这个行为的监听。

    // dev/src/core/observer/array.js
    /*
     * not type checking this file because flow doesn't play well with
     * dynamically accessing methods on Array prototype
     */
    
    import { def } from '../util/index'
    
    const arrayProto = Array.prototype
    export const arrayMethods = Object.create(arrayProto)
    
    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
    /**
     * Intercept mutating methods and emit events
     */
    methodsToPatch.forEach(function (method) {
      // cache original method
      const original = arrayProto[method]
      def(arrayMethods, method, function mutator (...args) {
        const result = original.apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args
            break
          case 'splice':
            inserted = args.slice(2)
            break
        }
        if (inserted) ob.observeArray(inserted)
        // notify change
        ob.dep.notify()
        return result
      })
    })
    

    处理方法

    重赋值

    Object.defineProperty()方法无法劫持对于数组值下标方式访问的值的改变,这样的话就需要避免这种访问,可以采用修改后再赋值的方式,也可以采用数组中的一些方法去形成一个新数组,数组中不改变原数组并返回一个新数组的方法有sliceconcat等方法以及spread操作符,当然也可以使用map方法生成新数组,此外在Vue中由于重写了splice方法,也可以使用splice方法进行视图的更新。

    var obj = { __x: [1, 2, 3] };
    Object.defineProperty(obj, "x", {
        set: function(x){ console.log("watch"); this.__x = x; },
        get: function(){ return this.__x; }
    });
    
    obj.x[0] = 11;
    obj.x = obj.x; // watch
    console.log(obj.x); // [11, 2, 3]
    
    obj.x[0] = 111;
    obj.x = [].concat(obj.x); // watch
    console.log(obj.x); // [111, 2, 3]
    
    obj.x[0] = 1111;
    obj.x = obj.x.slice(); // watch
    console.log(obj.x); // [1111, 2, 3]
    
    obj.x[0] = 11111;
    obj.x = obj.x.splice(0, obj.x.length); // watch
    console.log(obj.x); // [11111, 2, 3]
    

    Proxy

    Vue3.0使用Proxy实现数据劫持,Object.defineProperty只能监听属性,而Proxy能监听整个对象,通过调用new Proxy(),可以创建一个代理用来替代另一个对象被称为目标,这个代理对目标对象进行了虚拟,因此该代理与该目标对象表面上可以被当作同一个对象来对待。代理允许拦截在目标对象上的底层操作,而这原本是Js引擎的内部能力,拦截行为使用了一个能够响应特定操作的函数,即通过Proxy去对一个对象进行代理之后,我们将得到一个和被代理对象几乎完全一样的对象,并且可以从底层实现对这个对象进行完全的监控。

    var target = [1, 2, 3];
    var proxy = new Proxy(target, {
        set: function(target, key, value, receiver){ 
            console.log("watch");
            return Reflect.set(target, key, value, receiver);
        },
        get: function(target, key, receiver){ 
            return target[key];
        }
    });
    proxy[0] = 11; // watch
    console.log(target); // [11, 2, 3]
    

    每日一题

    https://github.com/WindrunnerMax/EveryDay
    

    参考

    https://zhuanlan.zhihu.com/p/50547367
    https://github.com/vuejs/vue/issues/8562
    https://juejin.im/post/6844903699425263629
    https://juejin.im/post/6844903597591773198
    https://segmentfault.com/a/1190000015783546
    https://cloud.tencent.com/developer/article/1607061
    https://www.cnblogs.com/tugenhua0707/p/11754291.html
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
    
  • 相关阅读:
    参考教程 python 手动搭建ANN,并进行具体实现
    ubuntu18.04 ssh 问题
    Ubuntu18.04 启动后进不了界面 [ failed command: READ DMA ]
    Emacs 使用 markdown 模式
    linux 下 Emacs dired 模式 隐藏 dot file ".filename" 文件
    Ubuntu 16.04 安装后(使用旧的用户目录)登陆不进去
    AWS-SS配置过程
    Jupyter-notebook 导出时不显示Input[]代码
    [网络流24题] 方格取数问题 (最大权独立集---网络最小割)
    [网络流24题] 最长递增子序列 (最多不相交路径---网络最大流)
  • 原文地址:https://www.cnblogs.com/WindrunnerMax/p/13657686.html
Copyright © 2011-2022 走看看