• 源码学习VUE之Observe


    在文章 源码学习VUE之响应式原理我们大概描述了响应式的实现流程,主要写了observe,dep和wather的简易实现,以及推导思路。但相应代码逻辑并不完善,今天我们再来填之前的一些坑。

    Observe

    之前实现的observe函数只能处理一个对象的单个属性,但我们更多的数据是保存在对象中,为了抽象话,我们也封装一个对象Observe,只要传进一个参数,就可以把这个对象进行监听。

    对现有所有属性进行监听

    var obj = {
        a: 1,
        b: 2
    }

    比如一个对象有两个属性 a,b。我们可以尝试写出下面的实现类

    class Observe{
        constructor(value){
            this.value = value //要监听的值。
            this.walk();
        }
        
        walk(){ //通过walk函数,依次处理
            const keys = Object.keys(obj);
            let self = this;
            for (let i = 0; i < keys.length; i++) {
              self.defineReactive(obj, keys[i])
            }
        }
        
        defineReactive (data, key, val) {
            var dep = new Dep();
            Object.defineProperty(obj, a, {
                enumerable: true,
                configurable: true,
                get: function(){
                   if(Dep.target){
                        dep.addSub(Dep.target); // Dep.target是Watcher的实例
                    }
                },
                set: function(newVal){
                    if(val === newVal) return
                    val = newVal;
                    dep.notify();
                }
            })
        }
    }

    当然,为了防止重复监听,我们可以给原object设置一个标识符以作辨别。

    class Obsever(){
        construct(){
            this.value = value //要监听的值。
            Object.defineProperty(value, "__ob__", {
                value: this,
                enumerable: false,
                writable: true,
                configurable: true
            })
            this.walk();
        }
    }

    监听数组

    虽然数组也是一个对象,但是我们队数组的操作却不会触发set,get方法。因此必须对数组特殊处理。
    首先需要对操作数组的方法进行改写,如push,pop,shift

    //首先拿到Array的原生原型链
    const arrayProto = Arrary.prototype;
    //为了保证修改不会影响原生方法,我们创建一个新对象
    const arrayMethods = Object.create(arrayProto);
    //要改写的方法
    const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
    methodsToPatch.forEach(function (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
        }
        //Observe插入的值
        if (inserted) ob.observeArray(inserted)
        // notify change
        ob.dep.notify()
        return result
      })
    })

    其实逻辑很简单。对于可以改变array的方法我们都改写一下。只要调用了这些方法,除了返回正确的值,我们都通知观察对象,数据改变了,触发观察者update操作。同时,数组里面可能是个对象,我们不改变数组本身,但是改变数组里面的某个值,这也算是一种改变,因此,除了监听数组本身的改变,也要对数组每个值进行observe。
    这涉及到两点,一是observe Array的时候,就要对每个值进行Observe。另外,插入数组的每个值也要observe.第二点就是上面代码中特别关注push,unshift,splice这三个可以插值方法的原因。

    class Obsever(){
        construct(){
            this.value = value //要监听的值。
            Object.defineProperty(value, "__ob__", {
                value: this,
                enumerable: false,
                writable: true,
                configurable: true
            })
            if(Array.isArray(value)){
                this.observeArray();
            }else{
                 this.walk();
            }
        }
        observeArray(items){
           for (let i = 0, l = items.length; i < l; i++) {
              observe(items[i])
            } 
        }
    },
    
    function observe (value) {
      let ob
      if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
      // 如果已经observe的对象就不再进行重复的observe操作
        ob = value.__ob__
      } else {
            ob = new Observer(value)
      }
      return ob
    }

    优化

    实际开发中我们经常会遇到一个很大的数据。如渲染tables时,table的数据很可能很大(一个多多维数组)。如果都进行observe无意会是很大的开销。关键是我们只是需要拿这些数据来渲染,并不关心数据内部的变化。因此可能就存在这种需求,可以不对array或object深层遍历observe。我们可以使用Object.freeze()将这个数据冻结起来。
    因此对于冻结的数据我们就不再进行observe。上面的代码可以这么优化

    function observe (value) {
      let ob
      if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
      // 如果已经observe的对象就不再进行重复的observe操作
        ob = value.__ob__
      } else if(Object.isExtensible(value)){// 如果数据被冻结,或者不可扩展,则不进行observe操作
            ob = new Observer(value)
      }
      return ob
    }
    
     defineReactive (data, key, val) {
            var dep = new Dep();
            var property = Object.getOwnPropertyDescriptor(obj, key)
            // 如果数据被冻结,或者不可扩展,则改写set,get方法
              if (property && property.configurable === false) {
                return
              }
              //传进来的对象可能之前已经被定义了set,get方法,因此我们不能直接拿value
            var getter = property && property.get
            var setter = property && property.set
            Object.defineProperty(obj, a, {
                enumerable: true,
                configurable: true,
                get: function(){
                   var value = getter ? getter.call(obj) : val;
                   if(Dep.target){
                        dep.addSub(Dep.target); // Dep.target是Watcher的实例
                    }
                    return value
                },
                set: function(newVal){
                    if(val === newVal) return
                    val = newVal;
                    dep.notify();
                }
            })
        }
    
  • 相关阅读:
    Redis 安全
    Redis 数据备份与恢复
    Redis 服务器
    Redis 连接
    Redis 脚本
    Linux中使用netstat命令的基本操作,排查端口号的占用情况
    ElasticSearch 常用查询语句
    GO代码风格指南 Uber Go (转载)
    coding 注意事项(总结中)
    Byte字节
  • 原文地址:https://www.cnblogs.com/twodog/p/12135980.html
Copyright © 2020-2023  润新知