【Vue2源码】响应式原理

news/2024/4/25 14:59:53/文章来源:https://blog.csdn.net/m0_63779088/article/details/130273962

【Vue2源码】响应式原理

文章目录

  • 【Vue2源码】响应式原理
    • `Vue响应式`的核心设计思路
    • 整体流程
    • 响应式中的关键角色
    • 检测变化注意事项
    • 响应式原理
        • 数据观测
        • 重写数组7个变异方法
        • 增加__ob__属性
          • __ob__有两大用处:

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

Vue响应式的核心设计思路

当创建Vue实例时,vue会遍历data选项的属性,利用Object.defineProperty为属性添加gettersetter对数据的读取进行劫持(getter用来依赖收集,setter用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。

每个组件实例会有相应的watcher实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有computed watcher,user watcher实例),之后依赖项被改动时,setter方法会通知依赖与此datawatcher实例重新计算(派发更新),从而使它关联的组件重新渲染。

整体流程

作为一个前端的MVVM框架,Vue的基本思路和AngularReact并无二致,其核心就在于: 当数据变化时,自动去刷新页面DOM,这使得我们能从繁琐的DOM操作中解放出来,从而专心地去处理业务逻辑。

这就是Vue的数据双向绑定(又称响应式原理)。数据双向绑定是Vue最独特的特性之一。此处我们用官方的一张流程图来简要地说明一下Vue响应式系统的整个流程:

在这里插入图片描述

Vue中,每个组件实例都有相应的watcher实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

这是一个典型的观察者模式。

响应式中的关键角色

在 Vue 数据双向绑定的实现逻辑里,有这样三个关键角色:

  • Observer: 它的作用是给对象的属性添加gettersetter,用于依赖收集和派发更新
  • Dep: 用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个Dep实例(里面subsWatcher实例数组),当数据有变更时,会通过dep.notify()通知各个watcher
  • Watcher: 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcher(user watcher)三种

检测变化注意事项

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 来实现一下对数组和对象的劫持。

\src\observe\index.js

import { newArrayProto } from "./array"class Observe {constructor (data) {//Object.defineReactive只能劫持已经存在的属性(vue俩民回为此单独写一些api)//data.__ob__ = this  //这里的this指的是Observe,把这个实例附到了ob上,还可以用于检测是否被劫持过Object.defineProperty(data,'__ob__',{value:this,enumerable:false //将__ob__编程不可枚举,这样循环的时候就无法获取到,不会进入死循环})if(Array.isArray(data)) {data.__proto__ = newArrayProtothis.observeArray(data)  //如果数组中放置的是对象,也可以被监控到//这里我们可以重写数组中的方法,7个变异方法,是可以修改到数组本身的}else {this.walk(data)}}walk (data) { //循环对象,对属性依次劫持//“重新定义”属性  性能差Object.keys(data).forEach(key => defineReactive(data, key, data[key]))}observeArray(data) {data.forEach(item=> observe(item))}
}export function defineReactive (target, key, value) {  //闭包observe(value) //对所有的对象都进行属性劫持  深度劫持Object.defineProperty(target, key, {get () { //取值的时候会执行getconsole.log(key,"key");return value},set (newValue) {  //修改的时候会执行setif (newValue === value) returnobserve(newValue)value = newValue}})
}export function observe (data) {if (typeof data !== 'object' || data == null) {//只对对象进行劫持return}if(data.__ob__ instanceof Observe) { //如果存在data.__ob__就说明这个被代理过了return data.__ob__}//如果一个对象被劫持过了,那就不需要再被劫持了//要判断一个对象是否被劫持过,可以增添一个实例,用实例来判断是否被劫持过return new Observe(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 调用原来的方法,并对新增数据进行劫持观测。
\src\observe\array.js


let oldArrayProto = Array.prototype  //获取数组的原型export let newArrayProto = Object.create(oldArrayProto)let methods = [ //通过遍历寻找到所有变异方法'push','pop','shift','reverse','sout','splice'
] //concat slice不会改变原数组methods.forEach(method => {newArrayProto[method] = function (...args) { //这里重写了数组的方法const result = oldArrayProto[method].call(this,...args)  //再内部调用原来的方法,函数的劫持,切片编程//我们需要对新增的数据进行劫持let insertedlet ob = this.__ob__switch (method) {case 'push':case 'unshift':inserted = argsbreak;case 'splice' : //arr.splice(0,1,{a:1},{b:2})inserted = argsbreak}console.log("xinzeng ");if(inserted) {//对新增的内容再次进行观测ob.observeArray(inserted)}return result}
})

增加__ob__属性

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

class Observe {constructor (data) {//Object.defineReactive只能劫持已经存在的属性(vue俩民回为此单独写一些api)//data.__ob__ = this  //这里的this指的是Observe,把这个实例附到了ob上,还可以用于检测是否被劫持过Object.defineProperty(data,'__ob__',{value:this,enumerable:false //将__ob__编程不可枚举,这样循环的时候就无法获取到,不会进入死循环})if(Array.isArray(data)) {data.__proto__ = newArrayProtothis.observeArray(data)  //如果数组中放置的是对象,也可以被监控到//这里我们可以重写数组中的方法,7个变异方法,是可以修改到数组本身的}else {this.walk(data)}}walk (data) { //循环对象,对属性依次劫持//“重新定义”属性  性能差Object.keys(data).forEach(key => defineReactive(data, key, data[key]))}observeArray(data) {data.forEach(item=> observe(item))}
}
__ob__有两大用处:

1、如果一个对象被劫持过了,那就不需要再被劫持了,要判断一个对象是否被劫持过,可以通过 ob 来判断


export function observe (data) {if (typeof data !== 'object' || data == null) {//只对对象进行劫持return}if (data.__ob__ instanceof Observe) { //如果存在data.__ob__就说明这个被代理过了return data.__ob__}//如果一个对象被劫持过了,那就不需要再被劫持了//要判断一个对象是否被劫持过,可以增添一个实例,用实例来判断是否被劫持过return new Observe(data)}

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

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Cartopy基础入门】如何丝滑的加载Geojson数据

原文作者:我辈李想 版权声明:文章原创,转载时请务必加上原文超链接、作者信息和本声明。 Cartopy基础入门 【Cartopy基础入门】Cartopy的安装 【Cartopy基础入门】如何丝滑的加载Geojson数据 文章目录 Cartopy基础入门一、Geojson数据来源二…

camunda的manual task节点用途

Camunda的Manual Task用于在流程中暂停执行,直到人工干预完成某个任务。与User Task不同,Manual Task没有分配给特定用户或用户组,而是需要手动启动并指定下一步流程。 Manual Task可以用于以下场景: 1、流程执行需要等待人工干…

安全狗入选2023年福建省数字经济核心产业领域创新企业名单

近日,福建省数字福建建设领导小组办公室公布了入选2023年全省数字经济核心产业领域创新企业名单。 作为国内云原生安全领导厂商,安全狗凭借综合表现与优势入选名单,荣膺“未来独角兽”称号。 据悉,此次对“未来独角兽”的评选条件…

Linux文件类型与属性

一、文件类型 Linux 系统下一共分为 7 种文件类型。通过 stat 命令或者 ls 命令来查看文件类型。 - :普通文件 d :目录文件 c :字符设备文件 b :块设备文件 l :符号链接文件 s :套接字文件 p &…

线性模型的介绍

一、背景 在一个理想的连续世界中,任何非线性的东西都可以被线性的东西来拟合,所以理论上线性模型可以模拟物理世界中的绝大多数现象。 线性模型(Linear Model)是机器学习中应用最广泛的模型,指通过样本特征的线性组…

【并发基础】一篇文章带你彻底搞懂Java线程中断的底层原理——interrupt()、interrupted()、isInterrupted()

目录 〇、Java线程中断与阻塞的区别 0.1 线程中断 0.2 线程阻塞 一、线程的中断 二、中断方法 2.1 void interrupt() 2.1.1 可中断的阻塞 2.1.2 不可中断的阻塞 2.1.3 实践案例 2.2 boolean isInterrupted() 2.3 boolean interrupted() 2.4 代码案例 三、源码分析…

指定GPU运行python程序

一、命令行运行python程序时 1、首先查看哪些GPU空闲,nvidia-smi显示当前GPU使用情况。 nvidia-smiGPU:编号,这里是0和1 Fan:风扇转速,在0到100%之间变动,第一个是29% Name:显卡名&#xff…

部署LVS-NAT群集实验

一、 实验准备 负载调度器:内网关 ens33:192.168.109.12,外网关 ens37:12.0.0.1外网 Web节点服务器1:192.168.109.13 Web节点服务器2:192.168.109.14 NFS服务器:192.168.109.11 客户端&#xf…

基于 Windows 安装 ESP32 Arduino 软件开发环境

ESP32 Arduino 源码库:arduino-esp32ESP32 Arduino 环境搭建说明:About Arduino ESP32 其他软件环境需求: Git 环境 1、安装 Arduino 软件 可在 Arduino 官网 获取 Windows 端 Arduino 安装包,如下: 使用如下 .exe 一…

中文编程最高境界,不用编程,会用excel就会用,香不香?

一直以来,关于中文编程的争议从未消停过。现如今,中文编程发展又是如何? ★为了实现中文编程,从未停下脚步 我们知道,中国人一直以来为了实现中文编程付出了不懈的努力,前前后后研发了几十种中文编程语言。…

ModuleNotFoundError: No module named ‘d2l’

目录 1. 下载李沐老师分享的源代码 step1:下载李沐老师分享的源代码: step3:Anaconda Prompt中安装d2l(这个l是英文) step4:运行代码,成功: (番外)ModuleNotFoundError: No mod…

R语言的Meta分析【全流程、不确定性分析】方法与Meta机器学习技术应用

Meta分析是针对某一科研问题,根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法,对来源不同的研究成果进行收集、合并及定量统计分析的方法,最早出现于“循证医学”,现已广泛应用于农林生态,资源环境等方面。…

Tensorflow GPU 版本安装教程

非常详细的 Tensorflow GPU 版本安装教程 一、安装Anaconda二、TensorFlow GPU 一、安装Anaconda 这一步比较简单,也没有太多的需要注意的,去官网下载即可: 官网地址如下: https://www.anaconda.com/blog/individual-edition-2…

今晚直播 | 思码逸陆春蕊:面对研发效能度量落地难点,如何让数据说话?

本期分享 本期 DevData Talks 邀请到了思码逸高级咨询专家陆春蕊老师。陆春蕊老师曾就职于 Oracle 美国,在软件质量、项目管理方面有着丰富的经验。在研发效能领域为上百家客户提供了技术、数据分析、实践落地等方面的咨询,协助客户提升研发效能10%-30%…

centos系统安装mysql8.0

centos系统安装mysql8.0 环境说明开始1、查看centos7中是否有MariaDB,MariaDB与MySQL关系请自行查阅2、如果有MariaDB,需要将 步骤1 中查询到的mairadb全部卸载,否则MySQL安装会出现问题3、查看本机是否已经安装过MySQL4、如果安装过MySQL&am…

【内网渗透】春秋云镜Intitle WP

前言 第一次正式接触内网渗透的东西,写的很新手,也适合新手观看,有问题可以私信或评论,接下来会持续更新 信息收集 拿到地址先nmap扫端口 没什么发现,直接访问80端口,看到图标知道是thinkphp 第一台Th…

JAVA队列(Queue)用法附实例讲解

队列是什么 队列用于模拟队列这种数据结构,队列通常是指“先进先出”的容器。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素。通常,队列不允许随机访问队列中的元素 …

MII、 RMII、 GMII、 RGMII 接口介绍

1、RGMII 接口概要 以太网的通信离不开物理层 PHY 芯片的支持,以太网 MAC 和 PHY 之间有一个接口,常用的接口有MII、 RMII、 GMII、 RGMII 等。 MII(Medium Independent Interface, 媒体独立接口): MII 支持…

三闯港交所,主打性价比的乡村基如何夺魁“中式快餐第一股”?

曾被中金公司称为“中国大消费最燃赛道”的中式餐饮,正在密集掀起IPO的风潮。去年5月和7月,老乡鸡和老娘舅分别向上交所提交招股书,绿茶餐厅、杨国福麻辣烫、捞王等企业也在推进上市计划。 国内第四大中式快餐集团,占据约0.6%市场…

Linux 通过Chrony实现NTP

Linux实现NTP服务器时间同步,可以通过ntp服务实现,也可以通过chrony服务实现 两者区别主要有 Chrony运行于UDP的323端口,NTP运行于UDP的123端口 Chrony相比于NTP可以更快同步,能够最大同步的减少时间和频率的误差 Chrony能够更好…