Vue Object.defineProperty发布订阅模式架构详解

本文主要是介绍Vue Object.defineProperty发布订阅模式架构详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Object.defineProperty发布订阅模式架构详解

1、 Object.defineProperty定义
Object.defineProperty(Object,prop,descriptor)

参数:
obj:必需,目标对象
prop:必需,目标对象 需获取或者修改的属性的名字
descriptor:必需,目标对象 属性所拥有的特性

简单的实例:

let data = {}// 采用中间变量去改变值_name = 1Object.defineProperty(data,'name',{get(){console.log("你访问了name属性");return _name},set(row){console.log("你设置修改了name属性,最新值为"+row);_name = row}})

输出
在这里插入图片描述

Object.defineProperty 属性详解:
  • writable:是否可以被重写,true可以重写,false不能重写,默认为false。
  • enumerable:是否可以被枚举(使用for…in或Object.keys( ))。设置为true可以被枚举;设置为false,不能被枚举。默认为false。
  • value:值可以使任意类型的值,默认为undefined
  • configurable:是否可以删除目标属性或是否可以再次修改属性的特性(writable, configurable, enumerable)。设置为true可以被删除或可以重新设置特性;设置为false,不能被可以被删除或不可以重新设置特性。默认为false。

一个简单的实例

let data= {}; 
Object.defineProperty(data, 'name', writable: true || false,configurable: true || false,enumerable: true || false,value:'gjf'
});

当然也还有我们上面存取器描述(set和get)

注意:当使用了getter或setter方法,不允许使用writable和value这两个属性
如果一起使用将会报错


Object.defineProperty更加通用的劫持方案
 // vue2 中的 Object.defineProperty 劫持的属性  vue3中的 porxy 劫持的是一个对象整体+惰性处理// 更加通用的劫持方案使用// 在vue开发中,都是提前把数据放到data配置中,// data(){//     return{//         name:"cp",//         height:120//     }// }let data={name:'cp',height:100,age:20}// 一般情况下,响应式数据很多,并且对象的属性很多,不止一个,// 对象的遍历方法Object.keys,遍历出来的对象是一个数组,所以我们可以使用foreachObject.keys(data).forEach(item=>{console.log(data[item]);defineReactive(data,item,data[item])// item代表data的每一个对象的属性// data[item]代表每一个对象属性的value// data是一个原对象// 处理每一个对象的key转变成响应式})// 把对象的属性改成get和set的形式// 1、函数定义形参相当于在内部申明了和形参名字对应的变量,并且初始值为undefined// 2、函数调用传入的实参的时候,相当于给内部申明好的变量做了赋值操作(首次遍历举例)// 3、defineReactive函数调用完毕,本应该内部遍历都会被回收,但是如果内部有其他函数使用了当前变量形成了闭包,不会被收回// 4、内部由于有其他方法引用了val属性,所以defineReactive函数的执行并不会导致val变量的销毁,会一直常驻内存// 5、由于闭包的特性,每一个传入下来的val都会常驻内存,相当于一个中间变量,目的是为了set和get的联动function defineReactive(data,item,val) {// 进行转换操作Object.defineProperty(data,item,{get(){console.log("你访问了属性"+item);return val},set(row){console.log("你修改了属性"+item+",最新值为"+row);val = row},})}// 形成了独立的作用域,互不干扰
实现 M->V的命令式操作
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>object.defineProperty</title>
</head><body><div id="app"><p v-text="name"></p></div>
</body>
<script>// 1、准备数据let data = {name: "1"}document.querySelector("#app p").innerHTML = data.name// 2、将数据转换为响应式(数据发生改变之后操作我们的视图)Object.keys(data).forEach(item => {defineReactive(data, item, data[item])})function defineReactive(data, item, val) {Object.defineProperty(data, item, {get() {console.log("你访问了属性" + item);return val},set(row) {if(row  === val){return}console.log("你修改了属性" + item + ",最新值为" + row);val = row// 这里我们把最新的值反应到视图中 关键位置// 核心就是操作DOM 把最新的值设置上去document.querySelector("#app p").innerHTML = val},})}
</script>
</html>

命令式操作就是操作了DOM

实现 M->V的 声明式的指令版本 (v-text)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>object.defineProperty</title>
</head><body><div id="app"><p v-text="name"></p></div>
</body>
<script>let data = {name: 'a',}// 实现指令的核心:不管是指令也好,还是差值表达式也好,他们都是数据和视图之间建立关联的“标识”,所以本质就是通过一定的手段找到标识的Dom元素,// 然后把数据放上去,每当数据发生变化,就更新执行一遍放置数据的操作function compile() {let app = document.querySelector('#app')// 拿到app下的所有节点const childNodes = app.childNodes //所以的节点,包括文本节点,标签节点等// nodetype是节点标识 我们通过nodetype来筛选,标签节点为1 文本节点为3childNodes.forEach(item =>{if(item.nodeType === 1){// 拿到标签属性const attrs = item.attributesArray.from(attrs).forEach(arrts =>{const nodeName = arrts.nodeNameconst nodeValue = arrts.nodeValue// console.log(nodeName,nodeValue);// nodeName -> v-text 就是我们需要找到的标识// nodeValue -> name  就我我们数据中的Keyif(nodeName === "v-text"){item.innerText = data[nodeValue]}})}})}Object.keys(data).forEach(item => {defineReactive(data, item, data[item])})function defineReactive(data, item, val) {Object.defineProperty(data, item, {get() {console.log("你访问了属性" + item);return val},set(row) {if(row  === val){return}console.log("你修改了属性" + item + ",最新值为" + row);val = rowcompile()},})}compile()
</script>
</html>
实现V->M(视图反应到数据)

我们在上面的代码基础上添加了(v-model)标签

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><div id="app"><p v-text="name"></p><input type="text" v-model="name"></div>
</body>
<!-- V->M  v-model() M->V  V->M-->
<script>let data = {name: 'a',}function compile() {let app = document.querySelector('#app')const childNodes = app.childNodes childNodes.forEach(item =>{if(item.nodeType === 1){const attrs = item.attributesArray.from(attrs).forEach(arrts =>{const nodeName = arrts.nodeNameconst nodeValue = arrts.nodeValueif(nodeName === "v-text"){item.innerText = data[nodeValue]}if(nodeName ==="v-model"){item.value = data[nodeValue]// 监听input事件,在数据回调中拿到最新的数值,赋值给绑定的属性item.addEventListener('input',(e)=>{let newValue = e.target.value// 反向赋值data[nodeValue] = newValue})}})}})}Object.keys(data).forEach(item => {defineReactive(data, item, data[item])})function defineReactive(data, item, val) {Object.defineProperty(data, item, {get() {console.log("你访问了属性" + item);return val},set(row) {if(row  === val){return}console.log("你修改了属性" + item + ",最新值为" + row);val = rowcompile()},})}compile()
</script>
</html>

这样就实现了双向绑定的效果
不过这样的架构有一点问题,就是我们修改一个数据的时候,没有更改值的属性也会进行更改,如下我们多添加一个值

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><div id="app"><p v-text="name"></p><div v-text="name"></div><span v-text="age"></span><input type="text" v-model="age"></div>
</body>
<script>let data = {name: 'a',age: 25}function compile() {let app = document.querySelector('#app')const childNodes = app.childNodeschildNodes.forEach(item => {if (item.nodeType === 1) {const attrs = item.attributesArray.from(attrs).forEach(arrts => {const nodeName = arrts.nodeNameconst nodeValue = arrts.nodeValueif (nodeName === "v-text") {console.log('修改了'+nodeValue);item.innerText = data[nodeValue]}if (nodeName === "v-model") {item.value = data[nodeValue]item.addEventListener('input', (e) => {let newValue = e.target.valuedata[nodeValue] = newValue})}})}})}Object.keys(data).forEach(item => {defineReactive(data, item, data[item])})function defineReactive(data, item, val) {Object.defineProperty(data, item, {get() {console.log("你访问了属性" + item);return val},set(row) {if (row === val) {return}console.log("你修改了属性" + item + ",最新值为" + row);val = rowcompile()},})}compile()
</script></html>

在这里我只在输入框中修改了age属性
在这里插入图片描述
但是他的name属性打印出了修改,所以我们要进行优化代码,利用订阅模式进行针对性的修改

利用订阅模式优化架构

需要理解订阅模式可以访问这个博客:链接: 发布订阅模式与观察者模式.
我们先了解一下浏览器的一对一和一对多的事件

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>发布订阅模式</title>
</head><body><button id="btn">click me!</button>
</body>
<script>const btn = document.querySelector("#btn")// 一对一// btn.onclick = function () {//     console.log("点击了按钮");// }// btn.onclick = function () {//     console.log("今天好热")// }// 一对多   实际上就是使用了发布订阅模式// btn.addEventListener("click",()=>{//     console.log("点击了按钮");// })// btn.addEventListener("click",()=>{//     console.log("今天好热");// })// 浏览器的实现过程分析:// 1、浏览器实现了一个方法 叫做:addEventlistener// 2、这个方法接收2个参数 参数1代表 事件类型 参数2代表回调函数// 3、为了实现一对多架构// {//     click:['回调函数1','回调函数2',...]// }// 4、当鼠标点击的时候,通过事件类型click去数据结构中找到存放了所有相关回调函数的数组,然后都遍历,都执行一遍,从而实现一对多/*实现一个自己的自定义事件收集和触发架构1、定义一个方法,接收两个参数,参数1为事件名称 参数2为回调函数只要方法一执行 就收集回调函数到对应的位置上去*/const map = {}function collect(eventName, fn) {//    如果当前map中已经初始化好了, click:[]//     就直接往里面push 如果没有初始化首次添加  就先进行初始化if (!map[eventName]) {map[eventName] = []}map[eventName].push(fn)}collect('cp',()=>{console.log("成功收集了cp");})collect('cp',()=>{console.log("再一次成功收集了cp");})console.log(map);// 以上完成了事件的收集工作,完成了一对多的存储架构// 事件触发函数// 模拟鼠标点击 主动通过程序去触发收集起来的事件// 需要通过事件名称 找到对应的回调函数数组 然后遍历执行function trigger (eventName){map[eventName].forEach(fn =>fn()    );}trigger('cp')
</script></html>

以上的js代码我们可以优化一下 把函数放到对象里面去

let Dep = {map: {},collect(eventName, fn) {if (!this.map[eventName]) { //this的指向为Depthis.map[eventName] = []}this.map[eventName].push(fn)},trigger(eventName) {this.map[eventName].forEach(fn => fn());}}

然后我们全部结合起来

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><div id="app"><p v-text="name"></p><div v-text="name"></div><span v-text="age"></span></div>
</body>
<script>let Dep = {map: {},collect(eventName, fn) {if (!this.map[eventName]) {this.map[eventName] = []}this.map[eventName].push(fn)},trigger(eventName) {this.map[eventName].forEach(fn => fn());}}let data = {name: 'a',age: 25}function compile() {let app = document.querySelector('#app')const childNodes = app.childNodeschildNodes.forEach(item => {if (item.nodeType === 1) {const attrs = item.attributesArray.from(attrs).forEach(arrts => {const nodeName = arrts.nodeNameconst nodeValue = arrts.nodeValueif (nodeName === "v-text") {console.log('修改了' + nodeValue);item.innerText = data[nodeValue]// 收集更新函数Dep.collect(nodeValue, () => {item.innerText = data[nodeValue]})}if (nodeName === "v-model") {item.value = data[nodeValue]item.addEventListener('input', (e) => {let newValue = e.target.valuedata[nodeValue] = newValueDep.collect(nodeValue, () => {item.innerText = data[nodeValue]})})}})}})}Object.keys(data).forEach(item => {defineReactive(data, item, data[item])})function defineReactive(data, item, val) {Object.defineProperty(data, item, {get() {console.log("你访问了属性" + item);return val},set(row) {if (row === val) {return}console.log("你修改了属性" + item + ",最新值为" + row);val = row// compile()// 在这里进行精准更新 ->找到data对应的属性名找到对应的更新函数依次执行Dep.trigger(item)},})}compile()console.log(Dep);
</script></html>

这样我们就完成了 看效果:
在这里插入图片描述

这篇关于Vue Object.defineProperty发布订阅模式架构详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/891593

相关文章

SpringIoC与SpringDI详解

《SpringIoC与SpringDI详解》本文介绍了Spring框架中的IoC(控制反转)和DI(依赖注入)概念,以及如何在Spring中使用这些概念来管理对象和依赖关系,感兴趣的朋友一起看看吧... 目录一、IoC与DI1.1 IoC1.2 DI二、IoC与DI的使用三、IoC详解3.1 Bean的存储

Spring Cloud之注册中心Nacos的使用详解

《SpringCloud之注册中心Nacos的使用详解》本文介绍SpringCloudAlibaba中的Nacos组件,对比了Nacos与Eureka的区别,展示了如何在项目中引入SpringClo... 目录Naacos服务注册/服务发现引⼊Spring Cloud Alibaba依赖引入Naco编程s依

C语言中的浮点数存储详解

《C语言中的浮点数存储详解》:本文主要介绍C语言中的浮点数存储详解,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、首先明确一个概念2、接下来,讲解C语言中浮点型数存储的规则2.1、可以将上述公式分为两部分来看2.2、问:十进制小数0.5该如何存储?2.3 浮点

大数据spark3.5安装部署之local模式详解

《大数据spark3.5安装部署之local模式详解》本文介绍了如何在本地模式下安装和配置Spark,并展示了如何使用SparkShell进行基本的数据处理操作,同时,还介绍了如何通过Spark-su... 目录下载上传解压配置jdk解压配置环境变量启动查看交互操作命令行提交应用spark,一个数据处理框架

MySQL中COALESCE函数示例详解

《MySQL中COALESCE函数示例详解》COALESCE是一个功能强大且常用的SQL函数,主要用来处理NULL值和实现灵活的值选择策略,能够使查询逻辑更清晰、简洁,:本文主要介绍MySQL中C... 目录语法示例1. 替换 NULL 值2. 用于字段默认值3. 多列优先级4. 结合聚合函数注意事项总结C

Java实现数据库图片上传功能详解

《Java实现数据库图片上传功能详解》这篇文章主要为大家详细介绍了如何使用Java实现数据库图片上传功能,包含从数据库拿图片传递前端渲染,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、前言2、数据库搭建&nbsChina编程p; 3、后端实现将图片存储进数据库4、后端实现从数据库取出图片给前端5、前端拿到

Windows命令之tasklist命令用法详解(Windows查看进程)

《Windows命令之tasklist命令用法详解(Windows查看进程)》tasklist命令显示本地计算机或远程计算机上当前正在运行的进程列表,命令结合筛选器一起使用,可以按照我们的需求进行过滤... 目录命令帮助1、基本使用2、执行原理2.1、tasklist命令无法使用3、筛选器3.1、根据PID

Nginx实现前端灰度发布

《Nginx实现前端灰度发布》灰度发布是一种重要的策略,它允许我们在不影响所有用户的情况下,逐步推出新功能或更新,通过灰度发布,我们可以测试新版本的稳定性和性能,下面就来介绍一下前端灰度发布的使用,感... 目录前言一、基于权重的流量分配二、基于 Cookie 的分流三、基于请求头的分流四、基于请求参数的分

MySql中的数据库连接池详解

《MySql中的数据库连接池详解》:本文主要介绍MySql中的数据库连接池方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mysql数据库连接池1、概念2、为什么会出现数据库连接池3、原理4、数据库连接池的提供商5、DataSource数据源6、DBCP7、C

Spring-AOP-ProceedingJoinPoint的使用详解

《Spring-AOP-ProceedingJoinPoint的使用详解》:本文主要介绍Spring-AOP-ProceedingJoinPoint的使用方式,具有很好的参考价值,希望对大家有所帮... 目录ProceedingJoinPoijsnt简介获取环绕通知方法的相关信息1.proceed()2.g