手写代码理解vue响应式原理

news2024/12/23 16:35:05

vue2响应式应用了Object.defineProperty,vue3中的响应式则是运用proxy。

目录标题

    • 1、defineProperty
    • 2、代码理解defineProperty
    • 3、手写vue2响应式原理
    • 4、vue2监听数组响应式
    • 5、Proxy理解
    • 6、总结

1、defineProperty

Object.defineProperty(obj, prop, descriptor)
obj:要定义属性的对象
prop:要定义或修改的属性的名称或 Symbol
descriptor:要定义或修改的属性描述符
返回值:被传递给函数的对象。

configurable
当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为 false。

enumerable
当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。 默认为 false。

数据描述符还具有以下可选键值:

value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为 undefined。

writable
当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符 (en-US)改变。 默认为 false。

存取描述符还具有以下可选键值:

get
属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 undefined。

set
属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 默认为 undefined。

描述符默认值汇总
拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
属性值和函数的键 value、get 和 set 字段的默认值为 undefined。

2、代码理解defineProperty

监听obj数据变化

let obj={}

Object.defineProperty(obj,"key",{
    value:1,
    writable:true,//是否可以赋值
    enumerable:true,//是否可循环
    configurable:true //是否可删除
})

console.log(obj);

let obj1={}
Object.defineProperty(obj1,'key',{
    get(){
        console.log("获取到的值:",value);
        return value
    },
    set(newVal){
        console.log("设置值为",newVal);
        value=newVal+"设置"
    }
})

obj1.key="橙子很甜"

打印结果:
在这里插入图片描述

3、手写vue2响应式原理

let obj = {
    name: "橙子",
    job: {
        code: 100
    },
    arr:[1,2,3]
}


//封装监听数据变化
function defineProperty(obj, key, val) {
    //遍历监听数据每一项
    observer(val);
    Object.defineProperty(obj, key, {
        get() {
            //读取
            console.log("读取key", key);
            return val
        },
        set(newVal) {
            //判断新老值
            if (newVal === val) return
            observer(newVal);
            console.log("设置", newVal);
            val = newVal
        }
    })

}

function observer(obj) {
    //先判断是不是object
    if (typeof obj !== 'object' || obj == null) {
        return
    }
    for (const key in obj) {
        //对象,当前的key,值
        defineProperty(obj, key, obj[key])
    }

}

observer(obj)
console.log(obj.name);
obj.name = "蛋挞排队"
//...

打印结果:
在这里插入图片描述

obj.job.code=300

打印结果:
在这里插入图片描述

obj.name = {
name1: ‘张三’
}
obj.name.name1 = “李四”

打印结果:
在这里插入图片描述
注意

  • defineProperty中没有办法监听数组的变化
  • 监听数组时读取成功但是没办法监听
  • vue2用了重写了数组的方法监听数组,defineProperty本身是不能监听数组
  • vue2中监听不到通过数组下标改变数组元素

obj.arr.push(4)
console.log(obj.arr);

在这里插入图片描述
Object.defineProperty 是对象的方法监听不到数组的变更的 Vue2.x的做法是重写数组的7个方法。
把数组原型上所有的方法拿出来,即先克隆一份Array的原型出来,给每一个方法都设置响应式

4、vue2监听数组响应式

// Object.defineProperty 是对象的方法监听不到数组的变更的 Vue2.x的做法是重写数组的7个方法
// 封装监听数据变化的函数
let obj = {
    name: "橙子",
    job: {
        code: 100
    },
    arr:[1,2,3]
}
const arrayMethods = Array.prototype;
// 把数组原型上所有的方法拿出来,即先克隆一份Array的原型出来,这七个方法
const arrayProto = Object.create(arrayMethods);
const methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]
methodsToPatch.forEach(method => {
    arrayProto[method] = function () {
        // 执行原始操作,给它添加监听,挂到原型链上,内部还是执行数组的方法
        arrayMethods[method].apply(this, arguments)
        console.log('监听赋值成功', method)
    }
})

function defineProperty(obj, key, val) {
    observer(val)
    Object.defineProperty(obj, key, {
        get() {
            // 读取方法
            console.log('读取', key, '成功')
            return val
        },
        set(newval) {
            // 赋值监听方法
            if (newval === val) return
            // 遍历监听数据的每一项 
            observer(newval)
            console.log('赋值成功', newval)
            val = newval
            // 可以执行渲染操作
        }
    })
}

function observer(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return
    }
    if (Array.isArray(obj)) {
        // 如果是数组, 重写原型
        obj.__proto__ = arrayProto
        // 传入的数据可能是多维度的,也需要执行响应式
        for (let i = 0; i < obj.length; i++) {
            observer(obj[i])
        }
    } else {
        for (const key in obj) {
            // 给对象中的每一个方法都设置响应式
            defineProperty(obj, key, obj[key])
        }
    }
}

observer(obj)

obj.arr.push(4)

在这里插入图片描述

5、Proxy理解

const p = new Proxy(target, handler)

proxy(数据源对象,处理数据方法)

let obj = {
    name: '小陈'
}
let handler = {
    get(target, key, receiver) {
        console.log('get', key)
        return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
        console.log('set', key, value)
        return Reflect.set(target, key, value, receiver)
    }
}
let proxy = new Proxy(obj, handler)
proxy.name = '橙汁'

在这里插入图片描述

handler 对象的方法
handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。
所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。
handler.getPrototypeOf()
Object.getPrototypeOf 方法的捕捉器。
handler.setPrototypeOf()
Object.setPrototypeOf 方法的捕捉器。
handler.isExtensible()
Object.isExtensible 方法的捕捉器。
handler.preventExtensions()
Object.preventExtensions 方法的捕捉器。
handler.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.defineProperty()
Object.defineProperty 方法的捕捉器。
handler.has()
in 操作符的捕捉器。
handler.get()
属性读取操作的捕捉器。
handler.set()
属性设置操作的捕捉器。
handler.deleteProperty()
delete 操作符的捕捉器。
handler.ownKeys()
Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.apply()
函数调用操作的捕捉器。
handler.construct()
new 操作符的捕捉器。
在官网可以看到proxy的用法
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

监听数组变化

let arr = [1, 2, 3]
let proxy = new Proxy(arr, {
    get(target, key, receiver) {
        console.log('get', key)
        return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
        console.log('set', key, value)
        return Reflect.set(target, key, value, receiver)
    }
})
proxy.push(4)

在这里插入图片描述

6、总结

1、Proxy 的第二个参数可以有 13 种拦截方法, 比 Object.defineProperty() 要更加丰富,
Proxy 作为新标准受到浏览器厂商的重点关注和性能优化。
2、相比之下Object.defineProperty() 是一个已有的老方法。Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改。
3、Proxy 的兼容性不如 Object.defineProperty() 。
4、proxy可以直接监听数组变化,监听的目标为对象本身,不需要像defineProperty那样遍历每个属性

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

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

相关文章

【8.索引篇】

索引分类 索引和数据就是位于存储引擎中&#xff1a; 按「数据结构」分类&#xff1a;Btree索引、Hash索引、Full-text索引。按「物理存储」分类&#xff1a;聚簇索引&#xff08;主键索引&#xff09;、二级索引&#xff08;辅助索引&#xff09;。按「字段特性」分类&#…

linux字符设备和块设备的区别 以及网络设备

一、字符设备 1、字符设备以字节为单位。大多数设备是字符设备&#xff0c;因为他们不需要缓冲而且不以固定块大小进行操作。 2、字符设备无需缓冲直接读写。 3、字符设备只能被顺序读写。 二、块设备 1、块设备只能以块为单位接受输入和输出。 2、块设备对I/0请求有对应的缓冲…

建立自己的博客

环境安装&#xff1a; w10系统安装 第一步&#xff1a;安装git Git 官网: https://git-scm.com/ 第二步&#xff1a;安装Node.js Node.js官网&#xff1a;https://nodejs.org/zh-cn/ 使用cmd检测&#xff1a; node -v 第三步&#xff1a;安装Hexo Hexo官网&#xff1a;htt…

PyInstaller 将DLL文件打包进exe

PyInstaller 将DLL文件打包进exe方法1&#xff1a;通过--add-data命令方法2&#xff1a;通过修改 .spec扩展&#xff1a;博主热门文章推荐&#xff1a;方法1&#xff1a;通过–add-data命令 注意&#xff1a;这里 dll末尾添加的.为当前目录&#xff0c;则该dll要放到main.py同一…

【零基础入门学习Python---Python的五大数据类型之字符串类型】

一.Python的五大数据类型之字符串类型 在Python中,变量用于存储数据。变量名可以是任何字母、数字和下划线的组合。Python支持多种数据类型,包括数字、字符串、列表、元组和字典。这篇文章我们就来学习一下五大数据类型中的字符串类型。 1.1 什么是字符串? 字符串是Pyth…

[acwing周赛复盘] 第 94 场周赛20230311

[acwing周赛复盘] 第 94 场周赛20230311 一、本周周赛总结二、 4870. 装物品1. 题目描述2. 思路分析3. 代码实现三、4871. 最早时刻1. 题目描述2. 思路分析3. 代码实现四、4872. 最短路之和1. 题目描述2. 思路分析3. 代码实现六、参考链接一、本周周赛总结 又是笨比的一周&…

保姆级图文教程 - VirtualBox安装配置Kali Linux

文章目录下载Kali Linux虚拟机包安装Kali用户配置网络配置静态ipDHCP分配IP换deb源下载Kali Linux虚拟机包 官网地址&#xff1a;https://www.kali.org/get-kali/#kali-virtual-machines 我们选择virtualbox版的&#xff0c;就是最中间的那个。 安装Kali 将压缩包解压&…

计算机网络:传输层概述

传输层 只有主机才有的层次 传输层的功能&#xff1a; 1.传输层提供进程与进程之间的逻辑通信。 2.复用&#xff1a;应用层的所有进程可以都使用一同传输层协议。 3.分用&#xff1a;传输层从网络层收到数据后&#xff0c;交付给指明的应用进程。 4.传输层对收到的报文进行差错…

二十一、Django-restframework之序列化器补充

一、常用序列化器字段 序列化器字段处理基元值和内部数据类型之间的转换。它们还处理输入值的验证&#xff0c;以及从它们的父对象检索和设置值。 &#xff08;1&#xff09;核心参数 每个序列化器字段类构造函数至少接受这些参数。一些字段类还接受额外的&#xff0c;字段特…

STM8S系列基于IAR标准外设printf输出demo

STM8S系列基于IAR标准外设printf输出demo&#x1f4cc;STM8S/A标准外设库&#xff08;库版本V2.3.1&#xff09;&#x1f4cd;官网标准外设库&#xff1a;https://www.st.com/zh/embedded-software/stsw-stm8069.html ⛳注意事项 &#x1f6a9;在内存空间比较有限的情况下&am…

.vue 组件打包成 .js

.vue 组件打包成 .js *** 所有的内容 cli 官网都有 *** *** https://cli.vuejs.org/zh/guide/build-targets.html *** 所有的内容 cli 官网都有&#xff1a; https://cli.vuejs.org/zh/guide/build-targets.html 准备 几个 .vue 组件文件 import Main from ./components/Ma…

MySQL InnoDB存储引擎锁与事务实现原理解析(未完成)

InnoDB MySQL存储引擎是基于表的&#xff0c;也就是说每张表可以选择不同的存储引擎。 InnoDB存储引擎的表是索引组织的&#xff0c;也就是数据即索引。 存储引擎文件 InnoDB引擎会包含RedoLog重做日志文件和TableSpace表空间文件。 表空间文件 默认表空间文件&#xff08…

Win32 ListBox控件

Win32 ListBox控件 创建ListBox控件 创建窗口函数 HWND CrateWindowEx(DWORD dwExStyle , // 窗口的扩展风格,基本没用LPCTSTR lpClassName, // 已经注册的窗口类名称LPCTSTR lpWindowName, // 窗口标题栏的名字DWORD dwStyle, // 窗口的基本风格int x, // 左上角水平坐标int …

HashMap ConcurrentHashMap介绍

目录 HashMap 数据结构 重要成员变量 Jdk7-扩容死锁分析 单线程扩容 多线程扩容 Jdk8-扩容 ConcurrentHashMap 数据结构 并发安全控制 源码原理分析 重要成员变量 协助扩容helpTransfer 扩容transfer 总结 CopyOnWrite机制 源码原理 HashMap 数据结构 数组…

【题解】百度2020校招Web前端工程师笔试卷(第一批):单选题、多选题

题目来源 若有错误请指正&#xff01; 单选 1 分页存储管理将进程的逻辑地址空间分成若干个页&#xff0c;并为各页加以编号&#xff0c;从0开始&#xff0c;若某一计算机主存按字节编址&#xff0c;逻辑地址和物理地址都是32位&#xff0c;页表项大小为4字节&#xff0c;若…

01.Java的安装

1.JDK&JREJDK : Java SE Development Kit--Java开发工具JRE : Java Runtime Environment--Java运行环境Java编程&#xff0c;需要安装JDK;如果仅仅是运行一款Java程序则只需要运行JREJava的安装包分为两类&#xff1a;一类是JRE--是一个独立的Java运行环境&#xff1b; 一类…

卷积神经网络中的padding理解

前言 在进行卷积神经网络中&#xff0c;经常用到padding&#xff0c;padding在卷积神经网络中起到什么样的作用呢&#xff0c;又是如何发挥作用的呢&#xff1f;本文就此谈下自己看法。 代码中的显示 model.add(Conv2D(filters 32,kernel_size [3,3],strides [1,1],padd…

基于SpringCloud的微服务架构学习笔记(2)注册中心Eureka和负载均衡Ribbon

1. 7 Eureka注册中心 1.7.1 远程调用的问题 地址信息获取&#xff1a;服务消费者如何获取服务提供者的地址信息&#xff08;不能每次都写死&#xff09;&#xff1a; URL&#xff1a;http://localhost:8081/user/"order.getUserId()多选一&#xff1a;如果有多个服务提供…

HBase存储结构、基本架构和shell操作

文章目录一、HBase简介1.1、HBase定义1.2、HBase的存储结构1.3、HBase基本架构二、HBase Shell操作2.1、基本操作2.2、namespace2.3、DDL2.4、DML一、HBase简介 1.1、HBase定义 HBase是一个开源的分布式NoSQL数据库&#xff0c;它是Apache Hadoop项目的一部分&#xff0c;使用…

代码随想录算法训练营 day56 | 动态规划 647. 回文子串 516.最长回文子序列

day56647 回文子串1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3&#xff0c;dp数组如何初始化4.确定遍历顺序5.举例推导dp数组516 最长回文子序列1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化…