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

相关文章

vue基于ElementUI动态设置表格高度的3种方法

《vue基于ElementUI动态设置表格高度的3种方法》ElementUI+vue动态设置表格高度的几种方法,抛砖引玉,还有其它方法动态设置表格高度,大家可以开动脑筋... 方法一、css + js的形式这个方法需要在表格外层设置一个div,原理是将表格的高度设置成外层div的高度,所以外层的div需要

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

Java中Object类的常用方法小结

《Java中Object类的常用方法小结》JavaObject类是所有类的父类,位于java.lang包中,本文为大家整理了一些Object类的常用方法,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. public boolean equals(Object obj)2. public int ha

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

详解如何在React中执行条件渲染

《详解如何在React中执行条件渲染》在现代Web开发中,React作为一种流行的JavaScript库,为开发者提供了一种高效构建用户界面的方式,条件渲染是React中的一个关键概念,本文将深入探讨... 目录引言什么是条件渲染?基础示例使用逻辑与运算符(&&)使用条件语句列表中的条件渲染总结引言在现代

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

SQL注入漏洞扫描之sqlmap详解

《SQL注入漏洞扫描之sqlmap详解》SQLMap是一款自动执行SQL注入的审计工具,支持多种SQL注入技术,包括布尔型盲注、时间型盲注、报错型注入、联合查询注入和堆叠查询注入... 目录what支持类型how---less-1为例1.检测网站是否存在sql注入漏洞的注入点2.列举可用数据库3.列举数据库