• 如何监听数组变化?

    2019-06-11 21:36:14
    起源:在 Vue 的数据绑定中会对一个对象属性的变化进行监听,并且通过依赖收集做出相应的视图更新等等。 问题:一个对象所有类型的属性变化都能被监听到吗? 之前用 Object.defineProperty通过对象的 getter/setter...

    起源:在 Vue 的数据绑定中会对一个对象属性的变化进行监听,并且通过依赖收集做出相应的视图更新等等。

    问题:一个对象所有类型的属性变化都能被监听到吗?

    之前用 Object.defineProperty通过对象的 getter/setter简单的实现了对象属性变化的监听,并且去通过依赖关系去做相应的依赖处理。

    但是,这是存在问题的,尤其是当对象中某个属性的值是数组的时候。正如 Vue 文档所说:

    由于 JavaScript 的限制,Vue 无法检测到以下数组变动:

    1. 当你使用索引直接设置一项时,例如 vm.items[indexOfItem] = newValue
    2. 当你修改数组长度时,例如 vm.items.length = newLength

    Vue 源码中也可以看到确实是对数组做了特殊处理的。原因就是 ES5 及以下的版本无法做到对数组的完美继承

    实验一下?

    用之前写好的 observe做了一个简单的实验,如下:

    import { observe } from './mvvm'
    const data = {
      name: 'Jiang',
      userInfo: {
        gender: 0
      },
      list: []
    }
    // 此处直接使用了前面写好的 getter/setter
    observe(data)
    data.name = 'Solo'
    data.userInfo.gender = 1
    data.list.push(1)
    console.log(data)
    复制代码

    结果是这样的:

    从结果可以看出问题所在,data中 name、userInfo、list 属性的值均发生了变化,但是数组 list 的变化并没有被 observe监听到。原因是什么呢?简单来说,操作数组的方法,也就是 Array.prototype上挂载的方法并不能触发该属性的 setter,因为这个属性并没有做赋值操作。

    如何解决这个问题?

    Vue 中解决这个问题的方法,是将数组的常用方法进行重写,通过包装之后的数组方法就能够去在调用的时候被监听到。

    在这里,我想的一种方法与它类似,大概就是通过原型链去拦截对数组的操作,从而实现对操作数组这个行为的监听。

    实现如下:

    // 让 arrExtend 先继承 Array 本身的所有属性
    const arrExtend = Object.create(Array.prototype)
    const arrMethods = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    /**
     * arrExtend 作为一个拦截对象, 对其中的方法进行重写
     */
    arrMethods.forEach(method => {
      const oldMethod = Array.prototype[method]
      const newMethod = function(...args) {
        oldMethod.apply(this, args)
        console.log(`${method}方法被执行了`)
      }
      arrExtend[method] = newMethod
    })
    
    export default {
      arrExtend
    }
    复制代码

    需要在 defineReactive 函数中添加的代码为:

    if (Array.isArray(value)) {
        value.__proto__ = arrExtend
     }
    复制代码

    测试一下:data.list.push(1)

    我们看看结果:

    上面代码的逻辑一目了然,也是 Vue 中实现思路的简化。将 arrExtend 这个对象作为拦截器。首先让这个对象继承 Array 本身的所有属性,这样就不会影响到数组本身其他属性的使用,后面对相应的函数进行改写,也就是在原方法调用后去通知其它相关依赖这个属性发生了变化,这点和 Object.definePropertysetter所做的事情几乎完全一样,唯一的区别是可以细化到用户到底做的是哪一种操作,以及数组的长度是否变化等等。

    还有什么别的办法吗?

    ES6 中我们看到了一个让人耳目一新的属性——Proxy。我们先看一下概念:

    通过调用 new Proxy() ,你可以创建一个代理用来替代另一个对象(被称为目标),这个代理对目标对象进行了虚拟,因此该代理与该目标对象表面上可以被当作同一个对象来对待。

    代理允许你拦截在目标对象上的底层操作,而这原本是 JS 引擎的内部能力。拦截行为使用了一个能够响应特定操作的函数(被称为陷阱)。

    Proxy顾名思义,就是代理的意思,这是一个能让我们随意玩弄对象的特性。当我们,通过Proxy去对一个对象进行代理之后,我们将得到一个和被代理对象几乎完全一样的对象,并且可以对这个对象进行完全的监控。

    什么叫完全监控?Proxy所带来的,是对底层操作的拦截。前面我们在实现对对象监听时使用了Object.defineProperty,这个其实是 JS 提供给我们的高级操作,也就是通过底层封装之后暴露出来的方法。Proxy的强大之处在于,我们可以直接拦截对代理对象的底层操作。这样我们相当于从一个对象的底层操作开始实现对它的监听。

    改进一下我们的代码?

    const createProxy = data => {
      if (typeof data === 'object' && data.toString() === '[object Object]') {
        for (let k in data) {
          if (typeof data[k] === 'object') {
            defineObjectReactive(data, k, data[k])
          } else {
            defineBasicReactive(data, k, data[k])
          }
        }
      }
    }
    
    function defineObjectReactive(obj, key, value) {
      // 递归
      createProxy(value)
      obj[key] = new Proxy(value, {
        set(target, property, val, receiver) {
          if (property !== 'length') {
            console.log('Set %s to %o', property, val)
          }
          return Reflect.set(target, property, val, receiver)
        }
      })
    }
    
    function defineBasicReactive(obj, key, value) {
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: false,
        get() {
          return value
        },
        set(newValue) {
          if (value === newValue) return
          console.log(`发现 ${key} 属性 ${value} -> ${newValue}`)
          value = newValue
        }
      })
    }
    
    export default {
      createProxy
    }
    复制代码

    对于一个对象中的基础类型的属性,我们还是通过Object.defineProperty来实现响应式的属性,因为这里并不存在痛点,但是在实现对Object类型的属性进行监听的时候,我采用的是创建代理,因为我们之前的痛点在于无法去有效监听数组的变化。当我们使用这种改进方法之后,我们不用像之前通过重写数组的方法来实现对数组操作的监听了,因为之前这种方法存在很多的局限性,我们不能覆盖所有的数组操作,同时,我们也不能响应到类似于data.array.length = 0这种操作。通过代理实现之后,一切都不一样了。我们可以从底层就实现对数组的变化进行监听。甚至能watch到数组长度的变化等等各种更加细节的东西。这无疑解决了很大的问题。

    我们调用一下刚才的方法,试试看?

    let data = {
      name: 'Jiang',
      userInfo: {
        gender: 0,
        movies: []
      },
      list: []
    }
    createProxy(data)
    
    data.name = 'Solo'
    data.userInfo.gender = 0
    data.userInfo.movies.push('星际穿越')
    data.list.push(1)
    复制代码

    输出为:

    结果非常完美~我们实现了对对象所有属性变化的监听Proxy的骚操作还有很多很多,比如说将代理当作原型放到原型链上,这样一来就可以只对子类不含有的属性进行监听,非常的强大。Proxy可以得到更加广泛的应用,而且场景很多。这也是我第一次去使用,还需要多加巩固( ;´Д`)

    转载于:https://juejin.im/post/5ade0e3df265da0b8e7f050b

    展开全文
  • 最近在做编辑器,我们创建一个物体在数据类中就被add到管理数组中。当增加一个线段到数组中,我们要计算线段和其他线段是否有交点等一系列问题,这时候就需要我们在数组增加和移除处重写并增加事件推送。代码如下: ...

    最近在做编辑器,我们创建一个物体在数据类中就被add到管理数组中。当增加一个线段到数组中,我们要计算线段和其他线段是否有交点等一系列问题,这时候就需要我们在数组增加和移除处重写并增加事件推送。代码如下:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
    public class MyList<T> : List<T>
    {
        /// <summary>
        /// 增加元素触发事件
        /// </summary>
        public Action<T> addAction;
        /// <summary>
        /// 移除元素触发事件
        /// </summary>
        public Action<T> removeAction;
        /// <summary>
        /// 数组改变触发事件
        /// </summary>
        public Action<T> changeAction;
        /// <summary>
        /// 清除数组触发事件
        /// </summary>
        private Action clearAction;
    
        public MyList()
        {
    
        }
    
    
        public new void Add(T item)
        {
            base.Add(item);
            if(addAction!=null) addAction.Invoke(item);
            if(changeAction!=null)changeAction.Invoke(item);
        }
    
    
        public new void Remove(T item)
        {
            base.Remove(item);
            if (removeAction != null) removeAction.Invoke(item);
            if (changeAction != null) changeAction.Invoke(item);
        }
    
        public new void AddRanage(IEnumerable<T> collection)
        {
            base.AddRange(collection);
            for (int i = 0; i < collection.Count(); i++)
            {
                if (addAction != null) addAction.Invoke(collection.ElementAt(i));
                if (changeAction != null) changeAction.Invoke(collection.ElementAt(i));
            }
        }
    
    
        public new void RemoveRange(int index,int count)
        {
            if (index < this.Count && index + count < this.Count)
            {
                for (int i = index; i < index+count; i++)
                {
                    if (removeAction != null) removeAction.Invoke(this[i]);
                    if (changeAction != null) changeAction.Invoke(this[i]);
                }
            }
            base.RemoveRange(index,count);
        }
    
        public new void Clear()
        {
            base.Clear();
            if(clearAction!=null)
                clearAction.Invoke();
        }
    }
    

    对于插入Insert有需要监听的也可以自己添加。如有错误,欢迎留言。

    展开全文
  • 为什么80%的码农都做不了架构师?>>> ...

     

    kvo方法很简单,但是有几个需要注意的地方。(自己就掉坑里了)

     

    例如:我需要监听 _selectedArray 数组内容的变化

     

    伪代码:

     

    注意点1,如果数组放到ViewController里面是无法监听的,我们需要把数组放到一个继承NSObject的类里面

    1,首先新建一个类

    @interface  ObserveModel : NSObject

    @property (nonatomic) NSMutableArray *array;

    @end

    @implementation ObserveModel

    @end

     

    2,把_selectedArray 赋给ObserveModel的属性

        _observeModel = [ObserveModel new];

        _observeModel.array = _selectedArray;

     

    3,添加观察者:

     [_observeModel addObserver:self forKeyPath:@"array" options:NSKeyValueObservingOptionNew context:nil];


    4,实现方法 :如果_observeModel.array 发生了改变,则会调用该方法。

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{

     

    }

     

    注意点2,如果我们修改_observeModel.array  

    不能使用[_observeModel.array addObject:cartModel]

    需要使用[[_observeModel mutableArrayValueForKey:@"array"]addObject:cartModel] 

     具体原因可以查查KVO的实现机制。

    5,最后记得移除观察者

        [_observeModel removeObserver:self forKeyPath:@"array"];  

     

    转载于:https://my.oschina.net/yup/blog/671281

    展开全文
  • C# 监听值的变化

    2019-08-03 22:59:39
    1、写一个监听变化的类 public class MonitorValueChange { private Visibility myValue; public Visibility MyValue { get { return myValue; } set ...

    1、写一个监听值变化的类

    public class MonitorValueChange
        {
            private Visibility myValue;
            public Visibility MyValue
            {
                get { return myValue; }
                set
                {
                    if (value != myValue)
                    {
                        WhenMyValueChange();
                    }
                    myValue = value;
                }
            }
            //定义的委托
            public delegate void MyValueChanged(object sender, EventArgs e);
            //与委托相关联的事件
            public event MyValueChanged OnMyValueChanged;
            //事件触发函数
            private void WhenMyValueChange()
            {
                if (OnMyValueChanged != null)
                {
                    OnMyValueChanged(this, null);
    
                }
            }
    
        }
    

      2、调用该类监听数据

    MonitorValueChange change = new MonitorValueChange();
    change.MyValue = Visibility.Visible;
    change.OnMyValueChanged += Change_OnMyValueChanged;
    change.MyValue = Visibility.Collapsed;
    
    private void Change_OnMyValueChanged(object sender, EventArgs e)
    {
       //要做的操作
    }
    

      

    转载于:https://www.cnblogs.com/zqyw/p/10919334.html

    展开全文
  • 定义一个类 @interface KVOMutableArray : NSObject @property (nonatomic, strong)NSMutableArray *array; @end@implementation KVOMutableArray - (instancetype)init { if (self = [super init]) { ...
    1. 定义一个类
    @interface KVOMutableArray : NSObject
    @property (nonatomic, strong)NSMutableArray *array;
    @end
    
    @implementation KVOMutableArray
    - (instancetype)init {
        if (self = [super init]) {
            self.array = [NSMutableArray array];
        }
        return self;
    }
    @end
    1. 在另一个类中,将KVOMutableArray的实例作为属性
    @property (nonatomic, strong)KVOMutableArray *selectedData;
    // 添加观察者
    
     [self addObserver:self forKeyPath:@"selectedData.array" options:NSKeyValueObservingOptionNew context:nil];
    // 实现代理方法
    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context {
        NSLog(@"%@", change);
    
    }
    // 移除观察者
    - (void)dealloc {
        [self removeObserver:self forKeyPath:@"selectedData.array"];
    }
    // 注意:在引用selectedData.array的时候,不能直接self.selectedData.array.不然监听不到变化.应该[self.selectedData mutableArrayValueForKey:@"array"]

    另注:很多帖子说要重写以下方法,但是我测试没有重写这些方法也可以。不知道这些方法的作用到底是什么,如果有看到这篇帖子并且知道的请告知,谢谢!
    - (NSUInteger)countOfArray {
    return self.array.count;
    }
    - (id)objectInArrayAtIndex:(NSUInteger)index {
    return [self.array objectAtIndex:index];
    }
    - (void)insertObject:(id)object inArrayAtIndex:(NSUInteger)index {
    [self.array insertObject:object atIndex:index];
    }
    - (void)removeObjectFromArrayAtIndex:(NSUInteger)index {
    [self.array removeObjectAtIndex:index];
    }
    - (void)replaceObjectInArrayAtIndex:(NSUInteger)index withObject:(id)object {
    [self.array replaceObjectAtIndex:index withObject:object];

    }

    展开全文
  • 我们都知道,vue 的数据监听是通过 Object.defineProperty 实现的,在 getter 中收集依赖,在 setter 中触发变更,当我们通过this.xx = xyz 的方式对 data 中定义的某个 key 赋值的时候,vue 便能够监测到这个行为,...
  • <div class="test-form" :key="index" v-for="(item, index) in arrs">  <input v-model="item.test_id" @input="changeFunc('test_id', item.test_id ,index)" /> <!-- 如果不加上@input中的方法...
  • 需求:主函数中需要监控其他类的属性,如果属性发生变化则需要在主函数所在类执行某个方法。 code: public class B { public event EventHandler PropertyChanged; private string _a; publuc string A { ...
  • 如何监听一个对象的变化 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta ...
  • 前言 本文分为入门和进阶两部分,建议有经验的读者直接阅读进阶部分。...首先我们需要知道如何通过Object.defineProperty这个API来监听一个对象的变化, 注意注释里的内容! const obj = {}; let val = obj...
  • 注意:这三种声明的方式类似,都只是定义的指针,并没有拷贝 void Func(int* b) ; void Func(int b[]) ; void Func(int b[3]) ; 未使用引用   #include &lt;iostream&... b ...
  • 一、获取用户输入的文字内容 设置组件Name为“RTB_OpenText” 二、使用RTB_OpenText.Text无法获取?需要使用组件名.Lines string [] GetOpenText = ...注意:Lines为一行一行的读取数据,每一行占一个数组容器。 ...
  • 1、什么情形下用回调函数/事件触发? 做过支付宝支付,微信支付等第三方支付功能的小伙伴都知道notify_url和return_url,其中notify_url 是第三方支付公司为用户开发的回调函数类,你可以在这个类中校验支付状态,...
  • 这篇文章,将《Effective C# Second Edition》一书中适用于Unity游戏引擎里使用C#的经验之谈进行了提炼,总结成为22条准则,供各位快速地掌握这本书的知识梗概,在Unity中写出更高质量的C#代码。 《Effective C# ...
  • 在发送消息的时候,我们可以通过代码看到RabbitMq默认的是使用SimpleMessageConverter来进行消息转换的   SimpleMessageConverter的源码如下图   源码分析总结: 1.MessageConverter可以把java对象转换成...
  • 前日一群友问:双击会连带触发单击事件,想让二者独立开来,分别做不同的监听,怎么破?我随口道:对单击事件做个封装呗?问怎么封装,思考了几分钟,还真难倒了我,我是个倔强的人,再想了一会,也没找到合适的解法...
  • 每隔10年左右,编程人员就需要花费大量的时间和精力去学习新的编程...幸运的是,使用C#和.NET进行的大多数工程的分析和设计与在C++和Windows中没有本质的变化。在本篇文章中,我将介绍如何实现由C++到C#的飞跃。 已经有
  • Node.js通过其强大的事件驱动...然而,这些变化将是非常值得的,因为你通过使用Node.js获得了在速度上的提高。 本章还包括用来把工作添加到Node.js事件队列的不同方法。你可以通过使用事件监听器或计时器添加工作...
  • 使用FileSystemWatcher类,需要添加using System.IO引用。 FileSystemWatcher控件主要功能: 监控指定文件或目录的文件的创建、删除、改动、重命名等活动。可以动态地定义需要监控的文件类型及文件属性改动的...
  • OPC UA简介 OPC是应用于工业通信的,在windows环境的下一种通讯技术,原有的通信技术难以满足日益复杂的环境,在可扩展性,安全性,跨平台性方面的不足日益明显,所以OPC基金会在几年前提出了面向未来的架构设计...
1 2 3 4 5 ... 20
收藏数 3,337
精华内容 1,334