zoukankan      html  css  js  c++  java
  • 源码学习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();
                }
            })
        }
    
  • 相关阅读:
    用 ArcMap 发布 ArcGIS Server FeatureServer Feature Access 服务 PostgreSQL 版本
    ArcMap 发布 ArcGIS Server OGC(WMSServer,MapServer)服务
    ArcScene 创建三维模型数据
    ArcMap 导入自定义样式Symbols
    ArcMap 导入 CGCS2000 线段数据
    ArcMap 导入 CGCS2000 点坐标数据
    ArcGis Server manager 忘记用户名和密码
    The view or its master was not found or no view engine supports the searched locations
    python小记(3)操作文件
    pytest(2) pytest与unittest的区别
  • 原文地址:https://www.cnblogs.com/twodog/p/12135980.html
Copyright © 2011-2022 走看看