简单实现vue2响应式原理

vue2 在实现响应式时,是根据 object.defineProperty() 这个实现的,vue3 是通过 Proxy 对象实现,但是实现思路是差不多的,响应式其实就是让 函数和数据产生关联,在我们对数据进行修改的时候,可以执行相关的副作用函数来保证数据的响应式。

首先介绍一下 Object.defineProperty()  

Object.defineProperty()  

对象中存在的属性描述符有两种主要类型:数据描述符和访问器描述符。数据描述符是一个具有可写或不可写值的属性。访问器描述符是由 getter/setter 函数对描述的属性。描述符只能是这两种类型之一,不能同时为两者。

数据描述符

  • configurable: 如果为 true 表示可以再次修改该属性的属性描述符,同时该属性也能从对应的对象上被删除,默认为 false ,通俗来点讲就是,为 true 时,对于该对象指定了一次Object.defineProperty() 后续就不能修改属性描述符所代表的值,但是getter/setter 可以修改
  • writable: 如果为 true 表示这个属性运行被写入值,也就是修改,默认为false
  • value: 该对象的对应属性的原始值

上面只针对讲了部分数据描述符,访问描述符就是普通的 getter/setter 函数

使用:

然后这个还可以这样使用:

有人会问,那为什么要这样写,而不是直接在属性上修改,我们取上面的 age 属性的值的时候,就会调用到这里面的 set 函数,如果下面这样写:

栈溢出,因为我们一直不停地调用 set 函数,所以导致了这个情况,所以我们需要借助临时变量。

了解上述之后,我们来看看今天的主题

实现简单的响应式

首先这是我们的界面:

html 代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue</title>
    <link rel="stylesheet" href="./index.css">
</head>
<body>
    <div class="box">
        <div>
            <span>姓:</span>
            <span class="lastName"></span>
        </div>
        <div>
            <span>名:</span>
            <span class="firstName"></span>
        </div>
        <div>
            <span>年龄:</span>
            <span class="age"></span>
        </div>
    </div>
    <input type="text" class="nameInput" placeholder="请输入姓名">
    <input type="datetime-local" class="ageInput">
    <script src="./index.js"></script>
</body>
</html>

css 代码:

.box{
    padding: 30px;
    border-radius: 20px;
    width: 400px;
    margin: 40px auto;
    background-image: linear-gradient(to top, #fbc2eb 0%, #a6c1ee 100%);
    -webkit-border-radius: 20px;
    -moz-border-radius: 20px;
    -ms-border-radius: 20px;
    -o-border-radius: 20px;
    color: white;
    font-size: 20px;
    font-weight: bold;
    line-height: 50px;
}

js 代码:


let doms = {
    lastNameDom: document.querySelector('.lastName'),
    firstNameDom: document.querySelector('.firstName'),
    ageDom: document.querySelector('.age'),
    nameInput: document.querySelector('.nameInput'),
    ageInput: document.querySelector('.ageInput')
}

let obj = {
    name: '李泽言',
    age: "2000-1-1"
}

doms.nameInput.addEventListener('input', (e) => {
    console.log(e.target.value)
    obj.name = e.target.value
})
doms.ageInput.addEventListener('change', (e) => {
    obj.age = e.target.value
})


function getFirstName() {
    doms.firstNameDom.textContent = obj.name.substring(1)
}

function getLastName() {
    doms.lastNameDom.textContent = obj.name[0]
}

function getAge() {
    doms.ageDom.textContent = (new Date().getFullYear()) - (new Date(obj.age).getFullYear())
}


以上代码实现了:通过 obj 的 name 属性来获取姓和名 以及 obj 的 age(其实我应该写 birth 的,不要在意这个)来获取年龄。

然后分别调用 getFirstName() 、getLastName()、getAge()

于是我们可以得到一个上述页面,此时我们修改数据,页面上的数据不会因为这个而修改。

我们要实现的就是根据给出的对象,实现对这个对象响应式。

我们先定义一个 obeserve 函数,并且在定义好 obj 后执行这个函数。

function obeserve(obj) {

   //需要让里面的属性和上述函数产生依赖
}

结合上面给出的这个示例,我们可以这样写

function obeserve(obj) {

    Object.defineProperty(obj,'',{
        get:function(){

        },
        set:function(val){

        }
    })

    
}

但是 第二个参数是你所需要访问的属性,我们是希望这个对象的所以属性都需要实现响应式。

所以我们使用 in 来遍历这个对象所有的属性

因此有如下代码:

function obeserve(obj) {

    for (const key in obj) {
        console.log(key)
        let interval = obj[key];
        
        Object.defineProperty(obj, key, {
            get: function () {
               return interval
            },
            set: function (val) {
                interval = val
            }
        })
    }
}

上面我们使用了 interval 这个临时变量来实现了,我们现在访问相关属性,可以正确的拿到值(至于为什么需要借助其他的变量来实现,可看我上面的阐述)

现在我们有一个想法,就是当我们在进行设置相关属性的值,我们希望 设置好值后,我们能执行与这个值所有关的函数。

那么这个函数我们从那里知道?我该如何知道哪个函数使用了,就是 get 函数,当我们使用了这个属性,一定会在 get 函数这里留下 踪迹。

所以我们目前的代码是:

function obeserve(obj) {

    for (const key in obj) {
        console.log(key)
        let interval = obj[key];
        let func = []
        Object.defineProperty(obj, key, {
            get: function () {
                 func.push(xxx函数) 
                return interval
            },
            set: function (val) {
                interval = val
                //这里使用了 forEach 来遍历这个存储所有与该属性相关的函数,拿出来执行
                func.forEach(value => value())
                // 也可以这样写
                // for(let i=0;i<func.length;i++){
                //     func[i]()
                // }
            }
        })
    }
}

但是目前会存在一个问题,因为我们很有可能在一个函数里面使用了俩次该属性,会导致我们重复记录该函数,因为本来这个函数只应该执行一次即可。

于是我们需要做出修改,你可以使用 set 容器,当然也可以使用 数组的 includes 函数来判断是否重复。

function obeserve(obj) {

    for (const key in obj) {
        console.log(key)
        let interval = obj[key];
        let func = []
        Object.defineProperty(obj, key, {
            get: function () {
                if (!func.includes(xxx函数)) 
                    { func.push(xxx函数) }
                return interval
            },
            set: function (val) {
                interval = val
                func.forEach(value => value())
            }
        })
    }
}

好,目前我们只需要解决一个问题,就是我们如何知道这个 xxx 函数到底是什么。或者说我们怎么知道当前调用的是那个函数,这里用到了一个非常巧妙的思维。我们定义一个变量,挂载在 window 这个对象的变量,就叫 window.__activeFun,因为定义在 window 上就可以在同一个页面任何地方都可以拿到,即使我们后面需要把这个 obeserve 独立封装起来使用,也不影响。

我们给他赋值为 null 

在我们执行某些函数时,我们做这么一个操作:

//初始值
window.__activeFun = null

window.__activeFun = getFirstName
getFirstName()
window.__activeFun = null

 于是 obeserve 函数就应该变成这样

function obeserve(obj) {

    for (const key in obj) {
        console.log(key)
        let interval = obj[key];
        let func = []
        Object.defineProperty(obj, key, {
            get: function () {
                //判断 这个函数是否为null或者已经存在
                if (window.__activeFun !== null && !func.includes(window.__activeFun)) { func.push(window.__activeFun) }
                return interval
            },
            set: function (val) {
                interval = val
                func.forEach(value => value())
            }
        })
    }
}

但是考虑到函数的可复用性,前面我们所写的 赋值给 window.__activeFun 可以再修改一下

封装成一个函数

window.__activeFun = null
function addToRun(func) {
    window.__activeFun = func
    func()
    window.__activeFun = null
}

于是我们在执行函数时,不直接执行原本的函数

而是这样

addToRun(getFirstName)
addToRun(getLastName)
addToRun(getAge)

将所有的函数都放入这个 addToRun 函数里面走一遭

于是我们就完成了响应式的一个简单应用

当然真实场景会比这个更复杂,我们需要考虑到 浅响应,深响应以及简单类型数据,和数组集合这类数据。

 完整 js 代码:


let doms = {
    lastNameDom: document.querySelector('.lastName'),
    firstNameDom: document.querySelector('.firstName'),
    ageDom: document.querySelector('.age'),
    nameInput: document.querySelector('.nameInput'),
    ageInput: document.querySelector('.ageInput')
}

let obj = {
    name: '李泽言',
    age: "2000-1-1"
}

doms.nameInput.addEventListener('input', (e) => {
    console.log(e.target.value)
    obj.name = e.target.value
})
doms.ageInput.addEventListener('change', (e) => {
    obj.age = e.target.value
})

obeserve(obj)

function getFirstName() {
    doms.firstNameDom.textContent = obj.name.substring(1)
}

function getLastName() {
    doms.lastNameDom.textContent = obj.name[0]
}

function getAge() {
    doms.ageDom.textContent = (new Date().getFullYear()) - (new Date(obj.age).getFullYear())
}

window.__activeFun = null
function addToRun(func) {
    window.__activeFun = func
    func()
    window.__activeFun = null
}

function obeserve(obj) {

    for (const key in obj) {
        console.log(key)
        let interval = obj[key];
        let func = []
        Object.defineProperty(obj, key, {
            get: function () {
                if (window.__activeFun !== null && !func.includes(window.__activeFun)) { func.push(window.__activeFun) }
                return interval
            },
            set: function (val) {
                interval = val
                func.forEach(value => value())
            }
        })
    }
}

addToRun(getFirstName)
addToRun(getLastName)
addToRun(getAge)

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

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

相关文章

论文解析:EdgeToll:基于区块链的异构公共共享收费系统(2019,IEEE INFOCOM 会议);layer2 应对:频繁小额交易,无交易费

目录 论文解析:EdgeToll:基于区块链的异构公共共享收费系统(2019,IEEE INFOCOM 会议) 核心内容概述 核心创新点原理与理论 layer2 应对:频繁小额交易,无交易费 论文解析:EdgeToll:基于区块链的异构公共共享收费系统(2019,IEEE INFOCOM 会议) 核心内容是介绍了一个…

基于python Django的boss直聘数据采集与分析预测系统,爬虫可以在线采集,实时动态显示爬取数据,预测基于技能匹配的预测模型

本系统是基于Python Django框架构建的“Boss直聘”数据采集与分析预测系统&#xff0c;旨在通过技能匹配的方式对招聘信息进行分析与预测&#xff0c;帮助求职者根据自身技能找到最合适的职位&#xff0c;同时为招聘方提供更精准的候选人推荐。系统的核心预测模型基于职位需求技…

SemiDrive E3 硬件设计系列---唤醒电路设计

一、前言 E3 系列芯片是芯驰半导体高功能安全的车规级 MCU&#xff0c;对于 MCU 的硬件设计部分&#xff0c;本系列将会分模块进行讲解&#xff0c;旨在介绍 E3 系列芯片在硬件设计方面的注意事项与经验&#xff0c;本文主要讲解 E3 硬件设计中唤醒电路部分的设计。 二、RTC 模…

Leetcode198. 打家劫舍(HOT100)

代码&#xff1a; class Solution { public:int rob(vector<int>& nums) {int n nums.size();vector<int> f(n 1), g(n 1);for (int i 1; i < n; i) {f[i] g[i - 1] nums[i - 1];g[i] max(f[i - 1], g[i - 1]);}return max(f[n], g[n]);} }; 这种求…

一文探究48V新型电气架构下的汽车连接器

【哔哥哔特导读】汽车电源架构不断升级趋势下&#xff0c;48V系统是否还有升级的必要&#xff1f;48V新型电气架构将给连接器带来什么改变&#xff1f; 在插混和纯电车型逐渐普及、800V高压平台持续升级的当下&#xff0c;48V技术还有市场吗? 这个问题很多企业的回答是不一定…

React学习05 - redux

文章目录 redux工作流程redux理解redux理解及三个核心概念redux核心apiredux异步编程react-redux组件间数据共享 纯函数redux调试工具项目打包 redux工作流程 redux理解 redux是一个专门用于状态管理的JS库&#xff0c;可以用在react, angular, vue 等项目中。在与react配合使…

2024年11月最新 Alfred 5 Powerpack (MACOS)下载

在现代数字化办公中&#xff0c;我们常常被繁杂的任务所包围&#xff0c;而时间的高效利用成为一项核心需求。Alfred 5 Powerpack 是一款专为 macOS 用户打造的高效工作流工具&#xff0c;以其强大的定制化功能和流畅的用户体验&#xff0c;成为众多效率爱好者的首选。 点击链…

batchnorm与layernorn的区别

1 原理 简单总结&#xff1a; batchnorn 和layernorm是在不同维度上对特征进行归一化处理。 batchnorm在batch这一维度上&#xff0c; 对一个batch内部所有样本&#xff0c; 在同一个特征通道上进行归一化。 举个例子&#xff0c; 假设输入的特征图尺寸为16x224x224x256&…

【c++丨STL】stack和queue的使用及模拟实现

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C、STL 目录 前言 一、什么是容器适配器 二、stack的使用及模拟实现 1. stack的使用 empty size top push和pop swap 2. stack的模拟实现 三、queue的…

ApiChain 从迭代到项目 接口调试到文档生成单元测试一体化工具

项目地址&#xff1a;ApiChain 项目主页 ApiChain 简介 ApiChain 是一款类似 PostMan 的接口网络请求与文档生成软件&#xff0c;与 PostMan 不同的是&#xff0c;它基于 项目和迭代两个视角管理我们的接口文档&#xff0c;前端和测试更关注版本迭代中发生变更的接口编写代码…

力扣面试题 - 24 插入

题目&#xff1a; 给定两个整型数字 N 与 M&#xff0c;以及表示比特位置的 i 与 j&#xff08;i < j&#xff0c;且从 0 位开始计算&#xff09;。 编写一种方法&#xff0c;使 M 对应的二进制数字插入 N 对应的二进制数字的第 i ~ j 位区域&#xff0c;不足之处用 0 补齐…

网络安全,文明上网(4)掌握网络安全技术

前言 在数字化时代&#xff0c;个人信息和企业数据的安全变得尤为重要。为了有效保护这些宝贵资产&#xff0c;掌握一系列网络安全技术是关键。 核心技术及实施方式 1. 网络监控与过滤系统&#xff1a; 这些系统构成了网络防御体系的基石&#xff0c;它们负责监控网络通信&…

[开源] SafeLine 好用的Web 应用防火墙(WAF)

SafeLine&#xff0c;中文名 “雷池”&#xff0c;是一款简单好用, 效果突出的 Web 应用防火墙(WAF)&#xff0c;可以保护 Web 服务不受黑客攻击 一、简介 雷池通过过滤和监控 Web 应用与互联网之间的 HTTP 流量来保护 Web 服务。可以保护 Web 服务免受 SQL 注入、XSS、 代码注…

ELK8.15.4搭建开启安全认证

安装 Elastic &#xff1a;Elasticsearch&#xff0c;Kibana&#xff0c;Logstash 另外安装一个收集器filebeat 通过二进制安装包进行安装 创建一个专门放elk目录 mkdir /elk/ mkdir /elk/soft下载 es 、kibana、Logstash、filebeat二进制包 cd /elk/softwget https://art…

excel版数独游戏(已完成)

前段时间一个朋友帮那小孩解数独游戏&#xff0c;让我帮解&#xff0c;我看他用电子表格做&#xff0c;只能显示&#xff0c;不能显示重复&#xff0c;也没有协助解题功能&#xff0c;于是我说帮你做个电子表格版的“解题助手”吧&#xff0c;不能直接解题&#xff0c;但该有的…

Linux上安装单机版Kafka

1、上传Kafka安装包至Linux并进行解压 kafka_2.12-1.1.1.tgz 链接&#xff1a;https://pan.baidu.com/s/1i41RKHlCbp0q2xQ1PEgT5g 提取码&#xff1a;vofa 将安装包解压 tar -zxvf kafka_2.12-1.1.1.tgz 2、修改kafka配置 vi config/server.properties 只修改绑定 IP &#…

Kafka 消费者全面解析:原理、消费者 API 与Offset 位移

Kafka&#xff1a;分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析&#xff1a;从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…

AUTOSAR - 接口

Application Port Interface&#xff0c;Service Port Interface&#xff0c;除了IS-SERVICE字段外&#xff0c;其余都相同。 ClientServer 支持IsService <CLIENT-SERVER-INTERFACE UUID"523b6eb5-6814-4b10-893e-de3aa9b68b90"><SHORT-NAME>app_cs_1&…

Android Gradle自定义任务在打包任务执行完成后执行cmd命令

背景 在每次打包之后需要做某事&#xff0c;例如每次打包后我都会安装某个目录下的一个apk。这个apk是通过一堆shell命令过滤得到一个apk的地址&#xff0c;然后把执行的几个shell命令何必成一个alias指令&#xff0c;在打包后只需要执行alias指令实现功能。当然也可以直接写在…

2023AE软件、Adobe After Effects安装步骤分享教程

2023AE软件是一款由Adobe公司开发的视频编辑软件&#xff0c;也被称为Adobe After Effects。它在广告、电影、电视和网络视频等领域广泛应用&#xff0c;用于制作动态图形、特效、合成和其他视觉效果。该软件支持多种视频和音频文件格式&#xff0c;具有丰富的插件和预设&#…