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

news2024/11/19 22:39:02

一,前言

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

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

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

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


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

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

运行会导致死循环

image.png

// 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); 
    }
  }
}

再执行,问题解决:

image.png


三,结尾

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

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

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

下一篇,数据渲染的流程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/148876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

综合案例二 旅游网【1.项目导入技术选型注册表单校验登录退出表单】注册表空指针异常和其他一些错误

目录 前提:项目导入 在maven中点击travel项目(这里我是将项目添加到hello_maven里 ) 1.启动项目 方式一:maven命令启动 方式二:配置maven快捷启动 2.技术选型 1 Web层 2 Service层 3 Dao层 3.创建数据库…

56/14 shell脚本 后台启动 程序1 + “tail -f log“, ctrl +c 导致程序1中断

前言 接上一篇文章, node 程序后台执行加上 tail 命令, 中断 tail 命令, 同时也中断了 node 程序 我们来详细 参照对比一下 这个问题的各种情况 主要的脚本如下类似, 第一条命令 后台启动 程序1, 然后 第二条命令 tail -f 查看日志 然后 ctrlc 中断 "tail -f" …

实现自己的打印函数

文章目录前言前置知识代码说明实验操作单字符打印字符串打印整形字符串打印前言 本博客记录《操作系统真象还原》第六章实验的操作~ 实验环境:ubuntu18.04VMware , Bochs下载安装 实验内容: 实现 put_char 单字符打印输出函数。实现 put_…

TEE 背景知识

TEE 背景 计算机世界的安全,是保护计算机系统和网络免受攻击者的攻击,这些攻击可能导致未经授权的信息泄露、窃取或损坏硬件、软件或数据,以及它们所提供的服务的中断或误导。更多参考 Computer_security 1 安全是什么 谈安全,…

28.0:Combit LIST LABEL Reporting for Delphi Crack

作为 Delphi 开发人员的优势: 将 VCL 组件直接集成到 Embarcadero IDE 中 免版税报表设计器 交互式预览等 使用综合报表对象 Delphi 和所有其他相关语言和开发环境的报告工具。 将列表和标签集成到 Embarcadero RAD Studio List & Label 的 VCL 组件编译并直接…

HTML与CSS基础(八)—— CSS布局(定位、装饰、选择器拓展)

目标能够说出 定位 的常见应用场景 能够说出 不同定位方式 的特点 能够使用 子绝父相 完成元素水平垂直案例 能够写出三种常见的 光标类型(cursor) 能够使用 圆角边框 属性完成 正圆 和 胶囊按钮 效果 能够说出 display 和 visibility 让 元素本身隐藏 的…

【java查漏补缺】File类的使用

File类可以用来处理文件数据,比如使用list()方法可以获得某个文件夹下面的所有文件名。 File类的list()方法有一个含有一个参数的重载,在调用该方法的时候需要传入一个FilenameFilter对象,这样便可以进行文件名过滤。 我们先来看一下list()…

GJB 5000B二级-PP项目策划

一、主要变化情况 合并10项(绿色)、修订3项(蓝色)、删除1项(红色) 新增的主要内容 基于A版标准“项目策划”、“集成项目管理”、“定量项目管理”三个过程域中有关项目策划实践的内容来识别,没有新增实践,只增加了以下两个要求: 1、增加软件项目和设备/系统生存周…

Java线程之中断方法

interrupt()方法介绍interrupt() 给目标线程发送一个中断信号,同时将目标线程的中断标志设置为true。至于目标线程是否做出响应,需要看目标线程是否有对应的中断业务逻辑。场景1:假设,你是一个工厂的老板(main线程&…

C++类和对象(下)

目录 初始化列表 explicit关键字 Static成员 友元 友元函数 友元类 匿名对象 内部类 初始化列表 初始化列表是以冒号开始,以逗号分割的成员列表,每一个成员变量后面跟一个放在括号中的初始值或表达式。(代码演示以日期类为例&#xff…

【实现QQ登录界面 Objective-C语言】

一、实现QQ登录界面 1.实现这样的QQ登录界面 1.实现这样的QQ登录界面 2.首先,分析一下,这个界面里,都有哪些控件 是不是两个Label,两个TextField文本框,1个Button吧 3.先拖1个Label上来 再拖1个TextField文本框上来,在这个Label右边, 然后选中这两个控件, 按住o…

Altium Designer 超详细学习教程——印制电路板基础知识

在介绍Altium Designer软件使用办法之前先介绍下印制电路板的基础知识,不管是画图还是绘制PCB最终目的都是为了制作电路板,因此了解PCB的基础知识很有必要。 1.1印制电路板概述 1.1.1印制电路板结构 在进行PCB设计时,需要先对印制电路板的…

【Linux】对于make/Makefile的使用

本文目录 背景简介 细说关于make命令和makefile文件: 使用方法 为什么执行的指令是make和make clean呢? gcc如何判断文件是否需要重新执行? 背景简介 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中&…

064-Tcp/IP通讯使用ServerSocket及Server实现多人在线聊天项目实训

【上一讲】063-Tcp/IP通讯使用ServerSocket及Server实现多人在线聊天_CSDN专家-赖老师(软件之家)的博客-CSDN博客 本文主要讲解使用ServerSocket,Socket类实现多人同时在线聊天的网络通讯程序,主要功能及技术如下: 1.使用ServerSocket,Socket类实现网络通讯功能; 2.使用…

【go语言之websocket】

go语言之websocket写在前面服务端msggetConnUpgradeNewWsConnectionNextWriterwriteread客户端抓包表现总结写在前面 之前的文章都是介绍的是http的使用,这里主要介绍的是websocket,主要是解决长连接场景下的使用。这里概念不多说,网上很多,…

Ajax篇-前后端交互, 接口文档, ajax, axios, fetch,Postman工具

前后端交互接口文档请求方法区分方式GETPOST从语义上区分偏向于查询(获取数据....)偏向于提交数据(注册,修改,删除...)安全性显示的携带参数,参数是直接拼接在请求地址之后,安全性较差,隐私性差隐式的携带参数,不会在请求地址上显示,安全性好,以JSON格式…

网络虚拟化基本架构

文章目录架构概述架构图核心组件OpenFlow SwitchPipelineFlow Table EntryInstructionOpenFlow Switch Protocol测试验证Pipeline流表项间流程流表间流程Flow Table EntryInstructionsSwitch ProtocolFaucet架构概述 我们知道网络虚拟化的主要目标就是让报文可以在虚拟机之间进…

Windows Anaconda YOLOv3环境部署--2023年1月8日

时效性: 2023年1月8日 目录摘要1 使用 Anaconda 创建虚拟环境2 安装官方要求的依赖库3 验证安装 | 执行 detect.py 示例代码Key already registered with the same priority摘要 网好的可以直接参考官方文档安装,遇到安装报错和网络问题可以参考本文 本地…

网络安全等级保护定级指南 范围

声明 本文是学习github5.com 网站的报告而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 网络安全等级保护 为了配合《中华人民共和国网络安全法》的实施,适应云计算、移动互联、物联网、工业控制和大数据等新技术、新应用情况下网络安全等…

Spring Cloud OpenFeign入门示例

简介 Feign Feign是一个声明性web服务客户端。让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。让http远程调用就像接口调用一样简单。(远程http调用的工具有很多,HttpClient、OKHttp、Spring Boot中的RestTempl…