前端高级面试题-浏览器
1 事件机制
事件触发三阶段
-
document 往事件触发处传播,遇到注册的捕获事件会触发
-
传播到事件触发处时触发注册的事件
-
从事件触发处往 document 传播,遇到注册的冒泡事件会触发
事件触发⼀般来说会按照上⾯的顺序进⾏,但是也有特例,如果给⼀个⽬标节点同时注 册冒泡和捕获事件,事件触发会按照注册的顺序执⾏
// 以下会先打印冒泡然后是捕获
node.addEventListener('click',(event) =>{
console.log('冒泡')
},false);
node.addEventListener('click',(event) =>{
console.log('捕获 ')
},true)
注册事件
- 通常我们使⽤ addEventListener 注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture 参数来说,该参数默认值为 false 。useCapture 决定了注册的事件是捕获事件还是冒泡事件
- ⼀般来说,我们只希望事件只触发在⽬标上,这时候可以使⽤ stopPropagation 来阻⽌事件的进⼀步传播。通常我们认为 stopPropagation 是⽤来阻⽌事件冒泡的,其实该函数也可以阻⽌捕获事件。 stopImmediatePropagation 同样也能实现阻⽌事件,但是还能阻⽌该事件⽬标执⾏别的注册事件
node.addEventListener('click',(event) =>{
event.stopImmediatePropagation()
console.log('冒泡')
},false);
// 点击 node 只会执⾏上⾯的函数,该函数不会执⾏
node.addEventListener('click',(event) => {
console.log('捕获 ')
},true)
事件代理
如果⼀个节点中的⼦节点是动态⽣成的,那么⼦节点需要注册事件的话应该注册在⽗节点上
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('##ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
事件代理的⽅式相对于直接给⽬标注册事件来说,有以下优点
- 节省内存
- 不需要给⼦节点注销事件
2 跨域
因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端⼝有⼀个不同就是跨域, Ajax 请求会失败
JSONP
JSONP 的原理很简单,就是利⽤ <script> 标签没有跨域限制的漏洞。通过 <script> 标签指向⼀个需要访问的地址并提供⼀个回
调函数来接收数据当需要通讯时
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
- JSONP 使⽤简单且兼容性不错,但是只限于 get 请求
CORS
- CORS 需要浏览器和后端同时⽀持
- 浏览器会⾃动进⾏ CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了CORS ,就实现了跨域。
- 服务端设置 Access-Control-Allow-Origin 就可以开启 CORS 。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有⽹站都可以访问资源
document.domain
-
该⽅式只能⽤于⼆级域名相同的情况下,⽐如 a.test.com 和 b.test.com 适⽤于该⽅式。
-
只需要给⻚⾯添加 document.domain = ‘test.com’ 表示⼆级域名都相同就可以实现跨域
postMessage
这种⽅式通常⽤于获取嵌⼊⻚⾯中的第三⽅⻚⾯数据。⼀个⻚⾯发送消息,另⼀个⻚⾯判断来源并接收消息
// 发送消息端
window.parent.postMessage('message', 'http://blog.poetries.com');
// 接收消息端
var mc = new MessageChannel();
mc.addEventListener('message', (event) => {
var origin = event.origin || event.originalEvent.origin;
if (origin === 'http://blog.poetries.com') {
console.log('验证通过')
}
});
3 Event loop
JS中的event loop
众所周知 JS 是⻔⾮阻塞单线程语⾔,因为在最初 JS 就是为了和浏览器交互⽽诞⽣的。如果 JS 是⻔多线程的语⾔话,我们在多个线程中处理 DOM就可能会发⽣问题(⼀个线程中新加节点,另⼀个线程中删除节点)
- JS 在执⾏的过程中会产⽣执⾏环境,这些执⾏环境会被顺序的加⼊到执⾏栈中。如果遇到异步的代码,会被挂起并加⼊到 Task (有多种 task ) 队列中。⼀旦执⾏栈为空,Event Loop 就会从 Task 队列中拿出需要执⾏的代码并放⼊执⾏栈中执⾏,所以本质上来说 JS 中的异步还是同步⾏为
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
console.log('script end');
- 不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务( microtask ) 和 宏任务( macrotask )。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
new Promise((resolve) => {
console.log('Promise')
resolve()
}).then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTime
以上代码虽然 setTimeout 写在 Promise 之前,但是因为 Promise 属于微任务⽽ setTimeout 属于宏任务
微任务
- process.nextTick
- promise
- Object.observe
- MutationObserver
宏任务
-
script
-
setTimeout
-
setInterval
-
setImmediate
-
I/O
-
UI rendering
宏任务中包括了 script ,浏览器会先执⾏⼀个宏任务,接下来有异步代码
的话就先执⾏微任务
所以正确的⼀次 Event loop 顺序是这样的
-
执⾏同步代码,这属于宏任务
-
执⾏栈为空,查询是否有微任务需要执⾏
-
执⾏所有微任务
-
必要的话渲染 UI
-
然后开始下⼀轮 Event loop ,执⾏宏任务中的异步代码
通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有⼤量的计算并且需要操作 DOM 的话,为了更快的响应界⾯响应,我们可以把操作 DOM放⼊微任务中
Node 中的 Event loop
- Node 中的 Event loop 和浏览器中的不相同。
- Node 的 Event loop 分为 6 个阶段,它们会按照顺序反复运⾏
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
timer
- timers 阶段会执⾏ setTimeout 和 setInterval
- ⼀个 timer 指定的时间并不是准确时间,⽽是在达到这个时间后尽快执⾏回调,可能会因为系统正在执⾏别的事务⽽延迟
I/O
- I/O 阶段会执⾏除了 close 事件,定时器和 setImmediate 的回调
poll
- poll 阶段很重要,这⼀阶段中,系统会做两件事情
- 执⾏到点的定时器
- 执⾏ poll 队列中的事件
- 并且当 poll 中没有定时器的情况下,会发现以下两件事情
- 如果 poll 队列不为空,会遍历回调队列并同步执⾏,直到队列为空或者系统限制
- 如果 poll 队列为空,会有两件事发⽣
- 如果有 setImmediate 需要执⾏, poll 阶段会停⽌并且进⼊到 check 阶段执⾏setImmediate
- 如果没有 setImmediate 需要执⾏,会等待回调被加⼊到队列中并⽴即执⾏回调
- 如果有别的定时器需要被执⾏,会回到 timer 阶段执⾏回调。
check
- check 阶段执⾏ setImmediate
close callbacks
- close callbacks 阶段执⾏ close 事件
- 并且在 Node 中,有些情况下的定时器执⾏顺序是随机的
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
})
// 这⾥可能会输出 setTimeout,setImmediate
// 可能也会相反的输出,这取决于性能
// 因为可能进⼊ event loop ⽤了不到 1 毫秒,这时候会执⾏ setImmediate
// 否则会执⾏ setTimeout
上⾯介绍的都是 macrotask 的执⾏情况, microtask 会在以上每个阶段完成后⽴即执⾏
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
// 以上代码在浏览器和 node 中打印情况是不同的
// 浏览器中⼀定打印 timer1, promise1, timer2, promise2
// node 中可能打印 timer1, timer2, promise1, promise2
// 也可能打印 timer1, promise1, timer2, promise2
Node 中的 process.nextTick 会先于其他 microtask 执⾏
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function() {
console.log("promise1");
});
}, 0);
process.nextTick(() => {
console.log("nextTick");
});
// nextTick, timer1, promise1
4 Service Worker
Service workers 本质上充当Web应⽤程序与浏览器之间的代理服务器,也可以在⽹络可⽤时作为浏览器和⽹络间的代理。它们旨在
(除其他之外)使得能够创建有效的离线体验,拦截⽹络请求并基于⽹络是否可⽤以及更新的资源是否驻留在服务器上来采取适当的
动作。他们还允许访问推送通知和后台同步API
⽬前该技术通常⽤来做缓存⽂件,提⾼⾸屏速度
// index.js
if (navigator.serviceWorker) {
navigator.serviceWorker
.register("sw.js")
.then(function(registration) {
console.log("service worker 注册成功");
})
.catch(function(err) {
console.log("servcie worker 注册失败");
});
}
// sw.js
// 监听 `install` 事件,回调中缓存所需⽂件
self.addEventListener("install", e => {
e.waitUntil(
caches.open("my-cache").then(function(cache) {
return cache.addAll(["./index.html", "./index.js"]);
})
);
});
// 拦截所有请求事件
// 如果缓存中已经有请求的数据就直接⽤缓存,否则去请求数据
self.addEventListener("fetch", e => {
e.respondWith(
caches.match(e.request).then(function(response) {
if (response) {
return response;
}
console.log("fetch source");
})
);
});
- 打开⻚⾯,可以在开发者⼯具中的 Application 看到 Service Worker 已经启动了
- 在 Cache 中也可以发现我们所需的⽂件已被缓存
- 当我们重新刷新⻚⾯可以发现我们缓存的数据是从 Service Worker 中读取的
5 渲染机制
浏览器的渲染机制⼀般分为以下⼏个步骤
- 处理 HTML 并构建 DOM 树。
- 处理 CSS 构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成⼀个渲染树。
- 根据渲染树来布局,计算每个节点的位置。
- 调⽤ GPU 绘制,合成图层,显示在屏幕上
- 在构建 CSSOM 树时,会阻塞渲染,直⾄ CSSOM 树构建完成。并且构建 CSSOM 树是⼀个⼗分消耗性能的过程,所以应该尽量保证层级扁平,减少过度层叠,越是具体的 CSS 选择器,执⾏速度越慢
- 当 HTML 解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地⽅重新开始。也就是说,如果你想⾸屏渲染的越快,就越不应该在⾸屏就加载 JS ⽂件。并且 CSS 也会影响 JS 的执⾏,只有当解析完样式表才会执⾏ JS,所以也可以认为这种情况下,CSS 也会暂停构建 DOM
图层
⼀般来说,可以把普通⽂档流看成⼀个图层。特定的属性可以⽣成⼀个新的图层。不同的图层渲染互不影响,所以对于某些频繁需要
渲染的建议单独⽣成⼀个新图层,提⾼性能。但也不能⽣成过多的图层,会引起反作⽤
- 通过以下⼏个常⽤属性可以⽣成新图层
- 3D 变换: translate3d 、 translateZ
- will-change
- video 、 iframe 标签
- 通过动画实现的 opacity 动画转换
- position: fixed
重绘(Repaint)和回流(Reflow)
-
重绘是当节点需要更改外观⽽不会影响布局的,⽐如改变 color 就叫称为重绘
-
回流是布局或者⼏何属性需要改变就称为回流
回流必定会发⽣重绘,重绘不⼀定会引发回流。回流所需的成本⽐重绘⾼的多,改变深层次的节点很可能 导致⽗节点的⼀系列回流 -
所以以下⼏个动作可能会导致性能问题:
- 改变 window ⼤⼩
- 改变字体
- 添加或删除样式
- ⽂字改变
- 定位或者浮动
- 盒模型
很多⼈不知道的是,重绘和回流其实和 Event loop 有关
- 当 Event loop 执⾏完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新⼀次。
- 然后判断是否有 resize 或者 scroll ,有的话会去触发事件,所以 resize 和scroll 事件也是⾄少 16ms 才会触发⼀次,并且⾃带节流功能。
- 判断是否触发了 media query
- 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执⾏ requestAnimationFrame 回调
- 执⾏ IntersectionObserver 回调,该⽅法⽤于判断元素是否可⻅,可以⽤于懒加载上,但是兼容性不好
- 更新界⾯
- 以上就是⼀帧中可能会做的事情。如果在⼀帧中有空闲时间,就会去执⾏requestIdleCallback 回调
减少重绘和回流
- 使⽤ translate 替代 top
- 使⽤ visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
- 不要使⽤ table 布局,可能很⼩的⼀个⼩改动会造成整个 table 的重新布局
- 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使⽤requestAnimationFrame
- CSS 选择符从右往左匹配查找,避免 DOM 深度过深
- 将频繁运⾏的动画变为图层,图层能够阻⽌该节点回流影响别的元素。⽐如对于 video
标签,浏览器会⾃动将该节点变为图层
相关文章:
前端高级面试题-浏览器
1 事件机制 事件触发三阶段 document 往事件触发处传播,遇到注册的捕获事件会触发 传播到事件触发处时触发注册的事件 从事件触发处往 document 传播,遇到注册的冒泡事件会触发 事件触发⼀般来说会按照上⾯的顺序进⾏,但是也有特例&#x…...
Mongodb 多文档聚合操作处理方法三(聚合管道)
聚合 聚合操作处理多个文档并返回计算结果。您可以使用聚合操作来: 将多个文档中的值分组在一起。 对分组数据执行操作以返回单个结果。 分析数据随时间的变化。 要执行聚合操作,您可以使用: 聚合管道 单一目的聚合方法 Map-reduce 函…...
Zabbix分布式监控配置和使用
目录 1 Zabbix监控的配置流程2 添加主机组3 添加模板4 添加主机5 配置图形6 配置大屏7 新建监控项7.1 简介7.2 添加监控项7.3 查看数据7.4 图表 8 新建触发器8.1 概述8.2 添加触发器8.3 显示触发器状态 1 Zabbix监控的配置流程 在Zabbix-Web管理界面中添加一个主机,…...
XCTF_very_easy_sql
简单的进行sql注入测试后发现不简单尝试一下按照提示 结合这句提示应该是内部访问,所以采用的手段应该是ssrf顺便看看包 唯一值得关注的是set-cookie说回ssrf唯一能使用的方式应该是Gopher协议找到了一个POST的python脚本 import urllib.parsepayload ""…...
[React]useMemoizedFn和useCallback对比
useMemoizedFn文档地址:https://ahooks.js.org/zh-CN/hooks/use-memoized-fn hooks组件内什么时候会更新自定义函数 在 React 中,自定义的 Hooks 内部的函数在以下常见的几种情况下会被重新赋值,导致更新引用: 组件重新渲染&…...
计算机毕设 深度学习人体跌倒检测 -yolo 机器视觉 opencv python
文章目录 0 前言1.前言2.实现效果3.相关技术原理3.1卷积神经网络3.1YOLOV5简介3.2 YOLOv5s 模型算法流程和原理4.数据集处理3.1 数据标注简介3.2 数据保存 5.模型训练 6 最后 0 前言 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题…...
完全背包
动态规划解题步骤 : 动态规划问题一般从三个步骤进行考虑。 步骤一:集合和集合的状态 所谓的集合,就是一些方案的集合。 用 g[i][j] 表示从前 i 种物品中进行选择,且总体积不大于 j 的各个选法获得的价值的集合。注意:g[i][j] 不是一个数…...
【软件测试】webdriver常用API演示(Java+IDEA+chrome浏览器)
1.元素定位方法 对象的定位应该是自动化测试的核心,要想操作一个对象,首先应该识别这个对象。一个对象就是一个人一样,他会有各种的特征(属性),如比我们可以通过一个人的身份证号,姓名…...
Linux安装MySQL 8.1.0
MySQL是一个流行的开源关系型数据库管理系统,本教程将向您展示如何在Linux系统上安装MySQL 8.1.0版本。请按照以下步骤进行操作: 1. 下载MySQL安装包 首先,从MySQL官方网站或镜像站点下载MySQL 8.1.0的压缩包mysql-8.1.0-linux-glibc2.28-x…...
多线程面试相关的一些问题
面试题 1. 常见的锁策略相关的面试题 2. CAS相关的面试题 3. Synchronized 原理相关的面试题 4. Callable 接口相关的面试题 1. 常见的锁策略 乐观锁 vs 悲观锁 悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都…...
【使用维纳滤波进行信号分离】基于维纳-霍普夫方程的信号分离或去噪维纳滤波器估计(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Vue+axios如何解决跨域
1、为什么会产生跨域? 出于浏览器的同源策略限制。 同源策略(Sameoriginpolicy)是一种约定,是浏览器的一种安全机…...
网络安全系统中的守护者:如何借助威胁情报 (TI) 提高安全性
在这篇哈巴尔网站上的推文中,我们将解释 TI 缩写背后的含义、为什么需要它、Positive Technologies 收集哪些网络威胁数据以及如何帮助企业预防网络威胁。我们将以四种情况为例,说明公司如何使用 PT Threat Intelligence Feeds 来发现恶意活动并预防攻击…...
并发编程 - CompletableFuture
文章目录 Pre概述FutureFuture的缺陷类继承关系功能概述API提交任务的相关API结果转换的相关APIthenApplyhandlethenRunthenAcceptthenAcceptBoththenCombinethenCompose 回调方法的相关API异常处理的相关API获取结果的相关API DEMO实战注意事项 Pre 每日一博 - Java 异步编程…...
IPIDEA参展ChinaJoy!探索未来创新科技的峰会之旅
中国最大的国际数码互动娱乐展会ChinaJoy即将于7月28日在上海举行,届时将聚集全球来自22个国家和地区的领先科技公司、创业者和技术专家,为参观者呈现一系列引人入胜的展览和活动。而IPIDEA作为参展商之一,将为参观者带来一场关于数字科技的奇…...
2023最新ChatGPT商业运营版网站源码+支持ChatGPT4.0+GPT联网+支持ai绘画(Midjourney)+支持Mind思维导图生成
本系统使用Nestjs和Vue3框架技术,持续集成AI能力到本系统! 支持GPT3模型、GPT4模型Midjourney专业绘画(全自定义调参)、Midjourney以图生图、Dall-E2绘画Mind思维导图生成应用工作台(Prompt)AI绘画广场自定…...
轮趣科技教育版ros小车键盘控制运动
我之前买的ros小车是单独买的底板,以为随便一个树莓派就可以,因为我以前有一个树莓派3B,后来买了单独的小车之后,发现只能使用树莓派4B,然后又单独买了一个树莓派4B,给装上镜像,安装ros-melodic…...
深入理解Python中的os.chdir()方法
深入理解Python中的os.chdir()方法 1. 简介 在Python中,os.chdir()方法用于改变当前的工作目录。工作目录是指当前正在执行的脚本所在的目录。通过使用os.chdir()方法,我们可以在脚本执行过程中切换到不同的目录。 在编写Python脚本时,我们…...
【Golang 接口自动化02】使用标准库net/http发送Post请求
目录 写在前面 发送Post请求 示例代码 源码分析 Post请求参数解析 响应数据解析 验证 发送Json/XMl Json请求示例代码 xml请求示例代码 总结 资料获取方法 写在前面 上一篇我们介绍了使用 net/http 发送get请求,因为考虑到篇幅问题,把Post单…...
LaTex语法(常用数学符号的语法和注意事项)
说明:[]括号表示把语法括起来,并不表示LaTex语法。 1. 求和符号(Σ) 这个符号的基本语法为:[\sum_{}^{}]。 符号有两种模式:内联数学模式(inside math mode)和显示数学模式(displayed math mode)。 内联数学模式:排版时使用各…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
