写点什么

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

用户头像
Brave
关注
发布于: 2021 年 06 月 10 日
【Vue2.x 源码学习】第十篇 - 数组数据变化的观测情况

一,前言


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


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

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


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


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


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.jsclass 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

运行会导致死循环



// 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.jsclass 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); } }}
复制代码

再执行,问题解决:




三,结尾


又成功水了一篇,还剩 11 篇


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


  • 实现了数组数据变化被劫持后,已重写原型方法的具体逻辑;

  • 数组各种数据变化时的观测情况分析;


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


下一篇,数据渲染的流程

用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【Vue2.x 源码学习】第十篇 - 数组数据变化的观测情况