对前端PWA应用的部分理解和基础Demo
一、什么是PWA应用?
1、PWA简介
渐进式Web应用(Progressive Web App),简称PWA,是 Google 在 2015 年提出的一种使用web平台技术构建的应用程序,官方认为其核心在于Reliable
(可靠的)、Fast
(快速的)、Engaging
(可参与的),结合了web网站程序和原生应用程序两者的优点,可以带给用户更佳的使用体验。
PWA既能像网站一样,通过一套代码在多个平台运行,而且可以通过浏览器进行访问,并通过Url链接进行分享。又能像原生应用一样,通过应用商店或网页安装在设备上,安装之后可以通过图标访问,作为一个独立的应用程序被启动;而且即使脱离网络,也可以通过应用缓存访问到部分页面和数据。
但需要注意的是,当PWA应用通过安装在设备上的图标打开时,虽然从外观上看来像是一个原生的应用程序,但从技术角度来看,其仍属于网站范畴,所以仍需要一个浏览器引擎来解析和运行,为其提供正常运行的环境。因此其原理类似于打开了一个单独的、自定义窗口内容的浏览器窗口。
PWA不仅是一种技术,更代表了一种Web网站的开发理念,如果一个网站程序实现了可安装、可离线等多种特定功能,我们就可以将其视为一个PWA应用。目前国内支持PWA的网站有:微博、语雀等等。
2、PWA特点
原生应用程序代表了最佳的功能,因为其与操作系统深入结合,拥有易于访问、可离线、操作系统集成等优点。 Web 网站程序则代表了最广的范围,因为其以浏览器为基础,拥有跨平台、无需下载、易于更新部署等优点。而PWA 则处于原生应用程序功能和 Web 网站程序范围的交叉点,是两者的结合体,主要拥有以下几种特点:
① 跨平台: PWA应用只需开发者书写一套代码,就可以在不同操作平台上运行,而且PWA应用采取渐进式增强的理念,其核心功能可以在任何浏览器上正常运行,其余强大的功能则需要依赖于浏览器对PWA特性的支持,根据浏览器的支持性,逐步升级体验。
② 可安装: PWA应用可以添加到主屏幕或应用程序菜单中,实现类似原生应用的图标入口,点击图标,作为一个独立应用被启动,用户可以更方便地访问应用。也可以将程序打包并上传各个应用商店,让用户通过应用商店安装网站应用。
③ 离线访问: PWA应用具备离线访问的能力,它们可以缓存应用的核心资源,使得用户可以在没有网络连接的情况下继续访问应用,查看到部分页面和数据,提供基本的功能,并在网络恢复时更新缓存。
④ 推送通知: PWA应用可以主动发送推送通知给用户,使得应用可以及时通知用户有关重要更新、新消息或其他关键信息,类似于原生应用的通知功能。
⑤ 快速加载: PWA应用使用Service Workers来缓存资源并提供离线体验,这也使得应用可以更快地加载和响应用户操作。
⑥ 可搜索: PWA应用可以通过搜索引擎被发现,而且可以通过url链接进行分享。
⑦ 热更新: PWA应用中的部分内容发生更新时,可在联网后自动进行局部热更新,确保用户能用到最新的应用程序,而无需像原生应用一样,重新下载安装客户端。
结合官方提出的Reliable
(可靠的)、Fast
(快速的)、Engaging
(可参与的)三个核心,我认为跨平台、离线访问体现了Reliable
(可靠的),无论是在低版本浏览器还是无网络的情况下,PWA都可以展示基本功能;快速加载、热更新则体现了Fast
(快速的),利用缓存和自动更新,减少重复数据加载,提升响应速度;可安装和推送通知则体现了Engaging
(可参与的),可安装在设备上,并向用户推送通知。
3、适用场景
地图导航、资料文档、博客笔记等等。
二、PWA的核心技术是什么?
PWA的实现依赖于多种技术实现,其中最核心的技术为Service Worker
、Web App Manifest
和Push Notification
。
1、Service Worker
Service Worker是一个独立于网页线程的脚本,无权访问页面的DOM结构,充当了网站和浏览器之间的代理服务器,每个PWA应用都只能注册一个Service Worker,其在PWA中主要用来实现离线访问、缓存资源、推送通知等功能,当然除此之外,它还具有很多其他功能,在这我们就不展开讲述了。
在网络正常时,当PWA应用请求Service Worker范围内的资源时,Service Worker会拦截该请求,并充当网络代理,然后它可以决定是从缓存中获取数据还是从服务器中获取数据。如果是从服务器中获取数据,Service Worker会缓存请求的数据,等到离线访问时,返回缓存的数据,使得PWA应用可以在离线状态下运行,并且可以利用缓存提升应用的加载速度。
由于Service Worker权利太大,能够直接截取并返回用户的请求,处于安全性考虑,目前仅支持在HTTPS或本地环境的安全环境下使用。
Service Worker的浏览器兼容性如下图:
如何为PWA注册Service Worker?
在Service Worker控制页面之前,必须在PWA应用中注册Service Worker服务。这意味着,在用户第一次访问PWA应用时,页面还并未受到Service Worker的控制,也就无法实现离线访问等功能。
注册Service Worker时,我们只需先判断浏览器是是否支持相关的API,如果支持则直接通过navigator.serviceWorker.register(url)
进行注册即可,参数url
表示具体Service Worker逻辑代码文件的路径。
// 这是页面中唯一与Service Worker有关的代码
if ('serviceWorker' in navigator) {navigator.serviceWorker.register('/service-worker.js').then(registration => {console.log('Service Worker 注册成功!', registration);}).catch(error => {console.log('Service Worker 注册失败:', error);});
}
如果想要查看Service Worker是否已经注册并正常运行,以Chrome浏览器为例,我们可以通过F12开发者工具中的Application,然后选中左侧的Service Workers ,如果右侧展示的信息中的Status
中显示activity
则表示已经注册并正常运行。
如果想要在移动端页面检查是否已经注册并正常运行,也只能通过连接电脑调试的方法来查看,具体可查看该文档:tools-and-debug。
Service Worker的作用范围怎么确定?
Service Worker在注册时引入的具体逻辑文件所在文件夹决定了其作用范围,例如:
navigator.serviceWorker.register("example.com/my-pwa/serviceworker.js");
则该Service Worker的作用范围在my-pwa
文件夹下的任何文件,如: example.com/my-pwa/index.html
等等。
为了实现Service Worker在PWA应用中的作用最大化,推荐将具体逻辑文件设置在PWA应用程序的根目录下,因为这样可以拦截到PWA应用中的所有请求。
Service Worker的生命周期分为哪些阶段?
Service Worker 的生命周期从注册 Service Worker 开始,也就是前文所说的register()
方法,调用该方法时,就会发生注册行为。该生命周期阶段并没有对应的事件,然后我们可以通过register()
方法的.then()
来判断是否注册成功。
① Registration(注册)
if ('serviceWorker' in navigator) {navigator.serviceWorker.register('/service-worker.js').then(registration => {console.log('Service Worker 注册成功!', registration);}).catch(error => {console.log('Service Worker 注册失败:', error);});
}
然后浏览器开始下载并安装 Service Worker 文件,安装成功后,则会触发install
事件,在整个生命周期中,install
事件仅会触发这一次。开发者通常会在此事件中进行初始化,缓存一些静态资源,以备离线时访问。
② Installation(安装)
// 安装阶段
self.addEventListener('install', function(event) {event.waitUntil(// 向缓存中存储基本数据caches.open('cache-name').then(function(cache) {return cache.addAll(['/path/to/resource1','/path/to/resource2',// ...]);}));
});
在Service Worker中,我们需要通过全局对象self
才能监听各个生命周期事件。在waitUntil()
方法执行结束之前,Service Worker不会结束安装状态,必须等待其内部代码执行结束之后,才会进入到下一个生命周期。caches
对象是限制在Service Worker 生命周期内使用的特殊对象,用于实现数据的缓存。
③ Activation(激活)
当Service Worker安装完成后,并不会立即进入激活状态,为了不影响当前正在访问的页面,此时Service Worker 并没有控制当前页面。所以要等到当前页面关闭,且再次加载该页面时,Service Worker才会进入激活状态,触发activate
事件,开始控制网页的请求和缓存。在此阶段,开发者通常会进行清理旧的缓存、处理更新逻辑等操作,因为浏览器的缓存空间是有限的。
// Service Worker激活成功后
self.addEventListener('activate', function(event) {event.waitUntil(// 对缓存中的数据进行处理caches.keys().then(function(cacheNames) {return Promise.all(// 只保留符合要求的数据 删除不需要的旧数据cacheNames.filter(function(cacheName) {return cacheName !== 'cache-name';}).map(function(cacheName) {return caches.delete(cacheName);}));}));
});
在waitUntil()
方法执行结束之前,Service Worker不会进入下个状态,然后可以通过caches
对象,对缓存的数据进行操作。
还有要注意的一点是,Service Worker 进入激活状态后,它会一直保持激活状态,除非被手动注销或者被新的 Service Worker 脚本取代。
④ Update(更新)
浏览器会周期性的检测当前应用的Service Worker是否有更新,当检测到Server Worker 脚本文件发生更新时,会在后台下载新的脚本,并触发更新流程。更新流程与安装流程类似,需要经历下载、安装、激活三个阶段。下载完成之后,会立即进行安装,但是安装完成之后,默认并不会立即激活,而且进入等待状态。因为同一时间只能有一个版本的 Service Worker处于Activation
状态。只有当旧版本的Service Worker控制的所有页面都被关闭,然后用户再重新访问这些页面时,新的Service Worker才会被激活并接管旧版本所有页面的控制权。
我们也可以通过skipWaiting()
方法来强制激活等待中 Service Worker,使其取代旧版 Service Worker,获得页面的控制权。该方法只有在存在等待状态的 Service Worker时,调用才会有意义,所以通常都在install
事件中执行调用。
// 新版Service Worker的install事件
self.addEventListener("install", (event) => {// 安装好后 调用skipWaiting() 使其立即激活// skipWaiting() 返回一个 promise,但完全可以忽略它self.skipWaiting();// 然后执行 service worker 安装所需的缓存数据等其他操作e.waitUntil((async () => {const cache = await caches.open(cacheName);await cache.addAll(contentToCache);})(),);
});
⑤ Termination(终止)
当Service Worker被手动注销,或被新版本Service Worker取代后,就会进入终止阶段,它将不再控制页面的请求,并释放相应的资源。即使不被注销或者取代,Service Worker也不会无限期的存活,各大浏览器的处理逻辑不同,但在激活一段时间后,Service Worker就会被终止。终止之后,需要重新注册,才能继续运行。
if ('serviceWorker' in navigator) {navigator.serviceWorker.register('/service-worker.js').then(registration => {console.log('Service Worker 注册成功!', registration);// 手动注销Service Workerregistration.unregister().then(function (boolean) {if(boolean) {console.log('Service Worker 注销成功!')}});}).catch(error => {console.log('Service Worker 注册失败:', error);});
}
⑥ Fetch(请求)
Service Worker 还提供了一个fetch
事件,每当Service Worker控制的页面中,发出fetch
请求或者html、css、js等资源请求时,都会触发该事件,我们可以在此阶段拦截请求并结合缓存使用自定义响应来响应请求。注意:ajax
请求不会触发该事件。
通常当请求的资源存在缓存时,我们都会从缓存中获取资源而不是从服务器获取。如果缓存中没有,那我们会使用另一个请求从服务器获取资源,并将资源存储在缓存中,以便下次请求或离线请求时使用。
self.addEventListener("fetch", (e) => {e.respondWith((async () => {// 从缓存中获取资源const r = await caches.match(e.request);console.log(`Service Worker正在请求资源: ${e.request.url}`);if (r) {// 如果缓存中存在资源 则直接返回缓存中的资源return r;}// 如果缓存中没有 则去服务器请求资源const response = await fetch(e.request);const cache = await caches.open(cacheName);console.log(`Service Worker 缓存新资源: ${e.request.url}`);// 将请求的资源存储到缓存中 cache.put(e.request, response.clone());// 将请求结果缓存return response;})(),);
});
该fetch
事件的事件对象event
中包含了一个respondWith()
方法,该方法可以阻止浏览器默认的fetch
请求操作,并允许自定义请求的response
,更多信息请查看:FetchEvent.respondWith()。
2、Web App Manifest
Web App Manifest(Web应用清单),是一个遵守W3C规范的JSON文件,用来定义PWA安装的客户端在设备上应该如何显示和运行,例如应用的名称、图标、启动方式等等,该文件是实现PWA所必需的。通过该文件,用户可将PWA应用安装到用户的主屏幕上,使其更像一个原生应用的客户端。
该文件中可定义的应用信息很多,其中比较常用的有以下几条:
① name
该字段定义PWA应用的全名,是Web App Manifes中必须的一个基本字段。该名称一般会显示为应用商店的应用名称,也会在应用启动时显示在标题栏中。
"name": "学科网PWA示例"
② short_name
该字段定义PWA应用的简称,尽量控制在12个字符以内,当应用程序被安装在桌面上时,由于空间有限,通常就会显示该简称,但具体展示name
还是short_name
可能因设备、浏览器或操作系统而有所不同,例如:在macos系统中,统一展示name
字段。
"short_name": "PWA示例"
③ icons
该字段定义了应用程序安装在桌面上的图标,属性值为一个数组,数组元素为一个对象,对象中包含src
、sizes
、type
三个属性,分别代表图标地址、图标的尺寸和图标的MIME类型。
src
:指定了图标文件的位置,字段值可以是相对于manifest
文件的相对URL,或者是一个绝对的网络URL。sizes
:指明了图标的尺寸,以宽×高
的形式指定了图标的宽高,单位默认为px
,目前设备适配性最好的图标尺寸为512×512
。type
:指明了图标的MIME
媒体类型,帮助浏览器在选择合适的图标文件,例如:image/png
、image/jpeg
等等。
该字段属性值数组至少需要定义一个图标元素,也可以定义多个不同格式的图标元素,从而为用户提供最佳的图标效果。每个浏览器都会根据其需要和所安装的操作系统选择其中最接近其所需的规范的某个图标。图标选择规则很多,主要有尺寸匹配、类型匹配、设备类型匹配等规则。
"icons": [{"src": "icons/512.png","type": "image/png","sizes": "512x512"},{"src": "icons/1024.png","type": "image/png","sizes": "1024x1024"}
]
④ start_url
该字段定义PWA应用的起始URL,用户点击图标打开程序时,将会加载这个URL所对应的页面,可以是相对于manifest文件的相对路径,也可以是一个绝对路径。推荐使用绝对路径,如果PWA应用的主页是网站的根目录,那么将该字段设置为/
即可。如果没有设置该字段,则默认将安装PWA应用时的URL作为该字段的值。
"start_url": "./index.html"
⑤ display
该字段定义了PWA应用的打开方式,字段值有以下四种:
standalone
(推荐):应用将以独立窗口打开,类似于原生应用程序,没有导航栏等浏览器功能。
-
fullscreen
:应用将以全屏模式打开,隐藏浏览器的地址栏和工具栏。由于电脑操作系统的限制,该字段值表现效果与standalone
一致。 -
minimal-ui
:应用将独立窗口打开,但保留了一部分浏览器的导航功能,如后退、刷新功能等。
-
browser
:应用将以常规浏览器网页的形式打开,类似于设置了一个网页的快捷方式。但是由于电脑操作系统的限制,该字段值表现效果与standalone
一致。"display": "standalone"
⑥ id
该字段用于作为PWA应用的唯一标识,如果未设置,则默认以start_url
的值为字段值。
"id": "xkw-pwa"
⑦ background_color
该字段定义了PWA应用窗口打开后且样式表加载完成之前的窗口背景色,字段值支持关键字(red、green等)、十六进制色值(#FFFFFF、#CCCCC等)和RGB色值(rgb(255,255,255)等),但不建议使用rgba()
等带有透明度的颜色,因为各个浏览器的展示效果可能大相径庭。但是目前iOS 和 iPadOS 上的 Safari 以及部分桌面浏览器目前会忽略此字段。
"background_color": "#000000",
⑧ theme_color
该字段定义了PWA应用的窗口主题色,将会影响窗口工具栏、头部标题栏等区域的颜色,段值支持关键字(red、green等)、十六进制色值(#FFFFFF、#CCCCC等)和RGB色值(255,255,255等)。但是该属性会被<meta name="theme-color" content="#ccc">
标签设置的主题色所覆盖。
"theme_color": "#3880FF"
⑨ 其他属性
。。。
3、Push Notification
Push
和 Notification
是两个独立的API,Push
用来接收服务器推送的信息,Notification
用来向用户推送信息。两者都需要在 Service Worker 内调用运行。
具体可查看:Push Notification
三、如何开发一个PWA应用Demo?
1、创建一个demo文件夹,用来存储相关文件
2、创建manifest.json文件,设置PWA应用信息
{"name": "猪猪侠的PWA示例", "short_name": "PWA示例","start_url": "/index.html","display": "standalone","background_color": "red","theme_color": "#ccc","icons": [{"src": "/icons/android-chrome-192x192.png","sizes": "192x192","type": "image/png"},{"src": "/icons/android-chrome-512x512.png","sizes": "512x512","type": "image/png"}]
}
3、创建icons文件夹,存储PWA应用图标文件
存储以下两个图标文件:
4、创建main.css文件,设置页面样式
h3 {color: red;
}
5、创建sw.js文件,设置Service Worker相关逻辑
这里我们只需要直接书写Service Worker的处理逻辑即可:
// 缓存的key值,用于区别新旧版本缓存
var cacheStorageKey = 'minimal-pwa-2'
// 设置初始需要缓存的文件
var cacheList = ['/','index.html','main.css','/icons/android-chrome-512x512.png'
]
// 监听安装事件 并在此阶段 缓存基本资源
self.addEventListener('install', e => {e.waitUntil(caches.open(cacheStorageKey).then(// 缓存基本资源cache => cache.addAll(cacheList)).then(() =>// 当脚本更新时 使新版Service Worker强制进入activate状态self.skipWaiting()))
})
// 监听fetch请求事件
self.addEventListener('fetch', function (e) {// 拦截相关请求e.respondWith(// 如果缓存中已经有请求的数据就终止请求 直接返回缓存数据caches.match(e.request).then(async function (response) {if (response != null) {return response}// 否则就重新向服务端请求const res = await fetch(e.request)// 这块需要结合具体业务具体分析 我这里的示例逻辑是无脑全部缓存// 请求成功后将请求的资源缓存起来 后续请求直接走缓存const cache = await caches.open(cacheStorageKey)cache.put(e.request, res.clone())// 将请求的资源返回给页面。return res;}))
})
// 监听激活事件
self.addEventListener('activate', function (e) {e.waitUntil(//获取所有cache名称caches.keys().then(cacheNames => {return Promise.all(// 获取缓存中所有不属于当前版本cachekey下的内容cacheNames.filter(cacheNames => {return cacheNames !== cacheStorageKey}).map(cacheNames => {// 删除不属于当前版本的cache缓存数据return caches.delete(cacheNames)}))}).then(() => {// 无须刷新页面 即可使新版server worker接管当前页面return self.clients.claim()}))
})
6、创建主文件index.html,设置页面DOM,并引用各类资源
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Hello PWA</title><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><link rel="stylesheet" href="main.css"><link rel="manifest" href="manifest.json">
</head><body><h3>Hello 猪猪侠的PWA</h3>
</body>
<script>// 检测浏览器是否支持SWif ('serviceWorker' in navigator) {// 为当前页面注册Service Workernavigator.serviceWorker.register('./sw.js').then(function (registartion) {console.log('当前浏览器支持sw:', registartion.scope);console.log('Service Worker注册成功', registartion);})}
</script>
</html>
7、部署到服务器上(https) 或在本地环境使用
以本地环境为例,使用VSCode作为辅助工具:
① 在VSCode中,右键选中index.html文件,选中Open with live Server
选项,运行页面:
② F12控制台,查看Service Worker是否注册成功:
③ 然后点击Application,选中左侧Service Workers,查看sw脚本是否正常运行:
④ 点击左侧Cache Storage,选中我们定义的cacheStorageKey-当前域名地址,查看初始资源(sw.js文件中定义的cacheList数组中的资源)是否被缓存:
⑤ 点击Network,选中All,刷新页面,查看请求资源情况:
⑥ 经过上次刷新,所有相关资源已被缓存,再次刷新页面,所有资源都将经过Service Worker之后,从缓存中获取:
⑦ 通过选中NetWork中的Offline选项切断网络,查看在无网络时,页面是否能利用缓存正常显示:
⑧ 其他操作。。。
四、相关资料
PWA谷歌文档
PWA的MDN文档
Service Worker
Service Worker 生命周期
Web App Manifes
相关文章:

对前端PWA应用的部分理解和基础Demo
一、什么是PWA应用? 1、PWA简介 渐进式Web应用(Progressive Web App),简称PWA,是 Google 在 2015 年提出的一种使用web平台技术构建的应用程序,官方认为其核心在于Reliable(可靠的…...

CSGO饰品价格会一直下跌吗?市场何时止跌回升?
最后一届巴黎major终于落下帷幕,Vitality小蜜蜂2-0战胜GL成功赢下本次Major冠军,也是首次夺得Major冠军!有人欢喜有人忧啊,csgo搬砖的饰品商人们一点也高兴不起来。 4月-5月,csgo皮肤饰品已持续走低快两个月了。手里满…...

线程池原理
一、线程池的定义 线程池,按照配置参数(核心线程数、最大线程数等)创建并管理若干线程对象,没有任务的时候,这些线程都处于等待空闲状态。如果有新的线程任务,就分配一个空闲线程执行。如果所有线程都处于…...

拷贝构造函数
1. 拷贝构造函数是构造函数的一个重载形式。 2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。 class Date { public:Date(int year 1900, int month 1, int day 1){_year year;_mont…...

数据库: MySQL安装部署、主从
单机部署 mkdir -p /opt/soft/archive cd /opt/soft/archivewget -i -c https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm yum install -y mysql80-community-release-el7-7.noarch.rpm yum-config-manager --enable mysql80-community yum install -y …...

Java IO流(二)IO模型(BIO|NIO|AIO)
概述 Java IO模型同步阻塞IO(BIO)、同步非阻塞IO(NIO)、异步非阻塞IO(AIO/NIO2),Java中的BIO、NIO和AIO理解为是Java语言对操作系统的各种IO模型的封装 IO模型 BIO(Blocking I/O) 概述 BIO是一种同步并阻…...

java版本spring cloud 企业工程系统管理 工程项目管理系统源码em
工程项目管理软件(工程项目管理系统)对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营,全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典ÿ…...

飞天使-k8s简单搭建
文章目录 k8s概念安装部署-第一版无密钥配置与hosts与关闭swap开启ipv4转发安装前启用脚本开启ip_vs安装指定版本docker 安装kubeadm kubectl kubelet,此部分为基础构建模版 k8s一主一worker节点部署k8s三个master部署,如果负载均衡keepalived 不可用,可以用单节点做…...

java中把一个list转tree的方法
环境 我们有个需求,数据库要存一个无限级联的tree,比如菜单,目录,或者地区等数据,现有两个问题: 问如何设计表。怎么返回给前端一个无线级联的json数据。 思考 第一个问题 在设计表的时候,…...

QT设置widget背景图片
首先说方法,在给widget或者frame或者其他任何类型的控件添加背景图时,在样式表中加入如下代码,指定某个控件,设置其背景。 类名 # 控件名 { 填充方式:图片路径 } 例如: QWidget#Widget {border-image: url…...

【ROS】话题通信--从理论介绍到模型实现(C++)
1.简单介绍 话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式的,也即:一个节点发布消息,另一个节点订阅该消息。像雷达、摄像头、GPS… 等等一些传感器数据的采集,也都是使用了话题通信,换言之…...

服务器数据恢复-EqualLogic存储RAID5数据恢复案例
服务器数据恢复环境: 一台DELL EqualLogic存储中有一组由16块SAS硬盘组建的RAID5阵列。存储存放虚拟机文件,采用VMFS文件系统,划分了4个lun。 服务器故障&检测&分析: 存储设备上有两个硬盘指示灯显示黄色,存储…...

qsort函数详解
大家好,我是苏貝,本篇博客带大家了解qsort函数,如果你觉得我写的不错的话,可以给我一个赞👍吗,感谢❤️ 文章目录 一. qsort函数参数详解1.数组首元素地址base2.数组的元素个数num和元素所占内存空间大小w…...

C#学习,委托,事件,泛型,匿名方法
目录 委托 声明委托 实例化委托 委托的多播 委托的用途 事件 通过事件使用委托 声明事件 泛型 泛型的特性 泛型方法 泛型的委托 匿名方法 编写匿名方法的语法 委托 类似于指针,委托是存有对某个方法的引用的一种引用类型变量,引用可以在运…...

2023最新版本~KEIL5使用C++开发STM32
先看效果 开始教学 因为是第一次写这个配置教程 我会尽量详细些 打开一个Keil工程 移除本地core 添加在线core 第一次编译代码 不会有报错 修改main.c文件类型为C 点击魔术棒 把ARM编译器修改为V6 第二次编译会报错语法不兼容 我把汇编部分的这些代码做了…...

汽车领域专业术语
1. DMS/OMS/RMS/IMS DMS:即Driver Monitoring System,监测对象为Driver(驾驶员)。DMS三大核心: OMS:即Occupancy Monitoring System,监测对象为乘客。 RMS:后排盲区检测系统 IMS&…...

H3C交换机如何配置本地端口镜像并在PC上使用Wireshake抓包
环境: H3C S6520-26Q-SI version 7.1.070, Release 6326 Win 10 专业版 Wireshake Version 4.0.3 问题描述: H3C交换机如何配置本地端口镜像并在PC上使用Wireshake抓包 解决方案: 配置交换机本地端口镜像 1.进入系统视图,并创建本地镜像组1 <H3C>system-vie…...

零基础自学:2023 年的今天,请谨慎进入网络安全行业
前言 2023 年的今天,慎重进入网安行业吧,目前来说信息安全方向的就业对于学历的容忍度比软件开发要大得多,还有很多高中被挖过来的大佬。 理由很简单,目前来说,信息安全的圈子人少,985、211 院校很多都才…...

向gitee推送代码
目录 一、Gitee创建仓库 二、将刚刚创建的仓库放到虚拟机上 2.1 https 方式克隆仓库 2.2 ssh的方式克隆仓库 三、本地开发,推送 3.1 查看是否有远程库 3.2 推送代码 3.3 查看是否推送成功 一、Gitee创建仓库 二、将刚刚创建的仓库放到虚拟机上 2.1 https 方式…...

双指针算法实例1(移动零)
常⻅的双指针有两种形式: 1 对撞指针(左右指针): a 对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼 近 b 终止条件一般是两指针相遇or错过(也可能在循…...

C#程序随系统启动例子 - 开源研究系列文章
今天讲讲C#中应用程序随系统启动的例子。 我们知道,应用程序随系统启动,都是直接在操作系统注册表中写入程序的启动参数,这样操作系统在启动的时候就根据启动参数来启动应用程序,而我们要做的就是将程序启动参数写入注册表即可。此…...

最全攻略之人工智能顶会论文发表
最全攻略之人工智能顶会论文发表 1. 人工智能顶会1.1 CCF 顶会列表2023年人工智能顶会时间线 2.人工智能顶会论文发表流程2.1 顶会论文发表流程2.2 顶会论文审稿流程 3.1顶会论文发表指南3.1 顶会论文七要素3.2 顶会论文写作要点 4.人工智能发展趋势4.1 人工智能未来趋势4.2 人…...

Redis基于内存的key-value结构化NOSQL(非关系型)数据库
Redis Redis介绍Redis的优点Redis的缺点Redis的安装Redis的连接Redis的使用Redis中的数据类型String的使用get setsetex(expire)ttlsetnx(not exit)HashList列表(队列)Set集合ZSet集合Redis 通用命令Redis图形客户端Redis在Java中的使用RedisTemplate...

Spring学习笔记+SpringMvc+SpringBoot学习笔记
壹、核心概念: 1.1. IOC和DI IOC(Inversion of Control)控制反转:对象的创建控制权由程序转移到外部,这种思想称为控制反转。/使用对象时,由主动new产生对象转换为由外部提供对象,此过程种对象…...

如何在 3Ds Max 中准确地将参考图像调整为正确的尺寸?
您是否想知道如何在 3Ds Max 中轻松直观地调整参考图像的大小,而无需借助第三方解决方案、插件或脚本? 我问自己这个问题,并高兴地发现了FFD Box 2x2x2,我无法停止钦佩这个修改器的多功能性。 在本文中,我想与您分享一…...

集简云推出的全国第一款 AI+连接器解决方案产品语聚AI
语聚AI是集简云推出的全国第一款 AI连接器解决方案产品,官网:https://yuju.jijyun.cn 语聚AI包括了多个不同的AI功能,协助企业和个人更好的使用AI语言模型所带来的能力,包括: 应用助手 希望通过AI智能助手帮助您查询C…...

git错误记录
露id没有影响,搞得微软不知道我ip一样 git fatal: 拒绝合并无关的历史的错误解决(亲测有效)...

linux使用jmeter进行压测
1.准备好服务器,这里默认服务器用的系统镜像为contos7.9.2009 2.准备好jmeter的测试计划文件 .jmx 这里默认测试计划的jmx文件在 /nas目录下 3.安装JDK与jmeter进行测试 #创建JDK与jmeter目录,并复制安装文件 mkdir /jmeter mkdir /jmeter/jav…...

leetcode 139. 单词拆分
2023.8.18 本题可以看作完全背包问题,字符串s为背包,字符串列表worddict中的字符串为物品。由于本题的物品集合是排列问题(即物品的排列顺序对结果有影响),所以遍历顺序为:先遍历背包再遍历物品。 接下来看代码: clas…...

若依的使用(token补充、HTTPS(网络安全)、分页前后端配置)
本文章转载于公众号:王清江唷,仅用于学习和讨论,如有侵权请联系 QQ交流群:298405437 本人QQ:4206359 具体视频地址:8 跑后端_哔哩哔哩_bilibili 1、HTTP? 曾经我们在讲JWT的时候,当时JWT需要配合https…...