启动 React APP 后经历了哪些过程

news2024/12/26 12:02:37

本文作者为 360 奇舞团前端开发工程师

前言

本文中使用的React版本为18,在摘取代码的过程中删减了部分代码,具体以源代码为准。

React 18里,通过ReactDOM.createRoot创建根节点。并且通过调用原型链上的render来渲染。 本文主要是从以下两个方法来介绍展开。

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

一、createRoot()

createRoot这个方法主要是用来创建FiberRoot(全局唯一,保存全局状态)和RootFiber(是应用里的第一个fiber对象),并将其关系关联起来。主要有以下过程:

  1. 校验container有效性,以及处理options参数

  2. 创建FiberRootrootFiber,并关联起来

  3. 事件代理

  4. 返回ReactDOMRoot实例

function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  // 校验合法性,以及处理options参数,此处省略
  if (!isValidContainer(container)) {
    //...
  }

  // 调用 createFiberRoot,创建FiberRoot和rootFiber,并关联关系,最终返回FiberRoot。FiberRoot.current = rootFiber; rootFiber.stateNode = FiberRoot;
  const root = createContainer(
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );
  
  // 标记container和rootFiber关系  container['__reactContainer$' + randomKey] = root.current
  markContainerAsRoot(root.current, container); 
  
  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  
  listenToAllSupportedEvents(rootContainerElement); // 事件代理

  // 实例化,挂载render,unmount方法
  return new ReactDOMRoot(root); // this._internalRoot = root;
}

关系结构示意图

a1c45080a08ccad53d32b5d98262e03b.png
image.png

二、render()

render主要是通过调用updateContainer,将组件渲染在页面上。

ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function(
  children: ReactNodeList,
): void {
  const root = this._internalRoot;
  if (root === null) {
    throw new Error('Cannot update an unmounted root.');
  }
  updateContainer(children, root, null, null);
};

updateContainer

updateContainer主要执行了以下步骤:

  1. 获取当前时间eventTime和任务优先级lane,调用createUpdate生成update;

  2. 执行enqueueUpdate将更新添加到更新队列里,并返回FiberRoot;

  3. scheduleUpdateOnFiber 调度更新;

function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  const current = container.current; // rootFiber
  const eventTime = requestEventTime(); // 更新触发时间
  const lane = requestUpdateLane(current); // 获取任务优先级

  // ... context 处理 

  // 创建update:{eventTime, lane, tag: UpdateState // 更新类型, payload: null, callback: null, next: null, // 下一个更新}
  const update = createUpdate(eventTime, lane); 
  update.payload = {element}; // element首次渲染时为App

  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }

  const root = enqueueUpdate(current, update, lane); // 将update添加到concurrentQueues队列里,返回 FiberRoot
  if (root !== null) {
    scheduleUpdateOnFiber(root, current, lane, eventTime); // 调度
    entangleTransitions(root, current, lane);
  }

  return lane;
}

调度阶段

调度入口:scheduleUpdateOnFiber

主要有以下过程:

  1. root上标记更新

  2. 通过执行ensureRootIsScheduled来调度任务

function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  markRootUpdated(root, lane, eventTime); // 在root上标记更新 
  // root.pendingLanes |= lane; 将update的lane放到root.pendingLanes
  // 设置lane对应事件时间 root.eventTimes[laneToIndex(lane)] = eventTime;

  if (
    (executionContext & RenderContext) !== NoLanes &&
    root === workInProgressRoot
  ) { 
    // 更新是在渲染阶段调度提示错误 ...
  } else { // 正常更新
    // ...
    ensureRootIsScheduled(root, eventTime); // 调度任务
    // ...
  }
}
调度优先级:ensureRootIsScheduled

该函数用于调度任务,一个root只能有一个任务在执行

  1. 设置任务的过期时间,有过期任务加入到expiredLanes

  2. 获取下一个要处理的优先级,没有要执行的则退出

  3. 判断优先级相等则复用,否则取消当前执行的任务,重新调度。

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  const existingCallbackNode = root.callbackNode; // 正在执行的任务

  // 遍历root.pendingLanes,没有过期时间设置root.expirationTimes,有过期时间判断是否过期,是则加入到root.expiredLanes中
  markStarvedLanesAsExpired(root, currentTime);
  // 过期时间设置 root.expirationTimes = currentTime+t(普通任务5000ms,用户输入250ms);
  
  // 获取要处理的下一个lanes
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  
  // 没有要执行的lanes
  if (nextLanes === NoLanes) {
    if (existingCallbackNode !== null) {
      // 取消正在执行的任务
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }

  const newCallbackPriority = getHighestPriorityLane(nextLanes); // 获取最高优先级的lane

  const existingCallbackPriority = root.callbackPriority;
  // 优先级相等复用已有的任务
  if (
    existingCallbackPriority === newCallbackPriority &&
    !(
      __DEV__ &&
      ReactCurrentActQueue.current !== null &&
      existingCallbackNode !== fakeActCallbackNode
    )
  ) {
    return;
  }
  // 优先级变化,取消正在执行的任务,重新调度
  if (existingCallbackNode != null) {
    cancelCallback(existingCallbackNode);
  }

  let newCallbackNode; // 注册调度任务
  // 同步任务,不可中断
  // 1. 调用scheduleSyncCallback将任务添加到队列syncQueue里;
  // 2. 创建微任务,调用flushSyncCallbacks,遍历syncQueue队列执行performSyncWorkOnRoot,清空队列;
  if (newCallbackPriority === SyncLane) {
    if (root.tag === LegacyRoot) {
      scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else {
      scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    }
    if (supportsMicrotasks) {
      // 支持微任务
        scheduleMicrotask(() => {
          if (
            (executionContext & (RenderContext | CommitContext)) ===
            NoContext
          ) {
            flushSyncCallbacks();
          }
        });
    } else {
      scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
    }
    newCallbackNode = null;
  } else {
    let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
      // ...
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
    // 非同步任务,可中断
    // 1. 维护了两个队列 timerQueue taskQueue
    // 2. 通过requestHostCallback开启宏任务执行任务
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}
调度任务 scheduleSyncCallback or scheduleCallback
  • scheduleSyncCallback 只有一个队列,将任务添加到队列里。按照顺序同步执行,不能中断。

function scheduleSyncCallback(callback: SchedulerCallback) { // callback =》performSyncWorkOnRoot
  if (syncQueue === null) {
    syncQueue = [callback];
  } else {
    syncQueue.push(callback);
  }
}
  • scheduleCallback 有两个队列(小顶堆),timerQueue存放未就绪的任务,taskQueue存放已就绪任务。每次循环,判断timerQueue里是否有可执行任务,并将其移动到taskQueue中,然后从taskQueue中取出任务执行。

function unstable_scheduleCallback(priorityLevel, callback, options) {
  // ... startTime timeout expirationTime 等初始化
  var newTask = { // 新的调度任务
    id: taskIdCounter++,
    callback, // render时为performConcurrentWorkOnRoot.bind(null, root),
    priorityLevel,
    startTime, // getCurrentTime()
    expirationTime, // startTime + timeout(根据priorityLevel,-1、250、1073741823、10000、5000、)
    sortIndex: -1, // startTime > currentTime ? startTime: expirationTime,
  };
  // 按照是否过期将任务推到队列timerQueue或者taskQueue里
  if (startTime > currentTime) {
    newTask.sortIndex = startTime;
    push(timerQueue, newTask);
    if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
      if (isHostTimeoutScheduled) {
        cancelHostTimeout(); // 取消当前的timeout
      } else {
        isHostTimeoutScheduled = true;
      }
      // 本质上是从timerQueue去取可以执行的任务放到taskQueue里,然后执行requestHostCallback
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  } else {
    newTask.sortIndex = expirationTime;
    push(taskQueue, newTask);
    
    // 调度任务
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork); // 设置isMessageLoopRunning,开启宏任务【schedulePerformWorkUntilDeadline】(优先级:setImmediate > MessageChannel > setTimeout)执行 performWorkUntilDeadline()
    }
  }

  return newTask;
}

这里要注意下,一直以来都认为是MessageChannel优先级大于setTimeout,但在浏览器打印之后发现事实相反。这个原因是chrome在某次更新里修改了二者的优先级顺序。想了解更多可以查看这篇文章:聊聊浏览器宏任务的优先级 - 掘金

执行任务 performWorkUntilDeadline

当监听到MessageChannel message的时候,执行该方法。通过调用scheduledHostCallback(即flushWork->workLoop返回的)结果,判断是否还有任务,若有则开启下一个宏任务。

const performWorkUntilDeadline = () => {
  if (scheduledHostCallback !== null) {
    const currentTime = getCurrentTime();
    startTime = currentTime;
    const hasTimeRemaining = true;

    let hasMoreWork = true;
    try {
      hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime); // scheduledHostCallback = flushWork ->workLoop
    } finally {
      if (hasMoreWork) {
        schedulePerformWorkUntilDeadline(); // 开启下一个宏任务MessageChannel,执行 performWorkUntilDeadline()
      } else {
        isMessageLoopRunning = false;
        scheduledHostCallback = null;
      }
    }
  } else {
    isMessageLoopRunning = false;
  }
  needsPaint = false;
};
workLoop

taskQueue取出任务执行task.callback即(performConcurrentWorkOnRoot)。如果callback返回的是函数,则表示任务被中断。否则任务执行完毕,则弹出该任务。

function workLoop(hasTimeRemaining, initialTime) {
  let currentTime = initialTime;
  advanceTimers(currentTime); // 将 timerQueue里到时间执行的定时任务移动到 taskQueue 里
  currentTask = peek(taskQueue); // 从 taskQueue 取任务
  while (
    currentTask !== null &&
    !(enableSchedulerDebugging && isSchedulerPaused)
  ) {
    // 任务未过期并且任务被中断
    if (
      currentTask.expirationTime > currentTime &&
      (!hasTimeRemaining || shouldYieldToHost())
    ) {
      break;
    }
    const callback = currentTask.callback; // 在scheduleCallback接受的第二个参数:performConcurrentWorkOnRoot
    if (typeof callback === 'function') {
      currentTask.callback = null;
      currentPriorityLevel = currentTask.priorityLevel;
      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
      // 如果返回是函数,则代表要重新执行;
      const continuationCallback = callback(didUserCallbackTimeout);
      currentTime = getCurrentTime();
      if (typeof continuationCallback === 'function') {
        // 任务暂停重新赋值callback
        currentTask.callback = continuationCallback;
      } else {
        // 任务完成弹出
        if (currentTask === peek(taskQueue)) {
          pop(taskQueue);
        }
      }
      advanceTimers(currentTime); // 每次执行完,去timerQueue查看有没有到时间的任务
    } else {
      pop(taskQueue); // 弹出该任务
    }
    currentTask = peek(taskQueue);
  }
  // 返回给外部判断是否还有任务需要执行,即performWorkUntilDeadline里面的hasMoreWork
  if (currentTask !== null) {
    return true;
  } else {
    // taskQueue里面没有任务了,从timerQueue取任务
    const firstTimer = peek(timerQueue);
    if (firstTimer !== null) {
      // 目的将timerQueue里的任务,移动到taskQueue里执行
      requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
    }
    return false;
  }
}

Render 阶段

这里render不是实际的dom render,而是fiber树的构建阶段。

Render入口
  • performSyncWorkOnRoot: 同步更新 =》 renderRootSync =》 workLoopSync

  • performConcurrentWorkOnRoot: 异步更新 =》 renderRootConcurrent =》 workLoopConcurrent

二者的区别主要是是否调用shouldYield,判断是否中断循环。

render之后就进入了commit阶段。

function performConcurrentWorkOnRoot(root, didTimeout) {
  currentEventTime = NoTimestamp;
  currentEventTransitionLane = NoLanes;

  const originalCallbackNode = root.callbackNode;
  const didFlushPassiveEffects = flushPassiveEffects();
  if (didFlushPassiveEffects) {
   // ...
  }

  let lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  if (lanes === NoLanes) {
    return null;
  }

  // 判断是否有用户输入、过期任务打断,需要同步渲染
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &&
    !includesExpiredLane(root, lanes) &&
    (disableSchedulerTimeoutInWorkLoop || !didTimeout); 
  // renderRootConcurrent|renderRootSync里都会调用prepareFreshStack:构建新的workInProgress树
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
  // render执行完成或抛出异常
  if (exitStatus !== RootInProgress) {
    if (exitStatus === RootErrored) {
    }
    if (exitStatus === RootFatalErrored) {
    }

    if (exitStatus === RootDidNotComplete) {
      markRootSuspended(root, lanes);
    } else {
      // render完成
      const renderWasConcurrent = !includesBlockingLane(root, lanes);
      const finishedWork: Fiber = (root.current.alternate: any);
      if (
        renderWasConcurrent &&
        !isRenderConsistentWithExternalStores(finishedWork)
      ) {
        exitStatus = renderRootSync(root, lanes);

        if (exitStatus === RootErrored) {
        }
        if (exitStatus === RootFatalErrored) {
        }
      }
      // 将新的fiber树赋值给root.finishedWork
      root.finishedWork = finishedWork;
      root.finishedLanes = lanes;
      
      // 进入commit阶段->调用 commitRoot-> commitRootImpl;
      // commitRootImpl 执行完成之后会清空重置root.callbackNode和root.callbackPriority;以及重置workInProgressRoot、workInProgress、workInProgressRootRenderLanes。
      finishConcurrentRender(root, exitStatus, lanes); 
    }
  }

  ensureRootIsScheduled(root, now()); // 退出前检测,是否有其他更新,需要发起调度
  if (root.callbackNode === originalCallbackNode) { // 没有改变,说明任务被中断,返回function,等待调用
    return performConcurrentWorkOnRoot.bind(null, root);
  }
  return null;
}
是否可中断循环

workLoopSync 和 workLoopConcurrent

  • 共同点:用于构建fiber树,workInProgress从根开始,遍历创建fiber节点。

  • 区别是:workLoopConcurrent里面增加了shouldYield判断。

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}
递归阶段 performUnitOfWork

遍历过程:从rootFiber向下采用深度优先遍历,当遍历到叶子节点时(递),然后会进入到归阶段,即遍历该节点的兄弟节点,如果没有兄弟节点则返回父节点。然后进行递归的交错执行。

  • 递阶段 beginWork: 创建或复用fiber节点。diff过程在此发生;

  • 归阶段 completeWork: 由下至上根据fiber创建或复用真实节点,并赋值给fiber.stateNode

function performUnitOfWork(unitOfWork: Fiber): void { // unitOfWork即workInProgress,指向下一个节点
  const current = unitOfWork.alternate;
  let next;
  next = beginWork(current, unitOfWork, renderLanes); 

  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // 遍历到叶子节点后,开始归阶段,并创建dom节点
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next; // workInProgress指向next
  }

  ReactCurrentOwner.current = null;
}
递归后的新的fiber树
b79678cf80026f17731075bf6a3e5e0b.png
image.png

Commit 阶段

通过commitRoot进入commit阶段。此阶段是同步执行的,不可中断。接下来经历了三个过程:

  1. before mutation阶段(执行DOM操作前):处理DOM节点渲染/删除后的focus、blur逻辑;调用getSnapshotBeforeUpdate生命周期钩子;调度useEffect。

  2. mutation阶段(执行DOM操作):DOM 插入、更新、删除

  3. layout阶段(执行DOM操作后):调用类组件的 componentDidMount、componentDidUpdate、setState 的回调函数;或函数组件的useLayoutEffectcreate函数;更新ref

页面渲染结果

import { useState } from 'react';

export default function Count() {
  const [num, setNum] = useState(1);
  const onClick = () => {
    setNum(num + 1);
  };
  return (
    <div>
      num is {num}
      <button onClick={onClick}>点击+1</button>
    </div>
  );
}

function List() {
  const arr = [1, 2, 3];
  return (
    <ul>
      {arr.map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
}

function App() {
  return (
    <div>
      <Count />
      <List />
    </div>
  );
}

export default App;
f013f3f1941d7a7bbf59862930b5de92.png
image.png

参考文章

[1] React https://github.com/facebook/react
[2] React技术揭秘 https://react.iamkasong.com/
[3] 图解React https://7km.top/main/macro-structure/
[4] 聊聊浏览器宏任务的优先级 https://juejin.cn/post/7202211586676064315

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

e3b9d87c35cb0a460856abf00100b60e.png

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

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

相关文章

Python函数绘图与高等代数互融实例(五): 则线图综合案例

Python函数绘图与高等代数互融实例(一):正弦函数与余弦函数 Python函数绘图与高等代数互融实例(二):闪点函数 Python函数绘图与高等代数互融实例(三):设置X|Y轴|网格线 Python函数绘图与高等代数互融实例(四):设置X|Y轴参考线|参考区域 Python函数绘图与高等代数互融实例(五…

iOS应用程序数据保护:如何保护iOS应用程序中的图片、资源和敏感数据

目录 转载&#xff1a;怎么保护苹果手机移动应用程序ipa中文件安全&#xff1f; 前言 1. 对敏感文件进行文件名称混淆 2. 更改文件的MD5值 3. 增加不可见水印处理 3. 对html&#xff0c;js&#xff0c;css等资源进行压缩 5. 删除可执行文件中的调试信息…

关于分布式一致性

一致性&#xff08;consistency&#xff09; 说到一致性&#xff0c;我们可能最先想到的数据库里的事务 这里的讨论的是分布式的一致性&#xff0c;事务就简化一下&#xff0c;只考虑Read/Write 先列举一下事务的种类&#xff1a; 单机的事务&#xff1a;多个复杂事务发生在一…

React组件化开发

1.组件的定义方式 函数组件Functional Component类组件Class Component 2.类组件 export class Profile extends Component {render() {console.log(this.context);return (<div>Profile</div>)} } 组件的名称是大写字符开头&#xff08;无论类组件还是函数组件…

ElementUI之登录与注册

目录 一.前言 二.ElementUI的简介 三.登录注册前端界面的开发 三.vue axios前后端交互--- Get请求 四.vue axios前后端交互--- Post请求 五.跨域问题 一.前言 这一篇的知识点在前面两篇的博客中就已经详细详解啦&#xff0c;包括如何环境搭建和如何建一个spa项目等等知识…

【二叉树魔法:链式结构与递归的纠缠】

本章重点 二叉树的链式存储二叉树链式结构的实现二叉树的遍历二叉树的节点个数以及高度二叉树的创建和销毁二叉树的优先遍历和广度优先遍历二叉树基础oj练习 1.二叉树的链式存储 二叉树的链式存储结构是指&#xff0c;用链表来表示一棵二叉树&#xff0c;即用链来指示元素的逻辑…

GitHub Copilot Chat

9月21日&#xff0c;GitHub在官网宣布&#xff0c;所有个人开发者可以使用GitHub Copilot Chat。用户通过文本问答方式就能生成、检查、分析各种代码。 据悉&#xff0c;GitHub Copilot Chat是基于OpenAI的GPT-4模型打造而成&#xff0c;整体使用方法与ChatGPT类似。例如&…

帆软BI开发-Day2-趋势图的多种变形

前言&#xff1a; 在BI数据展示中&#xff0c;条形图、趋势图无疑是使用场景非常多的两种图形。与条形图不同的是&#xff0c;趋势图更能反馈出一定的客观规律和未来的趋势走向&#xff0c;因此用于作为预警和判异的业务场景&#xff0c;但实际业务场景的趋势图可没你想的那么简…

代码随想录算法训练营 动态规划part14

一、最长公共子序列 1143. 最长公共子序列 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int longestCommonSubsequence(String s1, String s2) {int n s1.length(), m s2.length();char[] cs1 s1.toCharArray(), cs2 s2.toCharArray();int[][] f n…

精华回顾:Web3 前沿创新者在 DESTINATION MOON 共话未来

9 月 17 日&#xff0c;由 TinTinLand 主办的「DESTINATION MOON: Web3 Dev Summit Shanghai 2023」线下活动在上海黄浦如约而至。 本次 DESTINATION MOON 活动作为 2023 上海区块链国际周的 Side Event&#xff0c;设立了 4 场主题演讲与 3 个圆桌讨论&#xff0c;聚集了诸多…

[python 刷题] 42 Trapping Rain Water

[python 刷题] 42 Trapping Rain Water 题目&#xff1a; Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining. 这题的前置我觉得至少还是得做过 11 Container With Most Wat…

神经辐射场(Neural Radiance Field,NeRF)的简单介绍

参考文章&#xff1a;https://arxiv.org/abs/2210.00379 1. 概述 神经辐射场&#xff08;NeRF&#xff09;模型是一种新视图合成方法&#xff0c;它使用体积网格渲染&#xff0c;通过MLP进行隐式神经场景表达&#xff0c;以学习3D场景的几何和照明。   应用&#xff1a;照片…

浅谈双指针算法

目录 算法概述 案例分析 1、删除有序数组中的重复项 2、环形链表 3、盛最多水的容器 4、有效三角形的个数 5、三数之和 6、1089. 复写零 内容总结 算法概述 双指针指的是在遍历元素的过程中&#xff0c;不是使用单个指针进行访问&#xff0c;而是使用两个指针进行访问…

Linux启动过程详解 Xmind导图笔记

参考大佬博客&#xff1a; 简要描述linux系统从开机到登陆界面的启动过程 Linux启动过程详解 Bootloader详解 来源&#xff1a;从BIOS开始画图了解Linux启动过程——老杨Linux

WBS字典解释和举例

定义 WBS词典通常包括&#xff1a;编码、工作包描述&#xff08;内容&#xff09;、成本预算、时间安排、质量标准或要求、责任人或部门或外部单位&#xff08;委托项目&#xff09;、资源配置情况、其他属性等。 实例

Qt5开发及实例V2.0-第十五章-Qt单元测试框架

Qt5开发及实例V2.0-第十五章-Qt单元测试框架 第15章 Qt 5单元测试框架15.1 QTestLib框架15.2 简单的Qt单元测试15.3 数据驱动测试15.4 简单性能测试 本章相关例程源码下载1.Qt5开发及实例_CH1501.rar 下载2.Qt5开发及实例_CH1502.rar 下载3.Qt5开发及实例_CH1503.rar 下载4.Qt5…

【李宏毅 | 深度学习】自注意力机制(Self-attention)

这里写目录标题 引言Sequence LabelingSelf-attention矩阵乘法Muti-head Self-attention&#xff08;多头注意力机制&#xff09; 引言 以往我们遇到的深度学习问题中&#xff0c;对于神经网络的输入一般都是一个向量&#xff0c;输出可能是一个类别。如果增加输入的复杂度&am…

基于open CV实现YOLOv3复现_预测阶段和后处理阶段

基于open CV实现YOLOv3复现_预测阶段和后处理阶段 1.导入所需的库&#xff1a;2.对输入的图像进行resize3.将图像输入yolov3的网络中进行预测&#xff0c;对三个特征层进行解码。4.非极大值抑制来去除多余的预测框完整代码 当训练好了模型后&#xff0c;用训练好的权重文件进行…

tftpd文件传输工具的学习记录

1.目的&#xff1a;在SOC板上的linux系统和本地电脑的windows系统进行文件的传输。 2.在windows中安装tftp服务器,其下载的文件如下&#xff1a; 链接: https://pan.baidu.com/s/1YN5WxcjqCJLHTtjhUtKbjg 提取码: 3cg9 3.打开软件&#xff0c;在当前目录下选择windows传输的…

TCP协议中常见的问题

文章目录 TCP协议中常见的问题谈一谈对OSI七层模型和TCP/IP四层模型的理解&#xff1f;谈谈TCP协议的3次握手过程&#xff1f;TCP协议为什么要3次握手&#xff1f;2次&#xff0c;4次不行吗&#xff1f;谈谈TCP协议的四次挥手过程&#xff1f;什么是流量控制&#xff1f;什么是…