最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【Vue2.x 源码学习】第十篇 - 数组数据变化的观测情况

    正文概述 掘金(BraveWang)   2021-06-13   426

    这是我参与更文挑战的第10天,活动详情查看: 更文挑战

    一,前言

    上篇,主要介绍了对象数据变化的观测情况,涉及以下几个点:

    实现了对象老属性值变更为对象、数组时的深层观测处理;

    结合实现原理,说明了对象新增属性不能被观测的原因,及如何实现数据观测;

    本篇,数组数据变化的观测情况(数组中,新增对象、数组、普通值的情况;)


    二,数组中,新增对象、数组、普通值的观测问题

    1,问题分析

    向数组 arr 中新增对象、数组、普通值,会触发数据更新吗?

    let vm = new Vue({
      el: '#app',
      data() {
        return { arr: [{ name: "Brave" }, 100] }
      }
    });
    
    vm.arr.push({a:100});
    vm.arr[2].a = 200;
    

    截止至当前版本,针对数组类型的处理:

    • 重写了数组链上的方法,能够对引起原数组变化的 7 个原型方法进行劫持;

    • 对数组中的每一项递归调用 observe 进行处理,使数组类型实现递归观测;

      由于 observe 仅处理对象类型,所以数组中的普通值不会被观测;

    虽然已经实现了数组的数据劫持,但尚未实现数据劫持后的具体逻辑:

    // src/Observer/array.js
    
    let oldArrayPrototype = Array.prototype;
    export let arrayMethods = Object.create(oldArrayPrototype);
    
    let methods = [
      'push',
      'pop',
      'shift',
      'unshift',
      'reverse',
      'sort',
      'splice'
    ]
    
    methods.forEach(method => {
      arrayMethods[method] = function () {
        console.log('数组的方法进行重写操作 method = ' + method)
        // 劫持到数组变化后,尚未实现处理逻辑
      }
    });
    

    所以,向数组中添加内容,是能够触发数据劫持的,但还没有实现劫持后的具体逻辑

    在 Vue2.x 中,向数组中新增对象,及修改新增对象的属性,都是可以触发更新的;

    2,思路分析

    重写 push 方法逻辑:

    由于 7 个方法的入参数量不一致,例如 push 可以传入多个参数

    3,代码实现

    当 push 的参数为对象类型时,需要再次进行观测

    // src/observe/array.js
    
    methods.forEach(method => {
      // 当前的外部调用:arr.push
      arrayMethods[method] = function (...args) {
        console.log('数组的方法进行重写操作 method = ' + method)
        // AOP:before 原生方法扩展... 
        // 调用数组原生方法逻辑(绑定到当前调用上下文)
        oldArrayPrototype[method].call(this, ...args)
        // AOP::after 原生方法扩展...
    
        // 数组新增的属性如果是属性,要继续观测
        // 哪些方法有增加数组的功能: splice push unshift
        let inserted = [];
        switch (method) {
          // arr.splice(0,0,100) 如果splice方法用于增加,一定有第三个参数,从第三个开始都是添加的内容
          case 'splice':  // 修改 删除 添加
            inserted = args.slice(2); // splice方法从第三个参数起是新增数据
          case 'push':    // 向前增加
          case 'unshift': // 向后增加
            inserted = args // push、unshift的参数就是新增
            break;
        }
        // 遍历inserted数组,看一下它是否需要进行劫持
      }
    });
    

    当 push 的参数为对象类型时,需继续对其进行观测;

    问题 1

    数组深层劫持的 observeArray 方法,在 Observer 类中

    由于没有导出,在 src/observe/array.js 的 methods.forEach 中是访问不到的

    Observer 类中也拿不到 vm,

    所以为当前 this 添加自定义属性进行关联:value.ob = this;

    value:为数组或对象添加自定义属性__ob__ = this,

    this:为当前 Observer 类的实例,实例上就有 observeArray 方法;

    如此,便可在src/observe/array.js 的 methods.forEach 中,调用到 observeArray 方法实现数组的深层劫持;

    // src/observe/index.js
    class Observer {
      
      constructor(value) {
        // value:为数组或对象添加自定义属性__ob__ = this,
        // this:为当前 Observer 类的实例,实例上就有 observeArray 方法;
        value.__ob__ = this;
    
        if (isArray(value)) {
          value.__proto__ = arrayMethods;
          this.observeArray(value);
        } else {
          this.walk(value);
        }
      }
    }
    

    添加了__ob__后的数组,调用了 push 方法,所以能够通过__ob__属性获取到 ob

    // src/observe/array.js
    
    methods.forEach(method => {
      arrayMethods[method] = function (...args) {
        oldArrayPrototype[method].call(this, ...args)
        let inserted = null;
        let ob = this.__ob__;	// 通过 __ob__ 属性获取到 ob
        switch (method) {
          case 'splice': 
            inserted = args.slice(2);
          case 'push':  
          case 'unshift':
            inserted = args 
            break;
        }
        
        // observeArray:内部遍历inserted数组,调用observe方法,是对象就new Observer,继续深层观测
        if(inserted)ob.observeArray(inserted);// inserted 有值就是数组
      }
    });
    

    所以,当向数组 push 对象或数组时,会继续走 observeArray 方法,使对象或数组成为响应式

    问题 2

    运行会导致死循环

    【Vue2.x 源码学习】第十篇 - 数组数据变化的观测情况

    // src/observe/index.js
    
    class Observer {
    
      constructor(value) {
        value.__ob__ = this;
    
        if (isArray(value)) {
          value.__proto__ = arrayMethods;
          this.observeArray(value);
        } else {
          this.walk(value);
        }
      }
    
      walk(data) {
        Object.keys(data).forEach(key => {
          defineReactive(data, key, data[key]);
        });
      }
    }
    

    在 Observer 类中,由于 value.ob = this; 这段代码

    value 如果是对象,会走到 this.walk(value); 方法,继续循环对象的属性,

    这时,属性__ob__会被循环出来,而__ob__又是一个对象,且在这个对象上还有__ob__

    所以,在 walk 循环中对属性__ob__做 defineProperty 后,它的值还是一个对象,就无限递归造成了死循环

    value 是对象就会进入 walk 方法,循环 value 对象中的所有属性,

    其中__ob__属性将被循环出来,而 ob 就是当前实例,实际也是一个对象,会被继续观测,造成死循环

    所以,这段代码不能这么写,即__ob__不能被遍历,否则遍历出来后就会被defineProperty,造成死循环;

    冻结:属性冻结后只是不能被修改了,但还是能被遍历出来的

    需要使用 defineProperty 定义__ob__ 属性,并将 ob 属性配置为不可被枚举

    // src/observe/index.js
    class Observer {
    
      constructor(value) {
        // value.__ob__ = this;	// 可被遍历枚举,会造成死循环
        // 定义__ob__ 属性为不可被枚举,防止对象在进入walk都继续defineProperty,造成死循环
        Object.defineProperty(value, '__ob__', {
          value:this,
          enumerable:false  // 不可被枚举
        });
        
        if (isArray(value)) {
          value.__proto__ = arrayMethods;
          this.observeArray(value);
        } else {
          this.walk(value); 
        }
      }
    }
    

    再执行,问题解决:

    【Vue2.x 源码学习】第十篇 - 数组数据变化的观测情况


    三,结尾

    本篇,主要介绍了数组数据变化的观测情况:

    • 实现了数组数据变化被劫持后,已重写原型方法的具体逻辑;
    • 数组各种数据变化时的观测情况分析;

    至此,数据劫持就全部完成了

    下一篇,数据渲染的流程


    起源地下载网 » 【Vue2.x 源码学习】第十篇 - 数组数据变化的观测情况

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元