当前位置: 首页 > news >正文

手写Vue3响应式数据原理

Vue3响应式数据

  • 前言
  • 一、proxy是什么?
    • 1.1 proxy基本使用
  • 二、实现最基本的reactive函数
  • 三、实现基本响应式系统
  • 四、完善基本响应式系统
    • 4.1 执行每一个副作用函数
    • 4.2 实现依赖收集
      • 4.2.1 基本实现
    • 4.3 改进桶结构
  • 五、相关面试题
    • 1.Object.defineProperty 和 Proxy 的区别?
    • 2.vue2.0 和 vue3.0 有什么区别? 双向绑定更新?
    • 3.Vue 是如何实现数据双向绑定的?
    • 4.介绍下 Set、Map、WeakSet 和 WeakMap的区别?
    • 5.Vue2.0为什么不能检查数组的变化?该怎么解决?
      • 解决方案


前言

我们想要对一个对象数据进行处理,从而实现更改dom。但如何更改对一个对象数据进行更改呢?

vue2 的双向数据绑定是利⽤ES5 的⼀个 API ,Object.defineProperty()对数据进⾏劫持 结合 发布订阅模式的⽅式来实现的。

vue3 中使⽤了 ES6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀个对象都包⼀层 Proxy,通过 Proxy 监听属性的变化,从⽽ 实现对数据的监控。

这⾥是相⽐于vue2版本,使⽤proxy的优势如下:

    1. defineProperty只能监听某个属性,不能对全对象监听可以省去for…in…、闭包等内容来提升效率(直接绑定整个对象即可)
    1. 监听数组,不⽤再去单独的对数组做特异性操作,通过Proxy可以直接拦截所有对象类型数据的操作,完美⽀持对数组的监听。

我们想要知道如何实现Vue3响应式数据,就要知道proxy这个概念。


一、proxy是什么?

Proxy(代理)是一种计算机网络技术,其作用是充当客户端和服务器之间的中间人,转发网络请求和响应。当客户端发送请求时,代理服务器会接收并转发请求到目标服务器,然后将服务器返回的响应转发给客户端。

相当于明星和经纪人,想要找明星办事,需要找他的经纪人,明星的事都交给经纪人做。明星就是源对象,经纪人就相当于proxy。

proxy用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

1.1 proxy基本使用

// 定义一个源对象
let obj = {name: 'qx',age: 24
}
// 实现一个Proxy,传入要代理的对象和get和set方法
const proxy =  new Proxy(obj, {// get中返回代理对象的,target代表源对象(也是上面的obj),key代表obj中每个属性get(target, key) {return target[key];},// set中返回代理对象的,target代表源对象(也是上面的obj),key代表obj中每个属性,value是修改的新值set(target, key, value) {target[key] = valuereturn true}
})
console.log(proxy)obj.name = 'xqx'
// 现在打印的是修改后的proxy,看看会变成什么样?  已经修改好了
console.log(proxy)

在这里插入图片描述

二、实现最基本的reactive函数

reactive 用于创建一个响应式对象,该对象可以包含多个属性和嵌套属性。当使用 reactive 创建响应式对象时,返回的对象是一个代理对象,该对象具有与原始对象相同的属性,并且任何对代理对象属性的更改都将触发组件的重新渲染。

既然我们已经知道reactive是个函数,并且返回的是一个代理对象,先把最基本的框架搭出来

function reactive(data) {return new Proxy(data, {get(target, key) {return target[key];},set(target, key, value) {target[key] = valuereturn true}})
}

看似已经完成了,但是当传入非对象时,却报错

提示这个对象是对象类型的,例如数组之类的,并只是{}这个。

const arr = true;
console.log(reactive(arr))

在这里插入图片描述
提示proxy要传入一个对象,所以需要先判断是不是对象

function reactive(data) {//判断是不是对象,null也是object要排除if(typeof data === Object && data !== null) return return new Proxy(data, {get(target, key) {return target[key];},set(target, key, value) {target[key] = valuereturn true}})
}

三、实现基本响应式系统

我们知道处理数据就是为了让视图更新,但一个系统离不开副作用函数。

副作用函数,顾名思义,会产生副作用的函数被称为副作用函数。通俗来说,就是这个函数可以影响其他的变量。

来看最基本的副作用函数

<div id="app"></div>
<script>let obj = {name: 'qx'}function effect(){app.innerText = obj.name}effect()
</script>

现在我们需要通过前面reactive函数来完善一个基本的响应式系统

<body><div id="app"></div><script>let obj = {name: 'qx',age: 24}function reactive(data) {if(typeof data === Object && data !== null) return return new Proxy(data, {get(target, key) {return target[key];},set(target, key, value) {target[key] = valuereturn true}})}const state = reactive({name:'xqx'});function effect(){app.innerText = state.name}effect()</script>
</body>

在这里插入图片描述
到现在一个最基本的响应式系统出现

四、完善基本响应式系统

如果多个副作用函数同时引用一个变量,我们需要当变量改变时,每一个副作用函数都要执行。

4.1 执行每一个副作用函数

可以把多个副作用函数放在一个列表里,在每次对对象操作时,执行proxy中的set方法时,对每一个副作用函数进行遍历。

<body><div id="app"></div><script>let obj = {name: 'qx'}let effectBucket = [];function reactive(data) {if(typeof data === Object && data !== null) return return new Proxy(data, {get(target, key) {return target[key];},set(target, key, value) {target[key] = valueeffectBucket.forEach(fn=>fn())return true}})}const state = reactive({name:'xqx'});function effect(){app.innerText = state.nameconsole.log('副作用函数1被执行')}effectBucket.push(effect)function effect1(){app.innerText = state.nameconsole.log('副作用函数2被执行')}effectBucket.push(effect1)state.name = 'zs'</script>
</body>

在这里插入图片描述

但是我们要是传两个同样的副作用函数怎么办。

function effect(){app.innerText = state.nameconsole.log('副作用函数1被执行')
}
effectBucket.push(effect)
effectBucket.push(effect)

在这里插入图片描述
发现列表里有两个重复的effect函数,如果列表很长,foreach也会浪费时间,那么大大浪费性能。es6有个Set数据结构可以帮助我们解决这个问题。

let effectBucket = new Set();const state = reactive({name:'xqx'});function effect(){app.innerText = state.nameconsole.log('副作用函数1被执行')
}
effectBucket.add(effect)  //添加两次
effectBucket.add(effect)function effect1(){app.innerText = state.nameconsole.log('副作用函数2被执行')
}
effectBucket.add(effect1)console.log(effectBucket)

我们把effect添加两次,看看结果是什么样的
在这里插入图片描述

4.2 实现依赖收集

前面我们只是对一个对象中的属性进行处理,如果多个属性都要更改呢?我们以上的操作会让每一个副作用函数都执行。

假设我们有这样一个结构

let obj = {name: 'qx',age:24}

我想改name属性时,只更新有name的副作用函数,不必把列表里所有副作用函数都更新。这就需要依赖收集。

4.2.1 基本实现

对每一个副作用函数进行一个保存,当调用副作用函数时,会执行proxy中的get方法,在get方法把当前副作用函数添加列表,就实现了当前依赖属性和副作用函数关联在一起。

具体实现步骤如下:

let obj = {name: 'qx',age:24}
let effectBucket = new Set();let activeEffect = null;   //1.保存当前的副作用函数状态function reactive(data) {if(typeof data === Object && data !== null) return return new Proxy(data, {get(target, key) {if(activeEffect != null){              //4. 将当前保存的副作用函数添加到副作用函数列表中effectBucket.add(activeEffect)  }return target[key];},set(target, key, value) {target[key] = valueeffectBucket.forEach(fn=>fn())return true}})
}
const state = reactive(obj);
function effectName(){console.log('副作用函数1被执行',state.name)
}
activeEffect = effectName()  // 2.将当前副作用函数赋值给activeEffect 
effectName()                 // 3.调用副作用函数,相当于访问proxy的get方法
activeEffect = null;         // 5.将副作用函数状态置空,给下一个副作用函数用function effectAge(){console.log('副作用函数2被执行',state.age)
}
activeEffect = effectAge()
effectAge()
activeEffect = null;state.name = 'zs'

在这里插入图片描述
再简化一下,对上面重复代码进行一个封装。调用的时候直接调封装后的方法

function registEffect(fn) {if (typeof fn !== 'function') return;activeEffect = fn();fn();activeEffect = null;
}

4.3 改进桶结构

Set结构像数组,只是能做到去重,并不能实现不同属性对应不同集合。需要我们改进成一个属性对应多个集合。

另一个数据结构Map出现在面前,它是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

创建一个这样的结构。

let a = {name: Set(fn,fn),age:Set(fn,fn)
}

在这里插入图片描述

let effectBucket = new Map();  //{name:Set(fn,fn),age:Set(fn,fn)}
let activeEffect = null;function reactive(data) {if (typeof data === Object && data !== null) returnreturn new Proxy(data, {get(target, key) {if (activeEffect !== null) {let deptSet;if(!effectBucket.get(key)){                       //没有得到key,说明没有添加过deptSet = new Set();            //重新创建一个集合effectBucket.set(key,deptSet);  //每次添加一个属性{name:Set(fn,fn)}结构}deptSet.add(activeEffect)}return target[key];},set(target, key, value) {target[key] = value//从副作用桶中依次取出每一个副作用函数执行let deptSet = effectBucket.get(key);if(deptSet){                   deptSet.forEach(fn => fn())}return true}})
}

继续封装收集依赖
get

function track(target, key) {if (!activeEffect) returnlet deptSet;if (!effectBucket.get(key)) { //没有得到key,说明没有添加过deptSet = new Set(); //重新创建一个集合effectBucket.set(key, deptSet);}deptSet.add(activeEffect)
}

set

function trigger(target, key) {let deptSet = effectBucket.get(key);if (deptSet) {deptSet.forEach((fn) => fn())}
}

在这里插入图片描述

function track(target, key) {if (!activeEffect) returnlet deptMap =effectBucket.get(key);if (!deptMap) { //没有得到key,说明没有添加过deptMap = new Map(); //重新创建一个集合effectBucket.set(target, deptMap);}let depSet = deptMap.get(key)if(!depSet){depSet = new Set()deptMap.set(key,depSet)}deptSet.add(activeEffect)
}function trigger(target, key) {let depMap = effectBucket.get(target)if(!depMap) returnlet deptSet = effectBucket.get(key);if (deptSet) {deptSet.forEach((fn) => fn())}
}

五、相关面试题

1.Object.defineProperty 和 Proxy 的区别?

  1. Proxy 可以直接监听对象而非属性;
  2. Proxy 可以直接监听数组的变化;
  3. Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等 是 Object.defineProperty 不具备的;
  4. Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty 只能遍历对象属性直接修改
  5. Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准 的性能红利
  6. Object.defineProperty 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题, 而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重 写

2.vue2.0 和 vue3.0 有什么区别? 双向绑定更新?

vue2 的双向数据绑定是利⽤ES5 的⼀个 API ,Object.defineProperty()对数据进⾏劫持 结合 发布订阅模式的⽅式来实现的。

vue3 中使⽤了 ES6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀个对象都包⼀层 Proxy,通过 Proxy 监听属性的变化,从⽽ 实现对数据的监控。

这⾥是相⽐于vue2版本,使⽤proxy的优势如下:

  1. defineProperty只能监听某个属性,不能对全对象监听 可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)

  2. 监听数组,不⽤再去单独的对数组做特异性操作,通过Proxy可以直接拦截所有对象类型数据的操作,完美⽀持对数组的监听。

获取props

vue2在script代码块可以直接获取props,vue3通过setup指令传递

API不同

Vue2使⽤的是选项类型API(Options API),Vue3使⽤的是合成型API(Composition API)

建立数据data

vue2是把数据放入data中,vue3就需要使用一个新的setup()方法,此方法在组件初始化构造得时候触发。

生命周期不同

vue2vue3
beforeCreatesetup() 开始创建组件之前,创建的是data和method
createdsetup()
beforeMountonBeforeMount 组件挂载到节点上之前执行的函数
mountedonMounted 组件挂载完成后执行的函数
beforeUpdateonBeforeUpdate 组件更新之前执行的函数
updatedonUpdated 组件更新完成之后执行的函数
beforeDestroyonBeforeUnmount 组件挂载到节点上之前执行的函数
destroyedonUnmounted 组件卸载之前执行的函数
activatedonActivated 组件卸载完成后执行的函数
deactivatedonDeactivated

关于v-if和v-for的优先级:

vue2 在一个元素上同时使用 v-if 和 v-for v-for会优先执行

vue3 v-if 总会优先于 v-for生效

vue2和vue3的diff算法

vue2

vue2 diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点 不同的地方,最后用patch记录的消息去局部更新Dom。

vue2 diff算法会比较每一个vnode,而对于一些不参与更新的元素,进行比较是有 点消耗性能的。

vue3

vue3 diff算法在初始化的时候会给每个虚拟节点添加一个patchFlags,patchFlags 就是优化的标识。

只会比较patchFlags发生变化的vnode,进行更新视图,对于没有变化的元素做静 态标记,在渲染的时候直接复用。

3.Vue 是如何实现数据双向绑定的?

Vue 数据双向绑定主要是指:数据变化更新视图

  • 输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。
  • Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化

Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

  • 第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

  • 第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

  • 第三步:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:

    • 1、在自身实例化时往属性订阅器(dep)里面添加自己
    • 2、自身必须有一个 update()方法
    • 3、待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile中绑定的回调,则功成身退。
  • 第四步:MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。

4.介绍下 Set、Map、WeakSet 和 WeakMap的区别?

Set
Set是一种叫做集合的数据结构,是由一堆无序的、相关联的,且不重复的内存结构组成的组合。集合是以[值,值]的形式存储元素

  1. 成员不能重复;
  2. 只有键值,没有键名,有点类似数组;
  3. 可以遍历,方法有 add、delete、has、clear

WeakSet

  1. WeackSet只能成员只能是引用类型,而不能是其他类型的值;
  2. 成员都是弱引用,随时可以消失(不计入垃圾回收机制)。可以用来保存 DOM 节点,不容易造成内存泄露;
  3. 不能遍历,没有size属性,方法有 add、delete、has ;

Map
Map是一种叫做字典的数据结构,每个元素有一个称作key 的域,不同元素的key 各不相同。字典是以[键,值]的形式存储。

  1. 本质上是键值对的集合,类似集合。Map的键可以时任何类型数据,就连函数都可以。;
  2. 可以遍历,方法很多,可以跟各种数据格式转换;
  3. Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键;

WeakMap

  1. 只接收对象为键名(null 除外),不接受其他类型的值作为键名;
  2. 键名指向的对象,不计入垃圾回收机制;
  3. 不能遍历,没有clear清空方法,方法同 get、set、has、delete ;

5.Vue2.0为什么不能检查数组的变化?该怎么解决?

  • 无法检测数组/对象的新增?

Vue检测数据的变动是通过Object.defineProperty实现的,所以无法监听数组的添加操作是可以理解的,因为是在构造函数中就已经为所有属性做了这个检测绑定操作。

  • 无法检测通过索引改变数组的操作?即vm.items[indexOfItem] = newValue?
function defineReactive(data, key, value) {Object.defineProperty(data, key, {enumerable: true,configurable: true,get: function defineGet() {console.log(`get key: ${key} value: ${value}`)return value},set: function defineSet(newVal) {console.log(`set key: ${key} value: ${newVal}`)value = newVal}})
}function observe(data) {Object.keys(data).forEach(function(key) {console.log(data, key, data[key])defineReactive(data, key, data[key])})
}let arr = [1, 2, 3]
observe(arr)

原来的Object.defineProperty发现通过索引是可以赋值的,并且也触发了set方法,但是Vue为什么不行呢?
在这里插入图片描述

对于对象而言,每一次的数据变更都会对对象的属性进行一次枚举,一般对象本身的属性数量有限,所以对于遍历枚举等方式产生的性能损耗可以忽略不计,但是对于数组而言呢?数组包含的元素量是可能达到成千上万,假设对于每一次数组元素的更新都触发了枚举/遍历,其带来的性能损耗将与获得的用户体验不成正比,故vue无法检测数组的变动。

解决方案

  • 数组
  1. this.$set(array, index, data)
//这是个深度的修改,某些情况下可能导致你不希望的结果,因此最好还是慎用
this.dataArr = this.originArr
this.$set(this.dataArr, 0, {data: '修改第一个元素'})
console.log(this.dataArr)        
console.log(this.originArr)  //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果 
  1. splice
//因为splice会被监听有响应式,而splice又可以做到增删改。
  1. 利用临时变量进行中转
let tempArr = [...this.targetArr]
tempArr[0] = {data: 'test'}
this.targetArr = tempArr
  • 对象
  1. this.$set(obj, key ,value) - 可实现增、改
  2. watch时添加deep:true深度监听,只能监听到属性值的变化,新增、删除属性无法监听
this.$watch('blog', this.getCatalog, {deep: true// immediate: true // 是否第一次触发});
  1. watch时直接监听某个key
watch: {'obj.name'(curVal, oldVal) {// TODO}
}

相关文章:

手写Vue3响应式数据原理

Vue3响应式数据 前言一、proxy是什么&#xff1f;1.1 proxy基本使用 二、实现最基本的reactive函数三、实现基本响应式系统四、完善基本响应式系统4.1 执行每一个副作用函数4.2 实现依赖收集4.2.1 基本实现 4.3 改进桶结构 五、相关面试题1.Object.defineProperty 和 Proxy 的区…...

基于PIC单片机篮球计分计时器

一、系统方案 本设计采用PIC单片机作为主控制器&#xff0c;矩阵键盘控制&#xff0c;比分&#xff0c;计时控制&#xff0c;24秒&#xff0c;液晶12864显示。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 2、液晶显示程序 /*************…...

关于Maxwell与Kafka和数据库的监控

1.Maxwell的配置 其实就是配置两端的配置信息,都要能连接上,然后才能去传输数据 config.properties #Maxwell数据发送目的地&#xff0c;可选配置有stdout|file|kafka|kinesis|pubsub|sqs|rabbitmq|redis producerkafka # 目标Kafka集群地址 kafka.bootstrap.servershadoop102…...

【设计模式】Java设计模式详细讲解

一、概述 Java设计模式是Java程序设计中一种重要的最佳实践&#xff0c;它提供了一种框架和结构&#xff0c;可以帮助开发者更好地理解和设计复杂的系统。设计模式不仅仅是一种语法规则&#xff0c;更是一种思想和方法论&#xff0c;它能够帮助开发者更好地分析、设计和实现软…...

【MySQL】表的增删查改(进阶)

目录 1.数据库约束 1.1NOT NULL&#xff1a;非空约束 1.2UNIQUE&#xff1a;唯一值约束 1.3DEFAULT&#xff1a;默认值约束 1.4PRIMARY KEY&#xff1a;主键约束 1.5FOREIGN KEY&#xff1a;外键约束 1.6CHECK约束 2.表的设计 2.1一对一 2.2一对多 2.3多对多 3.新增…...

Vim几种跳转方式

ps: 以下时我常用的一些跳转指令&#xff0c;用于参考和复习记忆。还有一些后续会更新。 文件内跳转 移动光标 普通模式下左h&#xff0c;右l&#xff0c;上k&#xff0c;下j。&#xff08;可以使用数字hlkj&#xff0c;实现跳跃式移动&#xff09;。 字符间跳转 …...

element-ui 弹窗里面嵌套弹窗,解决第二个弹窗被遮罩层掩盖无法显示的问题

当我们在 element-ui 中使用弹窗嵌套弹窗时&#xff0c;会出现第二个弹窗打开时被一个遮罩层挡着&#xff0c;就像下面这样&#xff1a; 下面提供两种解决方案 &#xff1a; 一、第一种方案 我们查询element-ui 官网可以发现 el-dialog 有这样几个属性&#xff1a; 具体使用就…...

【业务功能篇76】微服务网关路由predicates断言条件-filters路由转换地址-跨域问题-多级目录树化层级设计-mybatisPlus逻辑删除

业务开发-基础业务-分类管理 启动renren-fast如果出现如下错误 -Djps.track.ap.dependenciesfalse 添加相关配置即可 分类管理 1.后端分类接口 JDK8特性&#xff1a;https://blog.csdn.net/qq_38526573/category_11113126.html 在后端服务中我们需要查询出所有的三级分类信…...

apache的ab工具测试网页优化效果速度以及服务器承载

今天为大家介绍一款apache自带的一种的测试网页优化效果速度以及服务器承载的工具——ab.exe。 大家在工作中或者开发中可以使用apache的ab工具来测试自己的网站并发量大小&#xff0c;和某个页面的访问时间。 一、基本用法 如果你是用的是apache的话&#xff0c;那么只要进…...

【进阶篇】MySQL 存储引擎详解

文章目录 0.前言1.基础介绍2.1. InnoDB存储引擎底层原理InnoDB记录存储结构和索引页结构InnoDB记录存储结构&#xff1a;InnoDB索引页结构&#xff1a; 3. MVCC 详解3.1. 版本号分配&#xff1a;3.2. 数据读取&#xff1a;3.3. 数据写入&#xff1a;3.4. 事务隔离级别&#xff…...

Spring集成【MyBatis】和【PageHelper分页插件】整合---详细介绍

一&#xff0c;spring集成Mybatis的概念 Spring 整合 MyBatis 是将 MyBatis 数据访问框架与 Spring 框架进行集成&#xff0c;以实现更便捷的开发和管理。在集成过程中&#xff0c;Spring 提供了许多特性和功能&#xff0c;如依赖注入、声明式事务管理、AOP 等 它所带来给我们的…...

PyCharm下安装配置PySide6开发环境(Qt Designer(打开,编辑)、PyUIC和PyRCC)

一.准备工作 1.安装python和pycharm并配置好环境变量 python安装路径 pycharm安装路径&#xff1a; python系统变量&#xff1a; pycharm环境变量&#xff1a; 注意&#xff1a;正常安装&#xff0c;并勾选ADD PATH一般会自动配好 2.在pycharm创建一个新的python的虚拟环境 …...

pytest fixture 创建一个 requests.session() 对象

当你运行这段代码时&#xff0c;它会执行以下操作&#xff1a; 1. 导入必要的库&#xff1a;pytest 和 requests。 2. 定义一个夹具&#xff08;fixture&#xff09;函数 session&#xff0c;使用 pytest.fixture(scopesession) 装饰器进行标记。这个夹具函数在整个测试会话期…...

深入分析负载均衡情景

本文出现的内核代码来自Linux5.4.28&#xff0c;为了减少篇幅&#xff0c;我们尽量不引用代码&#xff0c;如果有兴趣&#xff0c;读者可以配合代码阅读本文。 一、有几种负载均衡的方式&#xff1f; 整个Linux的负载均衡器有下面的几个类型&#xff1a; 实际上内核的负载均衡…...

WPF基础入门-Class5-WPF命令

WPF基础入门 Class5-WPF命令 1、xaml编写一个button&#xff0c;Command绑定一个命令 <Grid><ButtonWidth"100"Height"40" Command"{Binding ShowCommand}"></Button> </Grid>2、编写一个model.cs namespace WPF_Le…...

云安全攻防(十三)之 使用minikube安装搭建 K8s 集群

使用minikube安装搭建 K8s 集群 Kubernetes 是一个可移植的、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;可促进声明式配置和自动化,一般来说K8s安装有三种方式&#xff0c;分别是Minikube装搭建 K8s 集群&#xff0c;特点是只有一个节点的集群&…...

Python数据分析 | 各种图表对比总结

本期将带领大家一起对在数据可视化的过程中常用的一些图表进行下总结&#xff1a; 条形图 【适用场景】 适用场合是二维数据集&#xff08;每个数据点包括两个值x和y&#xff09;&#xff0c;但只有一个维度需要比较&#xff0c;用于显示一段时间内的数据变化或显示各项之间的…...

linux系统(centos、ubuntu、银河麒麟服务、uos、deepin)判断程序是否已安装,通用判断方法:适用所有应用和命令的判断

前言 项目中需要判断linux服务器中是否已经安装了某个服务 方法有很多种&#xff0c;但是很多都不通用&#xff0c; 脚本代码就不容易做成统一的 解决方案 用下面的脚本代码去进行判断 用jdk测试 脚本意思如下&#xff1a; 输入java -version命令&#xff0c;将返回的字…...

Python3多线程/多进程解决方案(持续更新ing...)

诸神缄默不语-个人CSDN博文目录 文章目录 1. 多线程2. 多进程示例1&#xff1a;multiprocessing.Pool直接实现对一个列表中的每个元素的函数操作示例2&#xff1a;使用苏神写的工具函数实现对一个迭代器中每个元素的函数操作 1. 多线程 2. 多进程 示例1&#xff1a;multiproc…...

在`CentOS`中安装`Docker Engine`

本文总结如何在CentOS中安装Docker Engine 〇、Docker Engine 介绍 Docker Engine是一种开源容器化技术&#xff0c;用于构建和容器化应用程序。Docker引擎作为一个客户端-服务器应用程序: 具有长时间运行守护进程的服务器。指定接口的api&#xff0c;程序可以使用这些接口与…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...

FFmpeg avformat_open_input函数分析

函数内部的总体流程如下&#xff1a; avformat_open_input 精简后的代码如下&#xff1a; int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...

41道Django高频题整理(附答案背诵版)

解释一下 Django 和 Tornado 的关系&#xff1f; Django和Tornado都是Python的web框架&#xff0c;但它们的设计哲学和应用场景有所不同。 Django是一个高级的Python Web框架&#xff0c;鼓励快速开发和干净、实用的设计。它遵循MVC设计&#xff0c;并强调代码复用。Django有…...