第五篇 - 数组的劫持

news2024/9/22 6:00:43

一,前言

上篇,主要介绍了 Vue 数据初始化流程中,对象属性的深层劫持是如何实现的

核心思路就是递归,主要流程如下;

1.通过 data = isFunction(data) ? data.call(vm) : data;处理后的 data 一定是对象类型
2.通过 data = observe(data)处理后的 data 就实现了数据的响应式(目前只有劫持)
3.observe 方法最终返回一个 Observer 类
4.Observer 类初始化时,通过 walk 遍历属性
5.对每一个属性进行 defineReactive(Object.defineProperty)就实现对象属性的单层数据劫持
6.在 defineReactive 中,如果属性值为对象类型就继续调用 observe 对当前的对象属性进行观测(即递归步骤 3~5),这样就实现了对象属性的深层数据劫持

本篇,继续介绍 Vue 数据初始化流程中,对于数组类型的劫持


二,对象劫持回顾

1,Demo

data 数据中对象属性的深层观测,即对象属性为对象(包含多层)的情况

let vm = new Vue({
  el: '#app',
  data() {
    return { message: 'Hello Vue', obj: { key: "val" }, a: { a: { a: {} } } }
});

image.png

当 data 中的属性为数组时,Vue 是如何进行处理的


三,数组类型的处理

1,当前逻辑分析

按照当前版本的处理逻辑,所有对象类型会对被进行深层观测,数组也不例外

let vm = new Vue({
  el: '#app',
  data() {
    return { message: 'Hello Vue', obj: { key: "val" }, arr:[1,2,3]}
  }
});

image.png

可以看到,数组中的每一项,都被添加了 get、set 方法,也就相当于实现了对数组的深层观测

备注:Object.defineProperty支持数组数据类型的劫持

2,Vue 对性能的权衡

在 Vue2.x 中,不支持通过修改数组索引和长度的数据劫持;

那么,为什么原本可以实现对数组索引的观测,Vue 却选择了不支持呢?

主要是考虑了性能问题,比如,数组中的数据量非常大时:

let vm = new Vue({
  el: '#app',
  data() {
    return { arr:new Array(9999) }
  }
});

这时,数组中 9999 条数据,将全部被添加 get、set 方法

而这一套操作就比较费劲了:为了实现数组索引劫持,需要对数组中每一项进行处理

还有就是,虽然数组能够通过 defineProperty 实现对索引更新劫持

但在实际开发场景真的需要吗?似乎很少会使用 arr[888] = x 这种操作

所以,权衡性能和需求,Vue 源码中没有采用 defineProperty 对数组进行处理

当然,这也就导致了在 Vue 中无法通过直接修改索引、length 触发视图的更新

3,数组的劫持思路

核心目标是要实现数组的响应式:

Vue 认为这 7 个方法能够改变原数组:push、pop、splice、shift、unshift、reverse、sort

所以,只要对这 7 个方法进行处理,就能劫持到数组的数据变化,实现数组数据的响应式

备注:这种实现思路,也直接导致了 vue2 修改数组的索引和长度不能触发视图更新

梳理对象属性深层劫持的实现:

  1. 数据观测入口:src/observe/index.js#observe方法,
  2. 如果数据为对象类型就 new Observer
  3. Observer 初始化时,会遍历对象属性,逐一递归 Object.defineProperty

数组也是对象,所以,要把数组的处理逻辑单独拆出来。即对 7 个变异方法进行重写

// src/utils

/**
 * 判断是否是数组
 * @param {*} val 
 * @returns 
 */
export function isArray(val) {
  return Array.isArray(val)
}

// src/observe/index.js
import { arrayMethods } from "./array";

class Observer {
  constructor(value) {
    if(isArray(value)){
      // 对数组类型进行单独处理:重写 7 个变异方法
    }else{
      this.walk(value);
    }
  }
}

4,数组方法的拦截思路

  • 重写方法需要在原生方法基础上,实现对数据变化的劫持操作
  • 仅对响应式数据中的数组进行方法重写,不能影响非响应式数组

所以,对响应式数据中数组这 7 个方法进行拦截,即优先使用重写方法,其他方法还走原生逻辑

数组方法的查找,先查找自己身上的方法(即重写方法),找不到再去链上查(原生方法)

5,数组方法重写的实现

// src/Observer/array.js

// 拿到数组的原型方法
let oldArrayPrototype = Array.prototype;
// 原型继承,将原型链向后移动 arrayMethods.__proto__ == oldArrayPrototype
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)
  }
});

添加 new Observer 时,对数组方法重写的逻辑:

// src/observe/index.js
import { arrayMethods } from "./array";

class Observer {
  constructor(value) {
    // 分别处理 value 为数组和对象两种情况
    if(isArray(value)){
      value.__proto__ = arrayMethods; // 更改数组的原型方法
    }else{
      this.walk(value);
    }
  }
}

测试数组方法的重写:

image.png

数组的链:

  • array.proto:包含 7 个重写方法
  • array.proto.proto:原始方法

6,数组方法拦截的实现

// src/state.js#initData

function initData(vm) {
    let data = vm.$options.data;
    data = isFunction(data) ? data.call(vm) : data;
    observe(data);	// 在observe方法中new Observer执行后,数组的原型方法已完成重写

    // 测试数组方法的拦截效果
    data.arr.push(666); 
    data.arr.pop()
}

image.png

  • arrayMethods.push:会在数组自身找到重写的push方法,不会继续到链上查找,实现拦截
  • arrayMethods.pop:数组自身没找到重写方法,继续到链上找到原生pop方法

四,结尾

本篇主要介绍了 Vue 数据初始化流程中,数组类型的数据劫持,核心有以下几点:

出于对性能的考虑,Vue 没有对数组类型的数据使用 Object.defineProperty 进行递归劫持,而是通过对能够导致原数组变化的 7 个方法进行拦截和重写实现了数据劫持

下一篇,数据代理的实现

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

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

相关文章

如何定位Bug——Qt

1. 前言 在写程序的过程中,不可避免出现各种Bug,如何快速的定位到Bug的位置,是程序员必备的技能之一。 2. 几种方法 2.1. 逻辑分析 根据程序所出的问题,分析问题可能所在的几个位置,通过逻辑分析找出Bug&…

从 Nginx Ingress 窥探云原生网关选型

作者: 魁予 现今有越来越多的企业开始采纳云原生理念进行应用架构转型。而 K8s 和微服务是云原生的两大支柱,随着云原生浪潮而被广泛应用。 对多数应用而言,提供对外服务的使命并不会改变,相比于原来的单体应用,微服…

VUE3中,使用.env.development和.env.production

在实际项目开发过程中,可能开发环境和生产(测试)环境不一样,经常需要修改配置常量,才能满足对应的环境,才能使软件运行起来。在vue3中可以使用2个文件进行区分。 .env.development:开发环境 .…

Mysql存储引擎 初级(自用笔记)

内容来自于(https://www.bilibili.com/video/BV1Kr4y1i7ru?p58&vd_source3cf72bb393b8cc11b96c6d4bfbcbd890) 1.存储引擎 1.1Mysql体系结构 1.2什么是存储引擎呢? 存储引擎就是存储数据,建立索引,更新,查询数据等技术的实现方式. 存储引擎是基于表的,而不是基于数据库的…

魔兽世界开服教程wow服务器框架Trinirycore构建

首先明杰先给各位普及一下TrinityCore是什么TrinityCore 是c实现MMORPG框架来自MaNGOS,大型网络对象服务,随着时间的推移,该项目代码广泛的优化,改善和清理代码。需要准备的架设工具:端(版本)、…

皮尔逊相关性分析一文详解+python实例代码

目录 前言 一、数值类型 二、皮尔逊系数使用场景 二、皮尔逊相关系数(Pearson correlation) 1.定义 2.线性关系判定 3.正态检验 1.KS检验 4.计算代码 点关注,防走丢,如有纰漏之处,请留言指教,非常感谢 参阅 前言 相关性…

MySQL中这10个小玩意,让人眼前一亮

我最近几年用MYSQL数据库挺多的,发现了一些非常有用的小玩意,今天拿出来分享到大家,希望对你会有所帮助。 1.group_concat 在我们平常的工作中,使用group by进行分组的场景,是非常多的。 比如想统计出用户表中&…

给Kylin iso中添加自定义rpm包

前言 在日常产品交付中会有如下需求: 客户自己安装centos / ubuntu / kylin iso,然后我们把rpm包给到客户 我们直接将rpm包封装到iso,给客户交付整体iso,这样不管是对于客户还是对于公司都是最省心的,会避免很多因为系…

疯狂2023:科技迎来爆发之年,关注云计算这些方向

临近年末,熬过艰难苦恨繁霜鬓的2022,打工人已默默扛起来年的OKR 。根据市场研究机构Garner预测,2023将迎来科技爆发之年,最值得关注的云计算趋势是AI和ML、多云和混合云解决方案、物联网、云安全等。 及时了解不断变化的技术格局…

汇聚数据库创新力量 GBASE携手openGauss助企业数字化转型

12月29日,openGauss Summit 2022于线上举行。本次大会是由openGauss开源数据库社区联合行业组织、伙伴及客户共同举办的一场年度数据库产业界交流与分享峰会。天津南大通用数据技术股份有限公司(简称:GBASE)首次以openGauss社区理…

PDF转换成JPG图片怎么转换?这两招轻松搞定

PDF转换成JPG图片怎么转换?PDF文件是我们常用的一类文件,在现在的办公环境中,PDF真的用途很广,不仅是日常发送文件,还有学习工作中都会用到,不过有时候我们也需要将PDF文件转换成其他的格式,比如…

JUC并发编程学习笔记(五)读写锁和阻塞队列

8 读写锁 8.1 悲观锁和乐观锁介绍 回顾悲观锁和乐观锁的概念 悲观锁:单独每个人完成事情的时候,执行上锁解锁。解决并发中的问题,不支持并发操作,只能一个一个操作,效率低 顾名思义,就是比较悲观的锁&am…

配置文件中命名空间的作用

命名空间 实际上就是一个由程序设计者命名的内存区域,程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来。 命名空间的作用 是建立一些互相分隔的作用域,把一些…

11_3、Java集合之迭代器Iterator接口

一、引入Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。 GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元 素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器…

华脉智联铁路巡检及指挥调度系统

技术方案 整套系统基于云通讯平台和智能终端的结合,实现指挥中心和现场人员的实时互动,同时保证现场取证材料的实时上报以及指挥中心实时将决策及命令下发到现场人员。提高巡检效率及巡检准确性。 方案拓扑如下: 在指挥中心通信机房部署指挥…

k8s集群监控cadvisor+prometheus+grafana部署

目录 1.新建命名空间monitor 2.部署 2.1部署cadvisor 2.2部署node_exporter 2.3部署prometheus资源 2.4部署rbac权限 2.5.部署 metrics 2.6部署grafana 3.测试效果 参考文章: k8s集群部署cadvisornode-exporterprometheusgrafana监控系统 - cyh00001 - 博客园 …

JDBC --- 数据库连接池 C3P0

目录 1、基本定义 2、使用C3P0(数据库连接池)的必要性 2.1.JDBC传统模式开发存在的主要问题 2.1.1时间和内存资源消耗巨大 2.1.2 有内存泄漏的风险 3、数据库连接池的详细说明 4、使用连接池的明显优势 4.1.资源的高效利用 4.2.更快的系统反应速…

linux中awk命令和argxs命令的详解使用

一 awk命令1.1 命令的作用awk为行命令处理器,对每一行的文本数据,进行格式化文本信息。目的:在处理庞大文件时不会出现内存溢出或是处理缓慢的问题。1.2 案例应用获取某一行文本数据,提取不同字段的值。1.显示整行数据[rootlocalh…

怎么把照片合成一张?看完这篇文章你就知道了

大家平时有没有遇到过这样的情况?好朋友们组团一起出去玩,但是你因为个人原因无法跟大家一起去。这就可能导致姐妹中的合照少了你一个人,那么要怎样在图片上添加你去了的效果呢?其实很简单,只要编辑图片进行合成就可以…

指针进阶(1)

( )> [ ] > -> > -- > . > * (优先级比较) tips 1. (全是笔者个人自己总结,仅供参考,随意取用) 2. 3. 地址唯一标识一块内存空间 4. printf%s打印字符串的话只要给一个起始地址…