首页 > 其他分享 >Vue2响应式原理

Vue2响应式原理

时间:2023-03-24 14:47:17浏览次数:40  
标签:__ Vue2 对象 Object ob 响应 数组 原理 data

Vue.js 基本上遵循 MVVM(Model–View–ViewModel)架构模式,数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。 本文讲解一下 Vue 响应式系统的底层细节。

检测变化注意事项

Vue 2.0中,是基于 Object.defineProperty 实现的响应式系统 (这个方法是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因)
vue3 中,是基于 Proxy/Reflect 来实现的

  1. 由于 JavaScript 的限制,这个 Object.defineProperty() api 没办法监听数组长度的变化,也不能检测数组和对象的新增变化。
  2. Vue 无法检测通过数组索引直接改变数组项的操作,这不是 Object.defineProperty() api 的原因,而是尤大认为性能消耗与带来的用户体验不成正比。对数组进行响应式检测会带来很大的性能消耗,因为数组项可能会大,比如1000条、10000条。

响应式原理

响应式基本原理就是,在 Vue 的构造函数中,对 options 的 data 进行处理。即在初始化vue实例的时候,对data、props等对象的每一个属性都通过 Object.defineProperty 定义一次,在数据被set的时候,做一些操作,改变相应的视图。

数据观测

让我们基于 Object.defineProperty 来实现一下对数组和对象的劫持。

import { newArrayProto } from './array'

class Observer {
  constructor(data){
    if (Array.isArray(data)) {
      // 这里我们可以重写可以修改数组本身的方法 7个方法,切片编程:需要保留数组原有的特性,并且可以重写部分方法
      data.__proto__ = newArrayProto
      this.observeArray(data) // 如果数组中放的是对象 可以监控到对象的变化
    } else {
      this.walk(data)
    }
  }
  // 循环对象"重新定义属性",对属性依次劫持,性能差
  walk(data) {
    Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
  }
  // 观测数组
  observeArray(data) {
    data.forEach(item => observe(item))
  }
}

function defineReactive(data,key,value){
  observe(value)  // 深度属性劫持,对所有的对象都进行属性劫持

  Object.defineProperty(data,key,{
    get(){
      return value
    },
    set(newValue){
      if(newValue == value) return
      observe(newValue) // 修改属性之后重新观测,目的:新值为对象或数组的话,可以劫持其数据
      value = newValue
    }
  })
}

export function observe(data) {
  // 只对对象进行劫持
  if(typeof data !== 'object' || data == null){
    return
  }
  return new Observer(data)
}

重写数组7个变异方法

7个方法是指:push、pop、shift、unshift、sort、reverse、splice。(这七个都是会改变原数组的)

实现思路:面向切片编程!!!

不是直接粗暴重写 Array.prototype 上的方法,而是通过原型链继承与函数劫持进行的移花接木。

利用 Object.create(Array.prototype) 生成一个新的对象 newArrayProto,该对象的 __proto__指向 Array.prototype,然后将我们数组的 __proto__指向拥有重写方法的新对象 newArrayProto,这样就保证了 newArrayProto 和 Array.prototype 都在数组的原型链上。

arr.__proto__ === newArrayProto;newArrayProto.__proto__ === Array.prototype

然后在重写方法的内部使用 Array.prototype.push.call 调用原来的方法,并对新增数据进行劫持观测。

let oldArrayProto = Array.prototype // 获取数组的原型

export let newArrayProto = Object.create(oldArrayProto)

// 找到所有的变异方法
let methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice']

methods.forEach(method => {
  // 这里重写了数组的方法
  newArrayProto[method] = function (...args) {
    // args reset参数收集,args为真正数组,arguments为伪数组
    const result = oldArrayProto[method].call(this, ...args) // 内部调用原来的方法,函数的劫持,切片编程

    // 我们需要对新增的数据再次进行劫持
    let inserted
    let ob = this.__ob__

    switch (method) {
      case 'push':
      case 'unshift': // arr.unshift(1,2,3)
        inserted = args
        break
      case 'splice': // arr.splice(0,1,{a:1},{a:1})
        inserted = args.slice(2)
      default:
        break
    }

    if (inserted) {
      // 对新增的内容再次进行观测
      ob.observeArray(inserted)
    }
    return result
  }
})

增加__ob__属性

这是一个恶心又巧妙的属性,我们在 Observer 类内部,把 this 实例添加到了响应式数据上。相当于给所有响应式数据增加了一个标识,并且可以在响应式数据上获取 Observer 实例上的方法

class Observer {
  constructor(data) {
    // data.__ob__ = this // 给数据加了一个标识 如果数据上有__ob__ 则说明这个属性被观测过了
    Object.defineProperty(data, '__ob__', {
      value: this,
      enumerable: false, // 将__ob__ 变成不可枚举 (循环的时候无法获取到,防止栈溢出)
    })

    if (Array.isArray(data)) {
      // 这里我们可以重写可以修改数组本身的方法 7个方法,切片编程:需要保留数组原有的特性,并且可以重写部分方法
      data.__proto__ = newArrayProto
      this.observeArray(data) // 如果数组中放的是对象 可以监控到对象的变化
    } else {
      this.walk(data)
    }
  }

}

__ob__有两大用处:

  1. 如果一个对象被劫持过了,那就不需要再被劫持了,要判断一个对象是否被劫持过,可以通过__ob__来判断
// 数据观测
export function observe(data) {
  // 只对对象进行劫持
  if (typeof data !== 'object' || data == null) {
    return
  }

  // 如果一个对象被劫持过了,那就不需要再被劫持了 (要判断一个对象是否被劫持过,可以在对象上增添一个实例,用实例的原型链来判断是否被劫持过)
  if (data.__ob__ instanceof Observer) {
    return data.__ob__
  }

  return new Observer(data)
}
  1. 我们重写了数组的7个变异方法,其中 push、unshift、splice 这三个方法会给数组新增成员。此时需要对新增的成员再次进行观测,可以通过__ob__调用 Observer 实例上的 observeArray 方法

标签:__,Vue2,对象,Object,ob,响应,数组,原理,data
From: https://www.cnblogs.com/burc/p/17251352.html

相关文章

  • vue-cli 初始化创建 vue2.9.6 项目路由守卫、封装axios、vuex
    阅读目录Vue如何封装Axios请求1安装axios2封装代码axios应用方式Vue中的路由守卫使用演示1全局守卫2组件级守卫3单个路由独享的守卫4官网整个路由守卫被触发流程的......
  • vue-cli 初始化创建 vue2.9.6 项目
    阅读目录vue-cli安装vue-cli初始化创建项目1、vueinit命令讲解2、初始化创建项目package.json项目结构1、build目录(webpack配置)2、config目录(vue项目配置目录)3、node_mod......
  • [FastAPI-21]响应状态码
    fromfastapiimportFastAPI,statusfrompydanticimportBaseModelapp=FastAPI()'''响应状态代码status'''classUser(BaseModel):username:str......
  • kafka集群原理及部署
    官方地址https://kafka.apache.org/概述Kakfa起初是由LinkedIn公司开发的一个分布式的消息系统,后成为Apache的一部分,它使用Scala编写,以可水平扩展和高吞吐率而被广泛使......
  • Bitmap、RoaringBitmap原理分析
    作者:京东科技 曹留界在人群本地化实践中我们介绍了人群ID中所有的pin的偏移量可以通过Bitmap存储,而Bitmap所占用的空间大小只与偏移量的最大值有关系。假如现在要向Bitma......
  • SG90舵机的原理和控制方式
    前言做过机器人、智能车或者玩航模的朋友应该对舵机不会陌生,这种舵机也是很常用的。舵机只是我们通俗的叫法,它的本质是一个伺服电机,也可以叫做位置(角度)伺服驱动器。一般被......
  • 网络对抗Exp2-后门原理与实践
    目录实验基础基础知识实验目标实验环境在主机上用ipconfig查看Windows11主机IP在Kali虚拟机中用sudoifconfig查看Kali虚拟机IP实验过程一、使用netcat获取主机操作Shell,启......
  • 后门原理与实践—20201229赵斌
    Exp2后门原理与实践—20201229赵斌基础问题回答例举你能想到的一个后门进入到你系统中的可能方式?在网上下载软件的时候,后门很有可能被捆绑在下载的软件当中;浏览网页......
  • 网络对抗实验二 后门原理与实践--20201313
    目录《网络对抗技术》——Exp2后门原理与实践一、实验准备1、实验要求2、后门3、常用的后门工具(ncat,...)二、实验内容1.使用netcat获取主机操作Shell,cron启动2.使用soc......
  • Vue,js Vuex工作原理图
    Vuex原理解析视频107传参带数据允许走这条线store管理......