react.js源码二

news2024/11/27 6:12:17

三、调度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等,它是基于任务优先级,时间分片实现的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高云GW1NSR-4C开发板M3硬核应用

1.M3硬核IP下载&#xff1a;Embedded M3 Hard Core in GW1NS-4C - 科技 - 广东高云半导体科技股份有限公司 (gowinsemi.com.cn) 特别说明&#xff1a;IDE必须是1.9.9及以后版本&#xff0c;1.9.8会导致编译失败&#xff08;1.9.8下1.1.3版本IP核可用&#xff09; 以下根据官方…

【后端开发】Next.js 13.4:前端开发的游戏规则改变者!

自我介绍 做一个简单介绍&#xff0c;酒架年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【…

云计算大屏,可视化云计算分析平台(云实时数据大屏PSD源文件)

大屏组件可以让UI设计师的工作更加便捷&#xff0c;使其更高效快速的完成设计任务。现分享可视化云分析系统、可视化云计算分析平台、云实时数据大屏的大屏Photoshop源文件&#xff0c;开箱即用&#xff01; 若需 更多行业 相关的大屏&#xff0c;请移步小7的另一篇文章&#…

浅析不同NAND架构的差异与影响

SSD的存储介质是什么&#xff0c;它就是NAND闪存。那你知道NAND闪存是怎么工作的吗&#xff1f;其实&#xff0c;它就是由很多个晶体管组成的。这些晶体管里面存储着电荷&#xff0c;代表着我们的二进制数据&#xff0c;要么是“0”&#xff0c;要么是“1”。NAND闪存原理上是一…

TCP为什么可靠之“重传机制”

TCP重传机制 TCP针对数据包丢失的情况&#xff0c;会通过重传机制解决&#xff0c;包括像超时重传、快速重传、选择确认SACK、D-SACK 超时重传 TCP会设置一个定时器&#xff0c;如果在发送数据之后的规定时间内&#xff0c;没有收到对方的ACK报文&#xff0c;就会触发重新发…

基于SpringBoot+JSP+Mysql宠物领养网站+协同过滤算法推荐宠物(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

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

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

Unity中Shader黑白阀值后处理效果

文章目录 前言一、我们先来PS看一下黑白阀值的效果二、使用step(a,b)函数实现效果三、实现脚本控制黑白阀值1、在Shader属性面板定义控制阀值变量2、把step的a改为_Value3、在后处理脚本设置公共成员变量,并且设置范围为&#xff08;0&#xff0c;1&#xff09;4、在Graphics.B…

Echarts 环形图配置 环形半径(radius) 修改文本位置(label) 南丁格尔图(roseType)

数据 const data [{ name: 华为, value: 404 },{ name: 小米, value: 800 }, { name: 红米, value: 540 }, { name: 苹果, value: 157 }]设置南丁格尔图 roseType: area设置标签位置 label: {show: true,position: center // center 中间展示 outside 外侧展示 inside 内侧…

案例026:基于微信小程序的原创音乐系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

Vue 纯css方式实现自定义进度条组件

组件源码 <template><div><div class"bar" :style"{--precent: precent}"></div></div></template><script>export default {name: ProgressBar,props: {precent:{},},data() {return {}},methods: {}}</sc…

GoLong的学习之路,进阶,微服务之使用,RPC包(包括源码分析)

今天这篇是接上上篇RPC原理之后这篇是讲如何使用go本身自带的标准库RPC。这篇篇幅会比较短。重点在于上一章对的补充。 文章目录 RPC包的概念使用RPC包服务器代码分析如何实现的&#xff1f;总结Server还提供了两个注册服务的方法 客户端代码分析如何实现的&#xff1f;如何异步…

kali linux无法使用root打开vlc观看视频的解决办法

kali linux陆续装了几个视频播放器&#xff0c;都比较不够友好&#xff0c;无奈安装vlc,vlc安装方法就是 apt install vlc这个没什么好说的&#xff0c;多数源都集成这个著名软件了&#xff0c;kali linux打开闪退&#xff0c;终端下运行出现&#xff1a; VLC is not supposed…

【数学建模】《实战数学建模:例题与讲解》第七讲-Bootstrap方法(含Matlab代码)

【数学建模】《实战数学建模&#xff1a;例题与讲解》第七讲-Bootstrap方法&#xff08;含Matlab代码&#xff09; 基本概念习题7.31. 题目要求2.解题过程3.程序4.结果 习题7.51. 题目要求2.解题过程3.程序4.结果 如果这篇文章对你有帮助&#xff0c;欢迎点赞与收藏~ 基本概念…

软件设计师——计算机网络(二)

&#x1f4d1;前言 本文主要是【计算机网络】——软件设计师——计算机网络的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1…

Unity DOTS中的baking(一) Baker简介

Unity DOTS中的baking&#xff08;一&#xff09; Baker简介 baking是DOTS ECS工作流的一环&#xff0c;大概的意思就是将原先Editor下的GameObject数据&#xff0c;全部转换为Entity数据的过程。baking是一个不可逆的过程&#xff0c;原先的GameObject在运行时不复存在&#x…

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

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

软件工程考试复习

第一章、软件工程概述 &#x1f31f;软件程序数据文档&#xff08;考点&#xff09; &#x1f31f;计算机程序及其说明程序的各种文档称为 &#xff08; 文件 &#xff09; 。计算任务的处理对象和处理规则的描述称为 &#xff08; 程序 &#xff09;。有关计算机程序功能、…

Spring Cloud Gateway + Nacos + LoadBalancer实现企业级网关

1. Spring Cloud Gateway 整合Nacos、LoadBalancer 实现企业级网关 前置工作&#xff1a; 创建 SpringBoot 多模块项目创建网关&#xff08;gateway-service&#xff09;、用户&#xff08;user-service&#xff09;模块用户模块添加 Nacos discovery 支持以及 Spring Web&am…

一键抠图2:C/C++实现人像抠图 (Portrait Matting)

一键抠图2&#xff1a;C/C实现人像抠图 (Portrait Matting) 目录 一键抠图2&#xff1a;C/C实现人像抠图 (Portrait Matting) 1. 前言 2. 抠图算法 3. 人像抠图算法MODNet &#xff08;1&#xff09;模型训练 &#xff08;2&#xff09;将Pytorch模型转换ONNX模型 &…