react.js源码二

三、调度Scheduler
scheduling(调度)是fiber reconciliation的一个过程,主要决定应该在何时做什么?在stack reconciler中,reconciliation是“一气呵成”,对于函数来说,这没什么问题,因为我们只想要函数的运行结果,但对于UI来说还需要考虑以下问题:
并不是所有的state更新都需要立即显示出来,比如屏幕之外的部分的更新;
并不是所有的更新优先级都是一样的,比如用户输入的响应优先级要比通过请求填充内容的响应优先级更高;
理想情况下,对于某些高优先级的操作,应该是可以打断低优先级的操作执行的,比如用户输入时,页面的某个评论还在reconciliation,应该优先响应用户输入。比如18版本里提示一些不安全的生命周期主要时它被打断了可能会被执行多次。
所以理想状况下reconciliation的过程应该是每次只做一个很小的任务,做完后能够“喘口气儿”,回到主线程看下有没有什么更高优先级的任务需要处理,如果有则先处理更高优先级的任务,没有则继续执行(cooperative scheduling 合作式调度)。
当用户操作时,调用setState,react会把当前的更新送入对应组件对应的update queue中。但是react并不会立即执行对比并修改DOM的操作。而是交给scheduler去处理。
scheduler会根据当前主线程的使用情况去处理这次update。为了实现这种特性,最开始考虑使用了requestIdelCallback API

总的来讲,通常,客户端线程执行任务时会以帧的形式划分,大部分设备控制在30-60帧是不会影响用户体验;在两个执行帧之间,主线程通常会有一小段空闲时间,requestIdleCallback可以在这个空闲期(Idle Period)调用空闲期回调(Idle Callback),执行一些任务。
在这里插入图片描述
低优先级任务由requestIdleCallback处理;
高优先级任务,如动画相关的由requestAnimationFrame处理;
requestIdleCallback可以在多个空闲期调用空闲期回调,执行任务;
requestIdleCallback方法提供deadline,即任务执行限制时间,以切分任务,避免长时间执行,阻塞UI渲染而导致掉帧;
但是由于requestIdleCallback有以下两个问题就采用了messageChannel模拟实现了requestIdleCallback。
1)兼容性;
2)50ms 渲染问题;(可能在一些任务很长时这个回调不会执行)
|— task queue —|— micro task —|— raf —|— render —|— requestIdleCallback – -|
requestIdleCallback是宏任务,messageChannel也宏任务。
为什么没有⽤ generator ?因为它是有状态的,无法从中间中断。
为什么没有⽤ setTimeout ?因为setTimeout有4-5ms的延时。
模拟了requestIdleCallback行为:

/**
 * schedule —> 把我的任务放进一个队列里,然后以某一种节奏进行执行;
 * 
 */

// task 的任务队列
const queue = [];
const threshold = 1000 / 60;

const transtions = [];
let deadline = 0;

// 获取当前时间, bi  date-now 精确
const now = () => performance.now(); // 时间 ,精确
// 从任务queue中,选择第一个 任务 
const peek = arr => arr.length === 0 ? null : arr[0];

// schedule —> 把我的任务放进一个队列里,然后以某一种节奏进行执行;
export function schedule (cb) {
    queue.push(cb);
    startTranstion(flush);
}

// 此时,是否应该交出执行权
function shouldYield() {
    return navigator.scheduling.isInputPending() || now() >= deadline;
}

// 执行权的切换
function startTranstion(cb) {
    transtions.push(cb) && postMessage();
}

// 执行权的切换
const postMessage = (() => {
    const cb = () => transtions.splice(0, 1).forEach(c => c());
    const { port1, port2 } = new MessageChannel();
    port1.onmessage = cb;
    return () => port2.postMessage(null);
})()

// 模拟实现 requestIdleCallback 方法
function flush() {
    // 生成时间,用于判断
    deadline = now() + threshold;
    let task = peek(queue);

    // 我还没有超出 16.666ms 同时,也没有更高的优先级打断我
    while(task && !shouldYield()) {
        const { cb } = task;
        const next = cb();
        // 相当于有一个约定,如果,你这个task 返回的是一个函数,那下一次,就从你这里接着跑
        // 那如果 task 返回的不是函数,说明已经跑完了。不需要再从你这里跑了
        if(next && typeof next === "function") {
            task.cb = next;
        } else {
            queue.shift()
        }
        task = peek(queue);
    }

    // 如果我的这一个时间片,执行完了,到了这里。
    task && startTranstion(flush)
}

一旦reconciliation过程得到时间片,就开始进入work loop。work loop机制可以让react在计算状态和等待状态之间进行切换。为了达到这个目的,对于每个loop而言,需要追踪两个东西:下一个工作单元(下一个待处理的fiber);当前还能占用主线程的时间。第一个loop,下一个待处理单元为根节点。
每个工作单元(fiber)执行完成后,都会查看是否还继续拥有主线程时间片,如果有继续下一个,如果没有则先处理其他高优先级事务,等主线程空闲下来继续执行
react17版本有时间切片ric,但是没有使用。18版本里才使用了。
宏任务微任务执行示例

四、diff算法
react diff算法最好时是O(n), 最差的话,是 O(mn),而传统的diff算法是O(n^3)。
react 是如何将 diff 算法的复杂度降下来的?
其实就是在算法复杂度、虚拟 dom 渲染机制、性能中找了⼀个平衡,react 采⽤了启发式的算法,做了如下最优假设:
a. 如果节点类型相同,那么以该节点为根节点的 tree 结构,⼤概率是相同的,所以如果类型不同,可以直接「删除」原节点,「插⼊」新节点;
b. 跨层级移动⼦ tree 结构的情况⽐较少⻅,或者可以培养⽤户使⽤习惯来规避这种情况,遇到这种情况同样是采⽤先「删除」再「插⼊」的⽅式,这样就避免了跨层级移动
c. 同⼀层级的⼦元素,可以通过 key 来缓存实例,然后根据算法采取「插⼊」「删除」「移动」的操作,尽量复⽤,减少性能开销
d. 完全相同的节点,其虚拟 dom 也是完全⼀致的;

react为什么不去优化diff算法?
因为新版本下,diff算法不是约束性能瓶颈的问题了。

为什么要有key?
在⽐较时,会以 key 和 type 是否相同进⾏⽐较,如果相同,则直接复制

vue diff算法和react diff算法相同/不同点:
共同点:
vue和diff算法,都是不进行跨层级比较,只做同级比较
不同点:
1.vue进行diff时,调用patch打补丁函数,一边比较一边给真实的dom打补丁,vue对比节点时,当节点元素类型相同,类名不同时,认为是不同的元素,删除重新创建,而react认为是同类型的节点,进行修改操作
2.vue列表对比的时候,采用从两端到中间的方式,旧集合和新集合两端各存在两个指针,两两进行比较,每次对比结束后,指针向队列中间移动;react则是从左往右一次对比,利用元素的index和lastindex进行比较
3.当一个集合把最后一个节点移动到最前面,react会把前面的节点依次向后移动,而Vue只会把最后一个节点放在最前面,这样的操作来看,Vue的diff性能是高于react的。

四、模拟实现react流程
react.js


const normalize = (children = []) => children.map(child => typeof child === 'string' ? createVText(child): child)

export const NODE_FLAG = {
    EL: 1, // 元素 element
    TEXT: 1 << 1
};
// El & TEXT  = 0


const createVText = (text) => {
    return {
        type: "",
        props: {
            nodeValue: text + ""
        },
        $$: { flag: NODE_FLAG.TEXT }
    }
}

const createVNode = (type, props, key, $$) => {
    return {
        type, 
        props,
        key,
        $$,
    }
}

export const createElement = (type, props, ...kids) => {
    props = props || {};
    let key = props.key || void 0;
    kids = normalize(props.children || kids);

    if(kids.length) props.children = kids.length === 1? kids[0] : kids;

    // 定义一下内部的属性
    const $$ = {};
    $$.staticNode = null;
    $$.flag = type === "" ? NODE_FLAG.TEXT: NODE_FLAG.EL;

    return createVNode(type, props, key, $$)
}

path.js

import { mount } from "./mount";
import { diff } from './diff';

function patchChildren(prev, next, parent) {
    // diff 整个的逻辑还是耗性能的,所以,我们可以先提前做一些处理。
    if(!prev) {
        if(!next) {
            // nothing
        } else {
            next = Array.isArray(next) ? next : [next];
            for(const c of next) {
                mount(c, parent);
            }
        }
    } else if (prev && !Array.isArray(prev)) {
        // 只有一个 children
        if(!next) parent.removeChild(prev.staticNode);
        else if(next && !Array.isArray(next)) {
            patch(prev, next, parent)
        } else {
            // 如果prev 只有一个节点,next 有多个节点
            parent.removeChild(prev.staticNode);
            for(const c of next) {
                mount(c, parent);
            }
        }
    } else diff(prev, next, parent);
}

export function patch (prev, next, parent) {
    // type: 'div' -> 'ul'
    if(prev.type !== next.type) {
        parent.removeChild(prev.staticNode);
        mount(next, parent);
        return;
    }

    // type 一样,diff props 
    // 先不看 children 
    const { props: { children: prevChildren, ...prevProps}} = prev;
    const { props: { children: nextChildren, ...nextProps}} = next;
    // patch Porps
    const staticNode = (next.staticNode = prev.staticNode);
    for(let key of Object.keys(nextProps)) {
        let prev = prevProps[key],
        next = nextProps[key]
        patchProps(key, prev, next, staticNode)
    }

    for(let key of Object.keys(prevProps)) {
        if(!nextProps.hasOwnProperty(key)) patchProps(key, prevProps[key], null, staticNode);
    }

    // patch Children !!!
    patchChildren(
        prevChildren,
        nextChildren,
        staticNode
    )

}


export function patchProps(key, prev, next, staticNode) {
    // style 
    if(key === "style") {
        // margin: 0 padding: 10
        if(next) {
            for(let k in next) {
                staticNode.style[k] = next[k];
            }
        }
        if(prev) {
        // margin: 10; color: red
            for(let k in prev) {
                if(!next.hasOwnProperty(k)) {
                    // style 的属性,如果新的没有,老的有,那么老的要删掉。
                    staticNode.style[k] = "";
                }
            }
        }
    }

    else if(key === "className") {
        if(!staticNode.classList.contains(next)) {
            staticNode.classList.add(next);
        }
    }

    // events
    else if(key[0] === "o" && key[1] === 'n') {
        prev && staticNode.removeEventListener(key.slice(2).toLowerCase(), prev);
        next && staticNode.addEventListener(key.slice(2).toLowerCase(), next);

    } else if (/\[A-Z]|^(?:value|checked|selected|muted)$/.test(key)) {
        staticNode[key] = next

    } else {
        staticNode.setAttribute && staticNode.setAttribute(key, next);
    }
}

mount.js

import { patchProps } from "./patch";
import { NODE_FLAG } from "./react";

export function mount(vnode, parent, refNode) {
    // 为什么会有一个 refNode?
    /**                   |
     * 假如: ul ->  li  li  li(refNode) 
     */
    if(!parent) throw new Error('no container');
    const $$ = vnode.$$;

    if($$.flag & NODE_FLAG.TEXT) {
        // 如果是一个文本节点
        const el = document.createTextNode(vnode.props.nodeValue);
        vnode.staticNode = el;
        parent.appendChild(el);
    } else if($$.flag & NODE_FLAG.EL) {
        // 如果是一个元素节点的情况,先不考虑是一个组件的情况;
        const { type, props } = vnode;
        const staticNode = document.createElement(type);
        vnode.staticNode = staticNode;

        // 我们再来处理,children 和后面的内容
        const { children, ...rest} = props;
        if(Object.keys(rest).length) {
            for(let key of Object.keys(rest)) {
                // 属性对比的函数
                patchProps(key, null, rest[key], staticNode);
            }
        }

        if(children) {
            // 递归处理子节点
            const __children = Array.isArray(children) ? children : [children];
            for(let child of __children) {
                mount(child, staticNode);
            }
        }
        refNode ? parent.insertBefore(staticNode, refNode) : parent.appendChild(staticNode);
    }
   
}

diff.js

import { mount } from './mount.js'
import { patch } from './patch.js'

export const diff = (prev, next, parent) => {
  let prevMap = {}
  let nextMap = {}

  // 遍历我的老的 children
  for (let i = 0; i < prev.length; i++) {
    let { key = i + '' } = prev[i]
    prevMap[key] = i
  }

  let lastIndex = 0
  // 遍历我的新的 children
  for (let n = 0; n < next.length; n++) {
    let { key = n + '' } = next[n]
    // 老的节点
    let j = prevMap[key]
    // 新的 child
    let nextChild = next[n]
    nextMap[key] = n
    // 老的children      新的children
    // [b, a]           [c, d, a]  =>  [c, b, a]  --> c
    // [b, a]           [c, d, a]  =>  [c, d, b, a]  --> d
    
    if (j == null) {
      // 从老的里面,没有找到。新插入
      let refNode = n === 0 ? prev[0].staticNode : next[n - 1].staticNode.nextSibling
      mount(nextChild, parent, refNode)
    }
    else {
      // [b, a]           [c, d, a]  =>  [c, d, a, b]  --> a
      // 如果找到了,我 patch 
      patch(prev[j], nextChild, parent)

      if (j < lastIndex) {
        // 上一个节点的下一个节点的前面,执行插入
        let refNode = next[n - 1].staticNode.nextSibling;
        parent.insertBefore(nextChild.staticNode, refNode)
      }
      else {
        lastIndex = j
      }
    }
  }
  // [b, a]           [c, d, a]  =>  [c, d, a]  --> b
  for (let i = 0; i < prev.length; i++) {
    let { key = '' + i } = prev[i]
    if (!nextMap.hasOwnProperty(key)) parent.removeChild(prev[i].staticNode)
  }
}

render.js

import { mount } from "./mount";
import { patch } from "./patch";

// step 1
// setTimeout(() => render(vnode, document.getElementById("app")))

// step 2
// setTimeout(() => render(null, document.getElementById("app")),5000)

export function render(vnode, parent) {
    let prev = parent.__vnode;
    if(!prev) {
        mount(vnode, parent);
        parent.__vnode = vnode;
    } else {
        if(vnode) {
            // 新旧两个
            patch(prev, vnode, parent);
            parent.__vnode = vnode;
        } else {
            parent.removeChild(prev.staticNode)
        }
    } 
}

index.js

import { render } from "./render";
import { createElement } from "./react";

// 用户的开发:
// react / preact / vue

const vnode = createElement(
  "ul",
  {
    id: "ul-test",
    className: "padding-20",
    style: {
      padding: "10px",
    },
  },
  createElement("li", { key: "li-0" }, "this is li 01")
);

const nextVNode = createElement(
  "ul",
  {
    style: {
      width: "100px",
      height: "100px",
      backgroundColor: "green",
    },
  },
  [
    createElement("li", { key: "li-a" }, "this is li a"),
    createElement("li", { key: "li-b" }, "this is li b"),
    createElement("li", { key: "li-c" }, "this is li c"),
    createElement("li", { key: "li-d" }, "this is li d"),
  ]
);

const lastVNode = createElement(
  "ul",
  {
    style: {
      width: "100px",
      height: "200px",
      backgroundColor: "pink",
    },
  },
  [
    createElement("li", { key: "li-a" }, "this is li a"),
    createElement("li", { key: "li-c" }, "this is li c"),
    createElement("li", { key: "li-d" }, "this is li d"),
    createElement("li", { key: "li-f" }, "this is li f"),
    createElement("li", { key: "li-b" }, "this is li b"),
  ]
);

setTimeout(() => render(vnode, document.getElementById("app")))
setTimeout(() => render(nextVNode, document.getElementById("app")),6000)
setTimeout(() => render(lastVNode, document.getElementById("app")),8000)
console.log(nextVNode);

使用rollup进行编译运行:
下载rollup插件,创建rollup.config.js文件

const livereload = require('rollup-plugin-livereload');
const serve = require('rollup-plugin-serve');

module.exports = {
    input: './react/index.js',
    output: {
        file: './dist/bundle.js',
        format: "iife" // es, umd, amd, cjs,iife以script脚本加载执行
    },
    plugins: [
        livereload(),
        serve({
            openPage: "/public/index.html",
            port: 3020,
            contentBase:'./'
        })
    ]
}

// rollup -c // 我默认去找根目录下的 rollup.config.js    -w 监听文件变化,重新编译。

执行 rollup -c // 我默认去找根目录下的 rollup.config.js -w 监听文件变化,重新编译。
将输入开始的文件,编译打包到output目录下。这样就可以访问对应端口查看页面。

react 冒泡到fiberroot 而不是到root,是因为render函数可能调用多次,会导致错乱。
react为什么实现合成事件,是因为如果写很多监听事件会导致性能下降,还有兼容性问题。

react18的新特性
React 18 中的重大更改仅限于几个简单的 API 更改,以及对 React 中多个行为的稳定性和一致性的一些改进,比较重要的一点是,不再支持 IE 浏览器。
1、客户端渲染 API
带有 createRoot() 的 root API,替换现有的 render() 函数,提供更好的人体工程学并启用新的并发渲染特性。
2、自动批量处理
以下都是批量处理了,以优化性能并避免重渲染。但是之前的版本在settimeout里是会渲染两次的。

const App = () => {
  const handleClick = () => {
    setA((a) => a + 1);
    setB((b) => b - 1);
    // Updates batched - single re-render
  };

  setTimeout(() => {
    setA((a) => a + 1);
    setB((b) => b - 1);
    // New (v18): Updates batched - single re-render
  }, 1000);

  // ...
};

3、并发渲染特性,比图startansition等,它是基于任务优先级,时间分片实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/234859.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

css未来:使用light-dark()切换主题色

css未来&#xff1a;使用light-dark()切换主题色 要根据使用的是浅色模式还是深色模式来更改颜色&#xff0c;我们通常会使用 prefers-color-scheme 媒体查询。为了让代码实现变得更容易&#xff0c;CSS 现在附带了一个名为 light-dark() 的实用函数。该函数接受两个颜色值作为…

2024年顶级的9个 Android 数据恢复工具(免费和付费)

不同的事情可能会损坏您的Android手机并导致您丢失数据。但大多数时候&#xff0c;您可以使用取证工具恢复部分或全部文件。 问题可能来自手机的物理损坏、磁盘的逻辑故障、完整的系统擦除&#xff0c;或者只是简单的粗心大意。 但是&#xff0c;无论数据丢失的原因是什么&am…

从零构建属于自己的GPT系列5:模型本地化部署(文本生成函数解读、模型本地化部署、文本生成文本网页展示、代码逐行解读)

&#x1f6a9;&#x1f6a9;&#x1f6a9;Hugging Face 实战系列 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在PyCharm中进行 本篇文章配套的代码资源已经上传 从零构建属于自己的GPT系列1&#xff1a;数据预处理 从零构建属于自己的GPT系列2&#xff1a;模型训…

基于Spring+Spring boot的SpringBoot在线电子商城管理系统

SSM毕设分享 基于SpringSpring boot的SpringBoot在线电子商城管理系统 1 项目简介 Hi&#xff0c;各位同学好&#xff0c;这里是郑师兄&#xff01; 今天向大家分享一个毕业设计项目作品【基于SpringSpring boot的SpringBoot在线电子商城管理系统】 师兄根据实现的难度和等级…

【K8S in Action】服务:让客户端发现pod 并与之通信(1)

服务是一种为一组功能相同的 pod 提供单一不变的接入点的资源。当服务存在时&#xff0c;它的 IP 地址和端口不会改变。 客户端通过 IP 地址和端口号建立连接&#xff0c; 这些连接会被路由到提供该服务的任意一个 pod 上。 pod 是短暂&#xff0c;会删除增加&#xff0c;调度…

基于JavaWeb+SSM+Vue微信小程序的科创微应用平台系统的设计和实现

基于JavaWebSSMVue微信小程序的科创微应用平台系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 Lun文目录 1系统概述 1 1.1 研究背景 1 1.2研究目的 1 1.3系统设计思想 1 2相关技术…

Java基础课的中下基础课04

目录 二十三、集合相关 23.1 集合 &#xff08;1&#xff09;集合的分支 23.2 List有序可重复集合 &#xff08;1&#xff09;ArrayList类 &#xff08;2&#xff09;泛型 &#xff08;3&#xff09;ArrayList常用方法 &#xff08;4&#xff09;Vector类 &#xff08;…

排序算法之六:快速排序(非递归)

快速排序是非常适合使用递归的&#xff0c;但是同时我们也要掌握非递归的算法 因为操作系统的栈空间很小&#xff0c;如果递归的深度太深&#xff0c;容易造成栈溢出 递归改非递归一般有两种改法&#xff1a; 改循环借助栈&#xff08;数据结构&#xff09; 图示算法 不是…

Zookeeper系统性学习-应用场景以及单机、集群安装

Zookeeper 是什么&#xff1f; Zookeeper 为分布式应用提供高效且可靠的分布式协调服务&#xff0c;提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面&#xff0c;ZooKeeper 并没有直接采用 Paxos 算法&#xff0c;而是采用了名为 …

漏刻有时百度地图API实战开发(8)关键词输入检索获取经纬度坐标和地址

在百度地图中进行关键词输入检索时&#xff1a; 在地图页面顶部的搜索框中输入关键词。点击搜索按钮或按下回车键进行搜索。地图将显示与关键词相关的地点、商家、景点等信息。可以使用筛选和排序功能来缩小搜索范围或更改搜索结果的排序方式。点击搜索结果中的地点或商家&…

办公word-从不是第一页添加页码

总结 实际需要注意的是&#xff0c;分隔符、分节符和分页符并不是一个含义 分隔符包含其他两个&#xff1b;分页符&#xff1a;是增加一页&#xff1b;分节符&#xff1a;指将文档分为几部分。 从不是第一页插入页码1步骤 1&#xff0c;插入默认页码 自己可以测试时通过**…

linux 14网站架构 编译安装mysql数据库

目录 LNMP网站架构下载源码包mysql 下载位置 mysql 安装1.1、清理安装环境&#xff1a;1.2、创建mysql用户1.3、从官网下载tar包1.4、安装编译工具1.5、解压1.6、编译安装编译安装三部曲1.7、初始化初始化,只需要初始化一次1.8、启动mysql1.9、登录mysql1.10、systemctl启动方式…

web 前端之标签练习+知识点

目录 实现过程&#xff1a; 结果显示 1、HTML语法 2、注释标签 3、常用标签 4、新标签 5、特殊标签 6、在网页中使用视频和音频、图片 7、表格标签 8、超链接标签 使用HTML语言来实现该页面 实现过程&#xff1a; <!DOCTYPE html> <html><head>…

nlkt中BigramAssocMeasures.pmi()方法的传参和使用

这个问题找遍全网没看到详细的介绍&#xff0c;最后用读代码数学公式的方法才理解怎么用。 BigramAssocMeasures.pmi 作用&#xff1a;计算x和y的互信息&#xff08;互信息是什么我就不科普啦&#xff09; 这里有个误区刚开始我以为是计算两个词之间的依赖程度&#xff0c;但…

【Spring教程25】Spring框架实战:从零开始学习SpringMVC 之 SpringMVC入门案例总结与SpringMVC工作流程分析

目录 1.入门案例总结2. 入门案例工作流程分析2.1 启动服务器初始化过程2.2 单次请求过程 欢迎大家回到《Java教程之Spring30天快速入门》&#xff0c;本教程所有示例均基于Maven实现&#xff0c;如果您对Maven还很陌生&#xff0c;请移步本人的博文《如何在windows11下安装Mave…

java resource ‘process/qingjia.png‘ not found

resource中的资源在target中没有&#xff0c;导致报错&#xff0c;如下图所示&#xff1a; 解决办法&#xff1a;在pom文件中添加如下代码&#xff1a; 重新执行代码&#xff0c;就能在target中看到png文件了。 类似的错误参考链接&#xff1a;mybatis-plus框架报错&#x…

探索HarmonyOS_开发软件安装

随着华为推出HarmonyOS NEXT 宣布将要全面启用鸿蒙原声应用&#xff0c;不在兼容安卓应用&#xff0c; 现在开始探索鸿蒙原生应用的开发。 HarmonyOS应用开发官网 - 华为HarmonyOS打造全场景新服务 鸿蒙官网 开发软件肯定要从这里下载 第一个为微软系统(windows)&#xff0c;第…

【Linux】使用Bash和GNU Parallel并行解压缩文件

介绍 在本教程中&#xff0c;我们将学习如何使用Bash脚本和GNU Parallel实现高效并行解压缩多个文件。这种方法在处理大量文件时可以显著加快提取过程。 先决条件 确保系统上已安装以下内容&#xff1a; BashGNU Parallel 你可以使用以下命令在不同Linux系统上安装它们&am…

RF射频干扰被动型红外传感器误判分析及整改事例

1.1 什么是红外传感 测量系统是以红外线为介质&#xff0c;探测可分成为光子和热探测器。 简洁原理就是利用产生的辐射与物质相互作用后呈现出来的物理效应就是它的基本原理。 1.2 红外按方式分类 &#xff08;1&#xff09;被动型红外&#xff1a;本身不会向外界辐射任何能量…

大师学SwiftUI第18章Part2 - 存储图片和自定义相机

存储图片 在前面的示例中&#xff0c;我们在屏幕上展示了图片&#xff0c;但也可以将其存储到文件或数据库中。另外有时使用相机将照片存储到设备的相册薄里会很有用&#xff0c;这样可供其它应用访问。UIKit框架提供了如下两个保存图片和视频的函数。 UIImageWriteToSavedPh…