Vue2响应式原理

目录

Object.defineProperty()

监听对象中的简单数据类型

监听对象中的对象(可以深层)

监听对象中的数组


借鉴的帖子:Object.defineProperty方法(详解)_objectdefineproperty_搞前端的小菜的博客-CSDN博客

b站视频讲解:Vue2响应式原理【Vue】_哔哩哔哩_bilibili

Object.defineProperty()

我们都知道Vue2响应式是应用了Object.defineProperty()这个方法进行数据代理,所以就需要了解一下Object.defineProperty()这个方法

首先这个方法,默认接收3个参数

  1. 第一个参数就是需要监听的属性所在的对象名,不需要带引号
  2. 第二个参数就是需要监听的属性名,需要带引号
  3. 第三个参数就是一个描述符对象

描述符对象里面可以有什么呢

可以有以下几个属性(前4个了解就行,最重要的是get和set方法)

  1. configurable  表示能否通过delete删除属性从而重新定义属性,默认值为false

  2. enumerable   表示能否通过for in循环访问属性,默认值为false

  3. writable   表示能否修改属性的值。默认值为false

  4. value   包含这个属性的数据值。默认值为undefined

const obj = { name: 'cxk', age: 32 }
Object.defineProperty(obj, 'name', {
    // 表示能否通过delete删除属性从而重新定义属性,默认值为false
    configurable: false,
    // 表示能否通过for in循环访问属性,默认值为false
    enumerable: false,
    // 表示能否修改属性的值。默认值为false
    writable: false,
    // 包含这个属性的数据值。默认值为undefined
    // value: ''
})
// ----------------
delete obj.name
console.log(obj) // {name: 'cxk', age: 32} 因为配置了configurable为false,所以name删除不掉
// ----------------
for (const key in obj) {
    console.log(key)      // age  因为配置了configurable为false,所以无法通过for...in遍历name
    console.log(obj[key]) // 32  同上
}
// ----------------
obj.name = 'kk'
console.log(obj) // {age: 32, name: 'cxk'} 因为配置了writable为false,所以无法进行修改name

还可以有get和set方法

  • 读取对象中的属性/方法,就会调用get方法。默认undefined
  • 只要设置(写入)对象中的属性/方法,就会调用set方法。默认undefined

// 字面量声明一个普通函数
        const obj = {
            name: 'zs',
            age: 24
        }
        // 定义一个新的name值
        let newName = ''
        // 使用Object.defineProperty(),并传入对应的参数
        Object.defineProperty(obj, 'name', {
            // 只要读取对象中的属性/方法,就会调用get方法。默认undefined
            get() {
                console.log('get执行了')
                return newName
            },
            // 只要设置对象中的属性/方法,就会调用set方法。默认undefined
            // 能够接收一个参数,就是修改后的新值
            set(newVal) {
                console.log('set执行了')
                newName = newVal
            }
        })
        obj.name = 'cxk' // set执行了
        console.log(obj.name) // get执行了  cxk

通过get和set方法,我们就实现了一个最简单的响应式

监听对象中的简单数据类型

首先判断是否为对象,然后遍历对象,调用Object.defineProperty(),传入每个属性

const obj = {
            name: 'cxk',
            age: 30,
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        // console.log(observer(10)) // 10
        // console.log(observer(null)) // null
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦

监听对象中的对象(可以深层)

首先我们还用上面的进行测试,发现是监听不到的,看注释

const obj = {
            name: 'cxk',
            age: 30,
            skill: {
                sing: '唱',
                dance: '跳',
            }
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        // console.log(observer(10)) // 10
        // console.log(observer(null)) // null
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                    // else {
                    //     console.log('测试对象是否会走这个')
                    // }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦
        // 测试下对象中的对象
        obj.skill.sing = '太美' // 就不会打印
        // 因为走到defineReactive()函数,我们传入的是defineReactive(obj,'skill',{ sing: '唱',dance: '跳' })
        // 我们虽然改变了sing的值,但是走到set的时候,判断的是对象内存地址,地址是没有改变的,所以它就没有走判断里面的
        // 也就不会赋值,不会打印。测试也很简单,我们在if下面加个else即可,看是否会打印else里面的(我注掉了,确实打印了)

我们进行一下改进,只需要在defineReactive()函数里,再调用一下observer()方法即可,把对象中的对象传进去。可以进行深层监听

const obj = {
            name: 'cxk',
            age: 30,
            skill: {
                dance: '跳',
                sing: {
                    song: '喂喂喂'
                }
            }
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        // console.log(observer(10)) // 10
        // console.log(observer(null)) // null
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            // 如果还要监听对象中的对象,则就需要再重新调用一下observer()
            observer(value)
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦
        // 测试下对象中的对象,还有深层的
        obj.skill.dance = '闻鸡起舞' // 视图更新啦
        obj.skill.sing.song = '太美' // 视图更新啦

这样看起来是解决了监听对象中的对象的问题。但是如果我们把对象的简单数据类型,改为复杂数据类型,然后再进行值的修改,再进行监听呢

const obj = {
            name: 'cxk',
            age: 30,
            skill: {
                dance: '跳',
                sing: {
                    song: '喂喂喂'
                }
            }
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            // 如果还要监听对象中的对象,则就需要再重新调用一下observer()
            observer(value)
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦
        // 测试下对象中的对象,还有深层的
        obj.skill.dance = '闻鸡起舞' // 视图更新啦
        obj.skill.sing.song = '太美' // 视图更新啦
        // 改变对象的简单数据类型为复杂数据类型,然后再进行改变,看是否能侦听到
        obj.age = { fangling: 30 } // 视图更新啦,这步是能监听到的,因为是监听简单数据类型
        obj.age.fangling = 26 // 我们再进行修改,是监听不到的。因为我们在设置的时候,没有把fangling设置上去,所以就没监听到

显然,没有监听到。想要监听到也很简单,就是在设置的时候,再调用一下

const obj = {
            name: 'cxk',
            age: 30,
            skill: {
                dance: '跳',
                sing: {
                    song: '喂喂喂'
                }
            }
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            // 如果还要监听对象中的对象,则就需要再重新调用一下observer(),可以深层
            observer(value)
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 如果还要监听对象中的简单数据类型修改为对象后,然后再修改值的对象中的属性,则就需要再重新调用一下observer()
                    observer(newVal)
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦');
                    }
                }
            })
        }

        obj.name = 'cxk' // 这个就不会打印,因为值没有变化(看set里的判断)
        obj.name = 'xxx' // 视图更新啦
        // 测试下对象中的对象,还有深层的
        obj.skill.dance = '闻鸡起舞' // 视图更新啦
        obj.skill.sing.song = '太美' // 视图更新啦
        // 改变对象的简单数据类型为复杂数据类型,然后再进行改变,看是否能侦听到
        obj.age = { fangling: 30 } // 视图更新啦
        obj.age.fangling = 26 // 视图更新啦

走到这,就感觉出来好麻烦。除了深度监听意外,对象属性的新增和删除,它是监听不到的。所以vue2推出了set方法,让我们手动更新

删除

const obj = {
            name: 'cxk',
            age: 28
        }
        // 测试删除
        let newName = ''
        Object.defineProperty(obj, 'name', {
            get() {
                console.log('get执行了')
                return newName
            },
            set(newVal) {
                console.log('set执行了')
                newName = newVal
            }
        })
        // 删除name属性
        delete obj.name // 发现什么也没有输出,说明它监听不到对象属性的删除操作

新增

const obj = {
            name: 'cxk',
            age: 28
        }
        // 测试属性删除
        let newName = ''
        Object.defineProperty(obj, 'name', {
            get() {
                console.log('get执行了')
                return newName
            },
            set(newVal) {
                console.log('set执行了')
                newName = newVal
            }
        })
        delete obj.name // 发现什么也没有输出,说明它监听不到对象属性的删除操作
        // 新增属性操作
        obj.skill = '唱跳两年半' // 也是侦听不到,因为它是对对象的某一属性进行侦听,一开始都没有,肯定也侦听不到的

监听对象中的数组

vue2官网上标明了数组不能修改的操作,一共有两个

 我们进行一下测试,发现数组通过下标修改,其实是可以监听到的,可能vue2舍弃了

const obj = {
            name: 'cxk',
            age: 30,
            skill: ['sing', 'dance', 'rap']
        }

        function observer(target) {
            // 判断传入的是否是对象
            if (typeof target !== 'object' || target === null) {
                return target
            }
            // 遍历对象,调用Object.defineProperty()对每个属性进行监听
            for (let key in target) {
                defineReactive(target, key, target[key])
            }
        }
        observer(obj)

        // 对每个属性都进行监听
        function defineReactive(target, key, value) {
            // 如果还要监听对象中的对象,则就需要再重新调用一下observer(),可以深层
            observer(value)
            Object.defineProperty(target, key, {
                get() {
                    return value
                },
                set(newVal) {
                    // 如果还要监听对象中的简单数据类型修改为对象后,然后再修改值的对象中的属性,则就需要再重新调用一下observer()
                    observer(newVal)
                    // 进行一下判断,如果值改变了,就会赋值,触发更新
                    if (newVal !== value) {
                        value = newVal
                        console.log('视图更新啦')
                    }
                }
            })
        }

        // 1. 根据数组下标是能监听到的,但是vue2官网说监听不到,可能是舍弃了
        obj.skill[0] = '太美' // 视图更新啦
        // 2. 直接修改length,修改数组,也是监听不到
        obj.skill.length = 5 // 没有输出,监听不到

欢迎姥爷观看,喜欢的给赞一下呗

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

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

相关文章

学习 Python 之 Pygame 开发魂斗罗(十三)

学习 Python 之 Pygame 开发魂斗罗(十三)继续编写魂斗罗1. 创建敌人2类2. 编写敌人2类的draw()函数3. 编写敌人越界消失函数4. 编写敌人开火函数5. 把敌人2加入地图进行测试继续编写魂斗罗 在上次的博客学习 Python 之 Pygame 开发魂斗罗(十…

Adapter基础讲解

这一节我们要讲的UI控件都是跟Adapter(适配器)打交道的,了解并学会使用Adapter很重要, Adapter是用来帮助填充数据的中间桥梁,简单来说就是:将各种数据以合适的形式显示到view上,提供 给用户看! 1.MVC模式的简单理解 在开始学习Adapter之前我们要来了解下这个MVC模式概…

SpringBoot——Mybatis-XML映射文件—动态SQL

使用XML映射文件配置SQL语句的规范 XML文件当中的Mapper标签里面使用的select标签的id属性是Mapper接口里面的方法名,resultType属性名是SQL语句要返回的对象类型。 MybatisX插件 用于管理接口方法和映射文件的关系 注解和XML配置SQL语句两种选择 动态SQL——w…

Cookie 和 Session的区别

文章目录时间:2023年3月23日第一:什么是 Cookie 和 Session ?什么是 Cookie什么是 Session第二:Cookie 和 Session 有什么不同?第三:为什么需要 Cookie 和 Session,他们有什么关联?第四&#x…

由本溯源,带你探索BI实时性的本质

BI 采用T1模式 BI的数据仓库架构本身就决定了对数据的实时性要求没有那么高,ETL的过程不可或缺,Extraction 抽取、Transformation 转换、Loading 加载,这三个环节本身就是有时间损耗的。 BI - 派可数据BI可视化分析平台 首先,Ex…

动态内存管理(上)——“C”

各位CSDN的uu们你们好呀,今天,小雅兰的内容是动态内存管理噢,下面,让我们进入动态内存管理的世界吧 为什么存在动态内存分配 动态内存函数的介绍 malloc free calloc realloc 常见的动态内存错误 为什么存在动态内存分配 我们已…

第十四届蓝桥杯三月真题刷题训练——第 22 天

目录 第 1 题:受伤的皇后_dfs 题目描述 输入描述 输出描述 输入输出样例 运行限制 代码: 思路: 第 2 题:完全平方数 问题描述 输入格式 输出格式 样例输入 1 样例输出 1 样例输入 2 样例输出 2 评测用例规模与约…

Jenkins--邮件通知设置与构建日志邮件通知设置(踩过很多坑之后,记录一下)

1为啥要配置邮件通知配置邮件通知的目的是为了给用户发送构建结果的状态,以便用户知晓构建任务后的结果。2 开始配置邮件通知2.1 插件准备先检查Jenkins是否安装了Email Extension Plugin 插件,检查方法如下:进入Jenkins-->Manage Jenkins…

uni-app:登录与支付--用户信息

用户信息 实现用户头像昵称区域的基本布局 在 my-userinfo 组件中&#xff0c;定义如下的 UI 结构&#xff1a; <template><view class"my-userinfo-container"><!-- 头像昵称区域 --><view class"top-box"><image src"…

蓝桥杯刷题冲刺 | 倒计时18天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录0.知识点1.乳草的入侵今天写 搜索题 0.知识点 DFS 设计步骤 确定该题目的状态&#xff08;包括边…

串口,IIC,SPI,USB等总线硬件知识总结

串口&#xff0c;IIC&#xff0c;SPI&#xff0c;USB等总线硬件知识总结 文章目录串口&#xff0c;IIC&#xff0c;SPI&#xff0c;USB等总线硬件知识总结1 串口2.I2C3.SPI4.USB控制&#xff08;Control&#xff09;传输方式同步&#xff08;Isochronous&#xff09;传输方式中…

vue3+vite+ts 搭建脚手架01创建vite项目并且在项目中初次使用router

vue3vite 搭建脚手架01创建vite项目并且在项目中使用router 1.使用yarn安装vite项目 yarn create vite 搭建vite项目 在开发语言中选择vuets2.安装现在最新的 vue-router4 yarn add vue-router4 在packger中检查是否成功安装3.简单配置router文件 在项目中新建views和…

【C++】map、set、multimap、multiset的介绍和使用

我讨厌世俗&#xff0c;也耐得住孤独。 文章目录一、键值对二、树形结构的关联式容器1.set1.1 set的介绍1.2 set的使用1.3 multiset的使用2.map2.1 map的介绍2.2 map的使用2.3 multimap的使用三、两道OJ题1.前K个高频单词&#xff08;less<T>小于号是小的在左面升序&…

如何将字符串反转?

参考答案 使用 StringBuilder 或 StringBuffer 的 reverse 方法&#xff0c;本质都调用了它们的父类 AbstractStringBuilder 的 reverse 方法实现。&#xff08;JDK1.8&#xff09;不考虑字符串中的字符是否是 Unicode 编码&#xff0c;自己实现。递归1. public AbstractStrin…

优秀程序员的5个特征,你在第几层?

每个人程序员都对未来的职业发展有着憧憬和规划&#xff0c;要做架构师、要做技术总监、要做CTO。但现实总是复杂的&#xff0c;日复一日的工作与生活总能让人一次又一次地陷入迷茫。大部分原因就是对职业发展轨迹和自我能力提升的一般规律缺乏认识&#xff0c;做事找不到方向或…

WebSocket 测试工具

一、WebSocket 简介 WebSocket是一种在单个TCP连接上进行全双工通信的协议。 WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向客户端推送数据。在WebSocket API中&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就直…

MySQL数据库管理系统安装部署——Linux

MySQL数据库管理系统安装部署——Linux 简介 MySQL数据库管理系统&#xff08;后续简称MySQL)&#xff0c;是一款知名的数据库系统&#xff0c;其特点是:轻量、简单、功能丰富。MySQL数据库可谓是软件行业的明星产品&#xff0c;无论是后端开发、大数据、AI、运维、测试等各类…

linux入门---操作体统的概念

什么是操作系统 操作系统是一个对软硬件资源进行管理的软件。计算机由一堆硬件组成&#xff0c;这些硬件遵循着冯诺依曼体系结构 在这个硬件的基础上还有一个软件叫做操作系统 操作系统的任务是对硬件进行管理&#xff0c;既然是管理的话操作系统得访问到底层的硬件&#xf…

【贪心算法】一文让你学会“贪心”(贪心算法详解及经典案例)

文章目录前言如何理解“贪心算法”&#xff1f;贪心算法实战分析1.分糖果2.钱币找零3.区间覆盖内容小结最后说一句&#x1f431;‍&#x1f409;作者简介&#xff1a;大家好&#xff0c;我是黑洞晓威&#xff0c;一名大二学生&#xff0c;希望和大家一起进步。 &#x1f47f;本…

Spring Boot 各层作用与联系

目录 1 Entity 层 2 DAO 层 3 Service 层 4 Controller 层 Spring Boot 各层之间的联系&#xff1a; controller 层-----> service 层(接口->接口实现类) -----> dao 层的.mapper 文件 -----> 和 mapper 层里的.xml 文件对应 1 Entity 层 实体层&#xff0c;…
最新文章