Vue数据代理的实现流程逐步讲解

目录
  • 一,前言
  • 二,对象劫持回顾
    • 1,Demo
  • 三,数组类型的处理
    • 1,当前逻辑分析
    • 2,Vue 对性能的权衡
    • 3,数组的劫持思路
    • 4,数组方法的拦截思路
    • 5,数组方法重写的实现
    • 6,数组方法拦截的实现
  • 四,结尾

一,前言

上篇,主要介绍了 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: {} } } }
});

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

三,数组类型的处理

1,当前逻辑分析

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

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

可以看到,数组中的每一项,都被添加了 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 修改数组的索引和长度不能触发视图更新

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

  • 数据观测入口:src/observe/index.js#observe方法
  • 如果数据为对象类型就 new Observer
  • 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);
    }
  }
}

测试数组方法的重写:

数组的链:

  • 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()
}

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

四,结尾

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

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

下一篇,数据代理的实现

到此这篇关于Vue数据代理的实现流程逐步讲解的文章就介绍到这了,更多相关Vue数据代理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue如何进行数据代理

    目录 了解如何代理 准备工作 查看VM 和谁做了数据代理? get和set 验证两条线 get 问题引出1 set 解决方式1 问题衍生2 验证set的过程 分析过程 图文解析 第一部分 第二部分 第三部分 总结 展开_data 思考 vue的承诺 在了解了关于js当中的Object.defineProperty()这个方法后,我们继续对vue当中的数据代理做一个基于现在的解析 建议观看之前先了解下js当中的Obejct.defineProperty() 链接地址 了解如何代理 准备工作 准备一

  • vue之数据代理详解

    目录 一.下面我讲的是前端人员在vue-cli中就可以完成的一种解决方式--数据代理 二.上面的数据代理还是有以下缺陷的 总结 解决跨域的方式有多种,例如jsonp.cors但这两种都需要后台人员的帮助, 一.下面我讲的是前端人员在vue-cli中就可以完成的一种解决方式--数据代理 (1)首先需要在vue-cli官方文档的配置项下载一个插件 (2)将上图红圈中的部分粘贴到vue脚手架的babel.config.js中 (3)上图中红圈部分http://localhost:5000为本地服务器地

  • vue Proxy数据代理进行校验部分源码实例解析

    目录 initProxy 触发代理 数据过滤 总结 initProxy 数据拦截的思想除了为构建响应式系统准备,它也可以为数据进行筛选过滤,我们接着往下看初始化的代码,在合并选项后,vue接下来会为vm实例设置一层代理,这层代理可以为vue在模板渲染时进行一层数据筛选 Vue.prototype._init = function(options) { // 选项合并 ... { // 对vm实例进行一层代理 initProxy(vm); } ... } initProxy // 代理函数 var

  • VUE的数据代理与事件详解

    目录 回顾Object.defineProperty方法 何为数据代理 Vue中的数据代理 事件的基本使用 事件的修饰符 键盘事件 总结 回顾Object.defineProperty方法 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>回顾Object.defineproperty方法</title> </head> <

  • Vue数据代理的实现流程逐步讲解

    目录 一,前言 二,对象劫持回顾 1,Demo 三,数组类型的处理 1,当前逻辑分析 2,Vue 对性能的权衡 3,数组的劫持思路 4,数组方法的拦截思路 5,数组方法重写的实现 6,数组方法拦截的实现 四,结尾 一,前言 上篇,主要介绍了 Vue 数据初始化流程中,对象属性的深层劫持是如何实现的 核心思路就是递归,主要流程如下: 1.通过 data = isFunction(data) ? data.call(vm) : data;处理后的 data 一定是对象类型 2.通过 data = o

  • Vue echarts画甘特图流程详细讲解

    vue项目中添加echarts,只需要增加echarts依赖,然后在main.js中引入echarts就可以使用了. 1.npm install echarts --save 2.修改main.js import * as echarts from 'echarts' Vue.prototype.$echarts = echarts 3.具体页面使用: <template> <div class="about"> <h1>This is echart

  • Vue引入高德地图实现流程分步讲解

    目录 一.功能需求 二.准备 三.在webstorm中安装 四.防止在使用中AMap无法识别问 五.正式开发 六.步骤总结 七.效果 一.功能需求 1.根据输入内容进行模糊查询,选择地址后在地图上插上标记,并更新经纬度坐标显示 2.在地图点击后,根据回传的左边更新地址信息和坐标显示 二.准备 1.申请高德地图账号,创建应用 2.在应用管理中 获得key 和安全密钥 三.在webstorm中安装 npm i @amap/amap-jsapi-loader -S 四.防止在使用中AMap无法识别问

  • Vue数据双向绑定的实现方式讲解

    目录 前言 一.input和textarea 二.radio和CheckBox 三.select 四.双向绑定的修饰符 前言 在web开发应用中,很多项目都会用到表格一列的组件进行数据的传输.获取和提交,在开发使用中,表格类组件数据的传输,我们一般可以使用v-model将输入的数据同步到data属性中,这个指令可以为不同的输入元素使用不同的属性,这个指令一般在form表单中的input等等元素上面来创建双向的数据绑定. 一.input和textarea 在vue实战项目中,vue里面的data属

  • vue中前进刷新、后退缓存用户浏览数据和浏览位置的实例讲解

    vue中,我们所要实现的一个场景就是: 1.搜索页面==>到搜索结果页时,搜索结果页面要重新获取数据, 2.搜索结果页面==>点击进入详情页==>从详情页返回列表页时,要保存上次已经加载的数据和自动还原上次的浏览位置. 最近在项目中遇到这个问题,思考了几套方案,总是不太完善.百度搜到的方案也基本都只能满足一些很简单的需求.对于复杂一些的情况,还是有些不完善的地方.以下是个人对于这种场景的一个摸索,也参考了百度.如有更好的方案,欢迎指出. 缓存组件,vue2中提供了keep-alive.首

  • Electron集成React和Vue流程方法讲解

    目录 集成React 1. 热调试 2. 打包 集成Vue 集成React 1. 热调试 在React项目目录下安装Electron npm install electron 修改package.json文件,增加或将已有的main属性值修改为main.js,在scriptes中添加"electron-start": "electron .",最终配置文件如下: { "name": "electron-react", &quo

随机推荐