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

Vue源码系列讲解——变化侦测篇【下】(Array的变化侦测)

目录

1. 前言

2. 在哪里收集依赖

3. 使Array型数据可观测

3.1 思路分析

3.2 数组方法拦截器

3.3 使用拦截器

4. 再谈依赖收集

4.1 把依赖收集到哪里

4.2 如何收集依赖

4.3 如何通知依赖

5. 深度侦测

6. 数组新增元素的侦测

7. 不足之处

8. 总结


1. 前言

上一篇文章中我们介绍了Object数据的变化侦测方式,本篇文章我们来看一下对Array型数据的变化Vue是如何进行侦测的。

为什么Object数据和Array型数据会有两种不同的变化侦测方式?

这是因为对于Object数据我们使用的是JS提供的对象原型上的方法Object.defineProperty,而这个方法是对象原型上的,所以Array无法使用这个方法,所以我们需要对Array型数据设计一套另外的变化侦测机制。

万变不离其宗,虽然对Array型数据设计了新的变化侦测机制,但是其根本思路还是不变的。那就是:还是在获取数据时收集依赖,数据变化时通知依赖更新。

下面我们就通过源码来看看VueArray型数据到底是如何进行变化侦测的。

2. 在哪里收集依赖

首先还是老规矩,我们得先把用到Array型数据的地方作为依赖收集起来,那么第一问题就是该在哪里收集呢?

其实Array型数据的依赖收集方式和Object数据的依赖收集方式相同,都是在getter中收集。那么问题就来了,不是说Array无法使用Object.defineProperty方法吗?无法使用怎么还在getter中收集依赖呢?

其实不然,我们回想一下平常在开发的时候,在组件的data中是不是都这么写的:

data(){return {arr:[1,2,3]}
}

想想看,arr这个数据始终都存在于一个object数据对象中,而且我们也说了,谁用到了数据谁就是依赖,那么要用到arr这个数据,是不是得先从object数据对象中获取一下arr数据,而从object数据对象中获取arr数据自然就会触发arrgetter,所以我们就可以在getter中收集依赖。

总结一句话就是:Array型数据还是在getter中收集依赖。

3. 使Array型数据可观测

上一章节中我们知道了Array型数据还是在getter中收集依赖,换句话说就是我们已经知道了Array型数据何时被读取了。

回想上一篇文章中介绍Object数据变化侦测的时候,我们先让Object数据变的可观测,即我们能够知道数据什么时候被读取了、什么时候发生变化了。同理,对于Array型数据我们也得让它变的可观测,目前我们已经完成了一半可观测,即我们只知道了Array型数据何时被读取了,而何时发生变化我们无法知道,那么接下来我们就来解决这一问题:当Array型数据发生变化时我们如何得知?

3.1 思路分析

Object的变化时通过setter来追踪的,只有某个数据发生了变化,就一定会触发这个数据上的setter。但是Array型数据没有setter,怎么办?

我们试想一下,要想让Array型数据发生变化,那必然是操作了Array,而JS中提供的操作数组的方法就那么几种,我们可以把这些方法都重写一遍,在不改变原有功能的前提下,我们为其新增一些其他功能,例如下面这个例子:

let arr = [1,2,3]
arr.push(4)
Array.prototype.newPush = function(val){console.log('arr被修改了')this.push(val)
}
arr.newPush(4)

在上面这个例子中,我们针对数组的原生push方法定义个一个新的newPush方法,这个newPush方法内部调用了原生push方法,这样就保证了新的newPush方法跟原生push方法具有相同的功能,而且我们还可以在新的newPush方法内部干一些别的事情,比如通知变化。

是不是很巧妙?Vue内部就是这么干的。

3.2 数组方法拦截器

基于上一小节的思想,在Vue中创建了一个数组方法拦截器,它拦截在数组实例与Array.prototype之间,在拦截器内重写了操作数组的一些方法,当数组实例使用操作数组方法时,其实使用的是拦截器中重写的方法,而不再使用Array.prototype上的原生方法。如下图所示:

经过整理,Array原型中可以改变数组自身内容的方法有7个,分别是:push,pop,shift,unshift,splice,sort,reverse。那么源码中的拦截器代码如下:

// 源码位置:/src/core/observer/array.jsconst arrayProto = Array.prototype
// 创建一个对象作为拦截器
export const arrayMethods = Object.create(arrayProto)// 改变数组自身内容的7个方法
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse'
]/*** Intercept mutating methods and emit events*/
methodsToPatch.forEach(function (method) {const original = arrayProto[method]      // 缓存原生方法Object.defineProperty(arrayMethods, method, {enumerable: false,configurable: true,writable: true,value:function mutator(...args){const result = original.apply(this, args)return result}})
})

在上面的代码中,首先创建了继承自Array原型的空对象arrayMethods,接着在arrayMethods上使用object.defineProperty方法将那些可以改变数组自身的7个方法遍历逐个进行封装。最后,当我们使用push方法的时候,其实用的是arrayMethods.push,而arrayMethods.push就是封装的新函数mutator,也就后说,实标上执行的是函数mutator,而mutator函数内部执行了original函数,这个original函数就是Array.prototype上对应的原生方法。 那么,接下来我们就可以在mutator函数中做一些其他的事,比如说发送变化通知。

3.3 使用拦截器

在上一小节的图中,我们把拦截器做好还不够,还要把它挂载到数组实例与Array.prototype之间,这样拦截器才能够生效。

其实挂载不难,我们只需把数据的__proto__属性设置为拦截器arrayMethods即可,源码实现如下:

// 源码位置:/src/core/observer/index.js
export class Observer {constructor (value) {this.value = valueif (Array.isArray(value)) {const augment = hasProto? protoAugment: copyAugmentaugment(value, arrayMethods, arrayKeys)} else {this.walk(value)}}
}
// 能力检测:判断__proto__是否可用,因为有的浏览器不支持该属性
export const hasProto = '__proto__' in {}const arrayKeys = Object.getOwnPropertyNames(arrayMethods)/*** Augment an target Object or Array by intercepting* the prototype chain using __proto__*/
function protoAugment (target, src: Object, keys: any) {target.__proto__ = src
}/*** Augment an target Object or Array by defining* hidden properties.*/
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {for (let i = 0, l = keys.length; i < l; i++) {const key = keys[i]def(target, key, src[key])}
}

上面代码中首先判断了浏览器是否支持__proto__,如果支持,则调用protoAugment函数把value.__proto__ = arrayMethods;如果不支持,则调用copyAugment函数把拦截器中重写的7个方法循环加入到value上。

拦截器生效以后,当数组数据再发生变化时,我们就可以在拦截器中通知变化了,也就是说现在我们就可以知道数组数据何时发生变化了,OK,以上我们就完成了对Array型数据的可观测。

4. 再谈依赖收集

4.1 把依赖收集到哪里

在第二章中我们说了,数组数据的依赖也在getter中收集,而给数组数据添加getter/setter都是在Observer类中完成的,所以我们也应该在Observer类中收集依赖,源码如下:

// 源码位置:/src/core/observer/index.js
export class Observer {constructor (value) {this.value = valuethis.dep = new Dep()    // 实例化一个依赖管理器,用来收集数组依赖if (Array.isArray(value)) {const augment = hasProto? protoAugment: copyAugmentaugment(value, arrayMethods, arrayKeys)} else {this.walk(value)}}
}

上面代码中,在Observer类中实例化了一个依赖管理器,用来收集数组依赖。

4.2 如何收集依赖

在第二章中我们说了,数组的依赖也在getter中收集,那么在getter中到底该如何收集呢?这里有一个需要注意的点,那就是依赖管理器定义在Observer类中,而我们需要在getter中收集依赖,也就是说我们必须在getter中能够访问到Observer类中的依赖管理器,才能把依赖存进去。源码是这么做的:

function defineReactive (obj,key,val) {let childOb = observe(val)Object.defineProperty(obj, key, {enumerable: true,configurable: true,get(){if (childOb) {childOb.dep.depend()}return val;},set(newVal){if(val === newVal){return}val = newVal;dep.notify()   // 在setter中通知依赖更新}})
}/*** Attempt to create an observer instance for a value,* returns the new observer if successfully observed,* or the existing observer if the value already has one.* 尝试为value创建一个0bserver实例,如果创建成功,直接返回新创建的Observer实例。* 如果 Value 已经存在一个Observer实例,则直接返回它*/
export function observe (value, asRootData){if (!isObject(value) || value instanceof VNode) {return}let obif (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__} else {ob = new Observer(value)}return ob
}

在上面代码中,我们首先通过observe函数为被获取的数据arr尝试创建一个Observer实例,在observe函数内部,先判断当前传入的数据上是否有__ob__属性,因为在上篇文章中说了,如果数据有__ob__属性,表示它已经被转化成响应式的了,如果没有则表示该数据还不是响应式的,那么就调用new Observer(value)将其转化成响应式的,并把数据对应的Observer实例返回。

而在defineReactive函数中,首先获取数据对应的Observer实例childOb,然后在getter中调用Observer实例上依赖管理器,从而将依赖收集起来。

4.3 如何通知依赖

到现在为止,依赖已经收集好了,并且也已经存放好了,那么我们该如何通知依赖呢?

其实不难,在前文说过,我们应该在拦截器里通知依赖,要想通知依赖,首先要能访问到依赖。要访问到依赖也不难,因为我们只要能访问到被转化成响应式的数据value即可,因为vaule上的__ob__就是其对应的Observer类实例,有了Observer类实例我们就能访问到它上面的依赖管理器,然后只需调用依赖管理器的dep.notify()方法,让它去通知依赖更新即可。源码如下:

/*** Intercept mutating methods and emit events*/
methodsToPatch.forEach(function (method) {const original = arrayProto[method]def(arrayMethods, method, function mutator (...args) {const result = original.apply(this, args)const ob = this.__ob__// notify changeob.dep.notify()return result})
})

上面代码中,由于我们的拦截器是挂载到数组数据的原型上的,所以拦截器中的this就是数据value,拿到value上的Observer类实例,从而你就可以调用Observer类实例上面依赖管理器的dep.notify()方法,以达到通知依赖的目的。

OK,以上就基本完成了Array数据的变化侦测。

5. 深度侦测

在前文所有讲的Array型数据的变化侦测都仅仅说的是数组自身变化的侦测,比如给数组新增一个元素或删除数组中一个元素,而在Vue中,不论是Object型数据还是Array型数据所实现的数据变化侦测都是深度侦测,所谓深度侦测就是不但要侦测数据自身的变化,还要侦测数据中所有子数据的变化。举个例子:

let arr = [{name:'NLRX',age:'18'}
]

数组中包含了一个对象,如果该对象的某个属性发生了变化也应该被侦测到,这就是深度侦测。

这个实现起来比较简单,源码如下:

export class Observer {value: any;dep: Dep;constructor (value: any) {this.value = valuethis.dep = new Dep()def(value, '__ob__', this)if (Array.isArray(value)) {const augment = hasProto? protoAugment: copyAugmentaugment(value, arrayMethods, arrayKeys)this.observeArray(value)   // 将数组中的所有元素都转化为可被侦测的响应式} else {this.walk(value)}}/*** Observe a list of Array items.*/observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}
}export function observe (value, asRootData){if (!isObject(value) || value instanceof VNode) {return}let obif (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__} else {ob = new Observer(value)}return ob
}

在上面代码中,对于Array型数据,调用了observeArray()方法,该方法内部会遍历数组中的每一个元素,然后通过调用observe函数将每一个元素都转化成可侦测的响应式数据。

而对应object数据,在上一篇文章中我们已经在defineReactive函数中进行了递归操作。

6. 数组新增元素的侦测

对于数组中已有的元素我们已经可以将其全部转化成可侦测的响应式数据了,但是如果向数组里新增一个元素的话,我们也需要将新增的这个元素转化成可侦测的响应式数据。

这个实现起来也很容易,我们只需拿到新增的这个元素,然后调用observe函数将其转化即可。我们知道,可以向数组内新增元素的方法有3个,分别是:pushunshiftsplice。我们只需对这3中方法分别处理,拿到新增的元素,再将其转化即可。源码如下:

/*** Intercept mutating methods and emit events*/
methodsToPatch.forEach(function (method) {// cache original methodconst original = arrayProto[method]def(arrayMethods, method, function mutator (...args) {const result = original.apply(this, args)const ob = this.__ob__let insertedswitch (method) {case 'push':case 'unshift':inserted = args   // 如果是push或unshift方法,那么传入参数就是新增的元素breakcase 'splice':inserted = args.slice(2) // 如果是splice方法,那么传入参数列表中下标为2的就是新增的元素break}if (inserted) ob.observeArray(inserted) // 调用observe函数将新增的元素转化成响应式// notify changeob.dep.notify()return result})
})

在上面拦截器定义代码中,如果是pushunshift方法,那么传入参数就是新增的元素;如果是splice方法,那么传入参数列表中下标为2的就是新增的元素,拿到新增的元素后,就可以调用observe函数将新增的元素转化成响应式的了。

7. 不足之处

前文中我们说过,对于数组变化侦测是通过拦截器实现的,也就是说只要是通过数组原型上的方法对数组进行操作就都可以侦测到,但是别忘了,我们在日常开发中,还可以通过数组的下标来操作数据,如下:

let arr = [1,2,3]
arr[0] = 5;       // 通过数组下标修改数组中的数据
arr.length = 0    // 通过修改数组长度清空数组

而使用上述例子中的操作方式来修改数组是无法侦测到的。 同样,Vue也注意到了这个问题, 为了解决这一问题,Vue增加了两个全局API:Vue.setVue.delete,这两个API的实现原理将会在后面学习全局API的时候说到。

8. 总结

在本篇文章中,首先我们分析了对于Array型数据也在getter中进行依赖收集;其次我们发现,当数组数据被访问时我们轻而易举可以知道,但是被修改时我们却很难知道,为了解决这一问题,我们创建了数组方法拦截器,从而成功的将数组数据变的可观测。接着我们对数组的依赖收集及数据变化如何通知依赖进行了深入分析;最后我们发现Vue不但对数组自身进行了变化侦测,还对数组中的每一个元素以及新增的元素都进行了变化侦测,我们也分析了其实现原理。

以上就是对Array型数据的变化侦测分析。

相关文章:

Vue源码系列讲解——变化侦测篇【下】(Array的变化侦测)

目录 1. 前言 2. 在哪里收集依赖 3. 使Array型数据可观测 3.1 思路分析 3.2 数组方法拦截器 3.3 使用拦截器 4. 再谈依赖收集 4.1 把依赖收集到哪里 4.2 如何收集依赖 4.3 如何通知依赖 5. 深度侦测 6. 数组新增元素的侦测 7. 不足之处 8. 总结 1. 前言 上一篇文…...

【机器学习笔记】贝叶斯学习

贝叶斯学习 文章目录 贝叶斯学习1 贝叶斯学习背景2 贝叶斯定理3 最大后验假设MAP(Max A Posterior)4 极大似然假设ML(Maximum Likelihood)5 朴素贝叶斯NB6 最小描述长度MDL 1 贝叶斯学习背景 试图发现两件事情的关系&#xff08;因果关系&#xff0c;先决条件&结论&#x…...

ElasticSearch之倒排索引

写在前面 本文看下es的倒排索引相关内容。 1&#xff1a;正排索引和倒排索引 正排索引就是通过文档id找文档内容&#xff0c;而倒排索引就是通过文档内容找文档id&#xff0c;如下图&#xff1a; 2&#xff1a;倒排索引原理 假定我们有如下的数据&#xff1a; 为了建立倒…...

win11安装mysql8.3.0压缩包版 240206

mysql社区版安装包版windows安装包下载地址 在系统环境变量path无点.的情况下 powershell 可以 .\ 或 ./ 开头表示当前文件夹cmd 可以直接命令或.\开头, 不能./开头 所以 .\ 在cmd和powershell中通用 步骤 在解压目录 .\mysqld --initialize-insecure root无密码初始化.\m…...

数据库索引与优化:深入了解索引的种类、使用与优化

数据库索引与优化&#xff1a;深入了解索引的种类、使用与优化 索引的种类 数据库索引是提高查询速度的重要手段之一&#xff0c;主要分为以下几种类型&#xff1a; 主键索引&#xff08;Primary Key Index&#xff09;&#xff1a; 唯一标识表中的每一行数据&#xff0c;保…...

React 错误边界组件 react-error-boundary 源码解析

文章目录 捕获错误 hook创建错误边界组件 Provider定义错误边界组件定义边界组件状态捕捉错误渲染备份组件重置组件通过 useHook 控制边界组件 捕获错误 hook getDerivedStateFromError 返回值会作为组件的 state 用于展示错误时的内容 componentDidCatch 创建错误边界组件 P…...

分享66个相册特效,总有一款适合您

分享66个相册特效&#xff0c;总有一款适合您 66个相册特效下载链接&#xff1a;https://pan.baidu.com/s/1jqctaho4sL_iGSNExhWB6A?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不…...

chagpt的原理详解

GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种基于Transformer架构的生成式预训练模型。GPT-3是其中的第三代&#xff0c;由OpenAI开发。下面是GPT的基本原理&#xff1a; Transformer架构&#xff1a; GPT基于Transformer架构&#xff0c;该架构由Att…...

dockerfile 详细讲解

当编写 Dockerfile 时&#xff0c;你需要考虑你的应用程序所需的环境和依赖项&#xff0c;并将其描述为一系列指令。下面是一个简单的示例&#xff0c;演示如何编写一个用于部署基于 Node.js 的网站的 Dockerfile&#xff1a; Dockerfile # 使用官方 Node.js 镜像作为基础镜像…...

跟着pink老师前端入门教程-day23

苏宁网首页案例制作 设置视口标签以及引入初始化样式 <meta name"viewport" content"widthdevice-width, user-scalableno, initial-scale1.0, maximum-scale1.0, minimum-scale1.0"> <link rel"stylesheet" href"css/normaliz…...

JRT监听程序

本次设计避免以往设计缺陷&#xff0c;老的主要为了保持兼容性&#xff0c;在用的设计就不好调了。 首先&#xff0c;接口抽象时候就不在给参数放仪器ID和处理类了&#xff0c;直接放仪器配置实体&#xff0c;接口实现想用什么属性就用什么属性&#xff0c;避免老方式要扩参数时…...

MCU+SFU视频会议一体化,视频监控,指挥调度(AR远程协助)媒体中心解决方案。

视频互动应用已经是政务和协同办公必备系统&#xff0c;早期的分模块&#xff0c;分散的视频应该不能满足业务需要&#xff0c;需要把视频监控&#xff0c;会议&#xff0c;录存一体把视频资源整合起来&#xff0c;根据客户需求&#xff0c;需要能够多方视频互动&#xff0c;直…...

1184. 欧拉回路(欧拉回路,模板题)

活动 - AcWing 给定一张图&#xff0c;请你找出欧拉回路&#xff0c;即在图中找一个环使得每条边都在环上出现恰好一次。 输入格式 第一行包含一个整数 t&#xff0c;t∈{1,2}&#xff0c;如果 t1&#xff0c;表示所给图为无向图&#xff0c;如果 t2&#xff0c;表示所给图为…...

学习 Redis 基础数据结构,不讲虚的。

学习 Redis 基础数据结构&#xff0c;不讲虚的。 一个群友给我发消息&#xff0c;“该学的都学了&#xff0c;怎么就找不到心意的工作&#xff0c;太难了”。 很多在近期找过工作的同学一定都知道了&#xff0c;背诵八股文已经不是找工作的绝对王牌。企业最终要的是可以创造价…...

Android 11 webview webrtc无法使用问题

问题&#xff1a;Android 11 webview 调用webrtc无法使用, 看logcat日志会报如下错误 [ERROR:address_tracker_linux.cc(245)] Could not send NETLINK request: Permission denied (13) 查了下相关的网络权限都有配置了还是不行&#xff0c;还是报这个权限问题 原因&#xff1…...

嵌入式单片机中晶振的工作原理

晶振在单片机中是必不可少的元器件&#xff0c;只要用到CPU的地方就必定有晶振的存在&#xff0c;那么晶振是如何工作的呢&#xff1f; 什么是晶振 晶振一般指晶体振荡器&#xff0c;晶体振荡器是指从一块石英晶体上按一定方位角切下的薄片&#xff0c;简称为晶片。 石英晶体谐…...

AWS配置内网EC2服务器上网【图形化配置】

第一种方法&#xff1a;创建EC2选择启用分配公网ip 1. 创建vpc 2. 创建子网 3. 创建互联网网关 创建互联网网关 创建互联网网关 &#xff0c;设置名称即可 然后给网关附加到新建的vpc即可 4. 给新建子网添加路由规则&#xff0c;添加新建的互联网网关然后点击保存更改 5. 新建…...

Android中的MVVM

演变 开发常用的框架包括MVC、MVP和本文的MVVM&#xff0c;三种框架都是为了分离ui界面和处理逻辑而出现的框架模式。mvp、mvvm都由mvc演化而来&#xff0c;他们不属于某种语言的框架&#xff0c;当存在ui页面和逻辑代码时&#xff0c;我们就可以使用这三种模式。 model和vie…...

制作耳机壳的UV树脂和塑料材质相比劣势有哪些?

以下是UV树脂相比塑料材质可能存在的劣势&#xff1a; 价格较高&#xff1a;相比一些常见的塑料材质&#xff0c;UV树脂的价格可能较高。这主要是因为UV树脂的生产过程较为复杂&#xff0c;需要较高的技术和设备支持。加工难度大&#xff1a;虽然UV树脂的加工过程相对简单&…...

CSP-202012-1-期末预测之安全指数

CSP-202012-1-期末预测之安全指数 题目很简单&#xff0c;直接上代码 #include <iostream> using namespace std; int main() {int n, sum 0;cin >> n;for (int i 0; i < n; i){int w, score;cin >> w >> score;sum w * score;}if (sum > 0…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…...