当前位置: 首页 > news >正文

React16源码: React中的beginWork的源码实现

beginWork


1 )概述

  • 在 renderRoot 之后,要对我们的 Fiber 树每一个节点进行对应的更新
  • 更新节点的一个入口方法,就是 beginWork
  • 这个入口方法会有帮助我们去优化整棵树的更新过程
    • react 它的节点其实是非常多的,如果每一次子节点的一个更新
    • 就需要每一个节点都执行一遍更新的话,它整体的性能肯定会不好,而且是没有必要的
    • 我们一个子节点的更新,可能不会影响到它的兄弟节点的更新
    • 所以这部分肯定是要优化的
  • 具体它是如何进行优化的,如下
    • 首先,要判断组件的更新是否可以优化
    • 然后要根据节点的类型分发进行处理,每一个节点它的更新方式会不一样
    • 最后是要根据 expirationTime 等信息判断这个节点的更新是否可以跳过

2 )源码

  • 在 renderRoot 中,它里面调用一个方法叫做 workLoop
  • 在 workLoop 中调用的一个方法叫做 performUnitOfWork
  • 而 beginWork 方法就在 performUnitOfWork 中调用
  • beginWork 就是执行对整棵树的每一个节点进行更新的一个操作
  • beginWork 来自于
    import {beginWork} from './ReactFiberBeginWork';
    

定位到 packages/react-reconciler/src/ReactFiberBeginWork.js
整个文件 export 出去的只有 beginWork 方法

function beginWork(current: Fiber | null,workInProgress: Fiber,renderExpirationTime: ExpirationTime,
): Fiber | null {// 读取了 workInProgress.expirationTime 这个值,这个 expirationTime 是节点上的 expirationTime// 函数第三个参数 renderExpirationTime 是 nextExpirationTimeToWorkOn 和 下面这个 expirationTime 有区别const updateExpirationTime = workInProgress.expirationTime;// 在 ReactDOM.render 第一次渲染时,第一个节点 current 是有值的, 接下去的节点都是没有的// 因为我们操作都是在 workInProgress 上操作的// 所以,workInProgress 创建的 child 节点也是一个 workInProgress 而不是 current// performUnitOfWork 接收的就是 workInProgressif (current !== null) {const oldProps = current.memoizedProps;const newProps = workInProgress.pendingProps;// 下面 renderExpirationTime 标志优先级最大都到哪个点,如果节点上的 expirationTime 比 renderExpirationTime 小// 说明节点上有优先级更高的任务,在这次渲染里是会执行的,反之,则不需要进行渲染,这个节点可以跳过// (updateExpirationTime === NoWork || updateExpirationTime > renderExpirationTime) 代表没有更新 或 有更新,但是优先级不高// hasLegacyContextChanged 属 context 相关api, childContextType 先跳过// oldProps === newProps 表示 props 一样if (oldProps === newProps &&!hasLegacyContextChanged() &&(updateExpirationTime === NoWork ||updateExpirationTime > renderExpirationTime)) {// This fiber does not have any pending work. Bailout without entering// the begin phase. There's still some bookkeeping we that needs to be done// in this optimized path, mostly pushing stuff onto the stack.// switch 先跳过switch (workInProgress.tag) {case HostRoot:pushHostRootContext(workInProgress);resetHydrationState();break;case HostComponent:pushHostContext(workInProgress);break;case ClassComponent: {const Component = workInProgress.type;if (isLegacyContextProvider(Component)) {pushLegacyContextProvider(workInProgress);}break;}case HostPortal:pushHostContainer(workInProgress,workInProgress.stateNode.containerInfo,);break;case ContextProvider: {const newValue = workInProgress.memoizedProps.value;pushProvider(workInProgress, newValue);break;}case Profiler:if (enableProfilerTimer) {workInProgress.effectTag |= Update;}break;case SuspenseComponent: {const state: SuspenseState | null = workInProgress.memoizedState;const didTimeout = state !== null && state.didTimeout;if (didTimeout) {// If this boundary is currently timed out, we need to decide// whether to retry the primary children, or to skip over it and// go straight to the fallback. Check the priority of the primary// child fragment.const primaryChildFragment: Fiber = (workInProgress.child: any);const primaryChildExpirationTime =primaryChildFragment.childExpirationTime;if (primaryChildExpirationTime !== NoWork &&primaryChildExpirationTime <= renderExpirationTime) {// The primary children have pending work. Use the normal path// to attempt to render the primary children again.return updateSuspenseComponent(current,workInProgress,renderExpirationTime,);} else {// The primary children do not have pending work with sufficient// priority. Bailout.const child = bailoutOnAlreadyFinishedWork(current,workInProgress,renderExpirationTime,);if (child !== null) {// The fallback children have pending work. Skip over the// primary children and work on the fallback.return child.sibling;} else {return null;}}}break;}}// 这个方法跳过该节点与其所有子节点的更新return bailoutOnAlreadyFinishedWork(current,workInProgress,renderExpirationTime,);}}// Before entering the begin phase, clear the expiration time.workInProgress.expirationTime = NoWork;// 如果节点有更新,通过节点的 tag,执行不同的方法,进行组件的更新switch (workInProgress.tag) {case IndeterminateComponent: {const elementType = workInProgress.elementType;return mountIndeterminateComponent(current,workInProgress,elementType,renderExpirationTime,);}case LazyComponent: {const elementType = workInProgress.elementType;return mountLazyComponent(current,workInProgress,elementType,updateExpirationTime,renderExpirationTime,);}case FunctionComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return updateFunctionComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}case ClassComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return updateClassComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}case HostRoot:return updateHostRoot(current, workInProgress, renderExpirationTime);case HostComponent:return updateHostComponent(current, workInProgress, renderExpirationTime);case HostText:return updateHostText(current, workInProgress);case SuspenseComponent:return updateSuspenseComponent(current,workInProgress,renderExpirationTime,);case HostPortal:return updatePortalComponent(current,workInProgress,renderExpirationTime,);case ForwardRef: {const type = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === type? unresolvedProps: resolveDefaultProps(type, unresolvedProps);return updateForwardRef(current,workInProgress,type,resolvedProps,renderExpirationTime,);}case Fragment:return updateFragment(current, workInProgress, renderExpirationTime);case Mode:return updateMode(current, workInProgress, renderExpirationTime);case Profiler:return updateProfiler(current, workInProgress, renderExpirationTime);case ContextProvider:return updateContextProvider(current,workInProgress,renderExpirationTime,);case ContextConsumer:return updateContextConsumer(current,workInProgress,renderExpirationTime,);case MemoComponent: {const type = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps = resolveDefaultProps(type.type, unresolvedProps);return updateMemoComponent(current,workInProgress,type,resolvedProps,updateExpirationTime,renderExpirationTime,);}case SimpleMemoComponent: {return updateSimpleMemoComponent(current,workInProgress,workInProgress.type,workInProgress.pendingProps,updateExpirationTime,renderExpirationTime,);}case IncompleteClassComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return mountIncompleteClassComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}default:invariant(false,'Unknown unit of work tag. This error is likely caused by a bug in ' +'React. Please file an issue.',);}
}
  • 注意,最顶层 workInProgress.expirationTime; 这个 expirationTime Fiber 节点上的 expirationTime
    • 对比 ReactFiberScheduler.js 中的 renderRoot 中读取的 root.nextExpirationTimeToWorkOn
    • 这个 root 是 FiberRoot, 而 FiberRoot 对应的 Fiber对象是 RootFiber
    • RootFiber 是 FiberRoot 的 current 属性,它没有对应React组件树的任何节点, 它只对应 FiberRoot 节点
      • FiberRoot 节点也就是挂载整个应用到的 Dom 节点
      • FiberRoot 上面存储整个应用的 expirationTime 和
    • RootFiber 的 stateNode 属性是 FiberRoot
    • FiberRoot 上面会存储整个应用上面的 expirationTime 和 nextExpirationTimeToWorkOn
    • 上面的两个值意义是不一样的, 在 renderRoot 函数中的一段代码如下
      // Reset the stack and start working from the root.
      resetStack();
      nextRoot = root;
      nextRenderExpirationTime = expirationTime;
      // nextUnitOfWork 是一个Fiber对象, 对应 RootFiber, 而非 FiberRoot
      // 更新过程使用的都是 Fiber 对象,不会是 FiberRoot
      nextUnitOfWork = createWorkInProgress(nextRoot.current, // 注意这里null,nextRenderExpirationTime,
      );
      
      • nextUnitOfWork 才是我们每一个节点去更新时要操作的节点对象
    • 所以,workInProgress.expirationTime 是对应Fiber节点产生更新的过期时间
      • 这里页面点击按钮让某个组件更新,最多到App上面创建更新
      • 不可能到 RootFiber 上面创建更新
    • 而 RootFiber 产生更新时在 ReactDOM.render 的时候才会有
    • 注意,在 beginWork 第一个参数是 current 是 Fiber对象,它对应的tag是 HostRoot
      • 在创建 FibeRoot 时, 在 ReactFiberReconciler.js 中的 createContainer 中
      • 有一个方法叫做 createFiberRoot, 定位到 ReactFiberRoot.js 中
      • 定位到 createHostRootFiber 方法,而这个方法又来自于 ReactFiber.js
      • 找到 createFiber, 是最终创建Fiber对象的时候调用的方法
        return createFiber(HostRoot, null, null, mode);
        
      • 参数是 HostRoot, null, null, mode
      • 第一个参数 HostRoot 就是 Fiber的tag
    • 在 ReactFiberScheduler.js 中 performUnitOfWork 接收的就是一个 workInProgress
      • 它传给 beginWork 的第一个参数 current 是 workInProgress.alternate
      • 在第一次渲染,也就是 ReactDOM.render 的时候, workInProgress 是刚刚创建的
      • 是不会在创建一个 current, 要等渲染结束后,把 current 和 workInProgress 的指针进行一个调换
      • 它才会变成有 current, 没有 workInProgress,再下次有更新产生,进行渲染时,才会创建一个新的 workInProgress
      • 这时候,current 和 workInProgress 都有了
      • 在最顶层 判断 current 是否 为 null, 也就是 判断是否是第一次渲染
  • 关于 bailoutOnAlreadyFinishedWork 这个方法帮助跳过该节点与其所有子节点的更新
    function bailoutOnAlreadyFinishedWork(current: Fiber | null,workInProgress: Fiber,renderExpirationTime: ExpirationTime,
    ): Fiber | null {cancelWorkTimer(workInProgress);if (current !== null) {// Reuse previous context listworkInProgress.firstContextDependency = current.firstContextDependency;}if (enableProfilerTimer) {// Don't update "base" render times for bailouts.stopProfilerTimerIfRunning(workInProgress);}// Check if the children have any pending work.const childExpirationTime = workInProgress.childExpirationTime; // 这个值是 React 16.5 加上的,更早跳过更新// 子树上无更新,或高优先级任务在这次渲染中完成的 则跳过,是一个非常大的优化if (childExpirationTime === NoWork ||childExpirationTime > renderExpirationTime) {// The children don't have any work either. We can skip them.// TODO: Once we add back resuming, we should check if the children are// a work-in-progress set. If so, we need to transfer their effects.return null;} else {// This fiber doesn't have work, but its subtree does. Clone the child// fibers and continue.// 如果子树上有更新要执行,拷贝老的childcloneChildFibers(current, workInProgress);return workInProgress.child;}
    }
    
    • 回过头看 performUnitOfWork, return 了 child 之后,赋值给next
      let next;
      if (enableProfilerTimer) {// ... 跳过很多代码next = beginWork(current, workInProgress, nextRenderExpirationTime);// ... 跳过很多代码
      } else {next = beginWork(current, workInProgress, nextRenderExpirationTime);workInProgress.memoizedProps = workInProgress.pendingProps;
      }// ... 跳过很多代码if (next === null) {// If this doesn't spawn new work, complete the current work.next = completeUnitOfWork(workInProgress);
      }ReactCurrentOwner.current = null;
      // next 不为 null, 直接 return
      return next;
      
    • return next; 之后 回调 workLoop, 之后赋值给 nextUnitOfWork
      while (nextUnitOfWork !== null) {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);}
      
    • 通过 while 循环继续执行 child 的更新
    • 这就是循环能够成立的条件
  • 回到 beginWork 中当节点还有更新的时候,执行 switch case
    • 通过节点类型执行不同的方法来进行组件的更新
    • 比如 FunctionComponent, MemoComponent 等

相关文章:

React16源码: React中的beginWork的源码实现

beginWork 1 &#xff09;概述 在 renderRoot 之后&#xff0c;要对我们的 Fiber 树每一个节点进行对应的更新更新节点的一个入口方法&#xff0c;就是 beginWork这个入口方法会有帮助我们去优化整棵树的更新过程 react 它的节点其实是非常多的&#xff0c;如果每一次子节点的…...

5-微信小程序语法参考

1. 数据绑定 官网传送门 WXML 中的动态数据均来自对应 Page 的 data。 数据绑定使用 Mustache 语法&#xff08;双大括号&#xff09;将变量包起来 ts Page({data: {info: hello wechart!,msgList: [{ msg: hello }, { msg: wechart }]}, })WXML <view class"vie…...

数组练习 Leetcode 566.重塑矩阵

在 MATLAB 中&#xff0c;有一个非常有用的函数 reshape &#xff0c;它可以将一个 m x n 矩阵重塑为另一个大小不同&#xff08;r x c&#xff09;的新矩阵&#xff0c;但保留其原始数据。 给你一个由二维数组 mat 表示的 m x n 矩阵&#xff0c;以及两个正整数 r 和 c &#…...

Linux centos中find命令的多种用途:按照具体应用来详细说明find的用法举例

目录 一、find命令 二、find命令的语法 &#xff08;一&#xff09;语法格式 &#xff08;二&#xff09;选项 1、选项(option)介绍 2、控制符号链接的option 3、调试选项debugopts 4、优化选项 &#xff08;三&#xff09;表达式expression 1、选项options 2、测试…...

服务器数据恢复—OceanStor存储raid5热备盘同步数据失败的数据恢复案例

服务器数据恢复环境&#xff1a; 华为OceanStor某型号存储&#xff0c;存储内有一组由24块硬盘组建的raid5阵列&#xff0c;配置1块热备盘。 服务器故障&#xff1a; 该存储raid5阵列中有一块硬盘离线&#xff0c;热备盘自动激活并开始同步数据&#xff0c;在热备盘同步数据的…...

Xline v0.6.1: 一个用于元数据管理的分布式KV存储

Xline是什么&#xff1f;我们为什么要做Xline&#xff1f; Xline是一个基于Curp协议的&#xff0c;用于管理元数据的分布式KV存储。现有的分布式KV存储大多采用Raft共识协议&#xff0c;需要两次RTT才能完成一次请求。当部署在单个数据中心时&#xff0c;节点之间的延迟较低&a…...

【CSS】解决height = line-height 文字不垂直居中(偏上、偏下)的问题

解决办法1&#xff1a; 查看 font-family 属性&#xff0c;确认是否是因为字体而导致的不垂直居中问题。 其他小知识&#xff1a; 基线就是小写x字母的下边缘(线) 就是我们常说的 基线。line-height 属性设置的行高也就是定义的两行文字基线之间的距离! 参考文章&#xff1a;…...

天津想转行学python培训班靠谱吗?

现在的职业如此繁多&#xff0c;很多人把高薪当成衡量工作好坏的重要标准&#xff0c;因此IT行业以超出其他行业几倍薪资水平成为不错的选择&#xff0c;而Python又以其简单易学好上手成为大家所青睐的学习目标。 Python发展前景如何 Python语言就业发展方向广泛&#xff1a;…...

(C语言)冒泡排序

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//实现buble_sort函数&#xff1b; void buble_sort(int arr[], int sz) {//初始化变量值&#xff1b;int i 0;//嵌套循环冒泡排序&#xff1b;//外层循环&…...

怎么样的布局是符合可制造性的PCB布局?

满足可制造性、可装配性、可维修性要求&#xff0c;方便调试的时候于检测和返修&#xff0c;能够方便的拆卸器件&#xff1a; 1&#xff09;极性器件的方向不要超过2种&#xff0c;最好都进行统一方向等要求&#xff0c;如图1-1所示&#xff1b; 图1-1 极性器件方向统一摆放 2…...

第28关 k8s监控实战之Prometheus(九)

------> 课程视频同步分享在今日头条和B站 大家好&#xff0c;我是博哥爱运维。早期我们经常用邮箱接收报警邮件&#xff0c;但是报警不及时&#xff0c;而且目前各云平台对邮件发送限制还比较严格&#xff0c;所以目前在生产中用得更为多的是基于webhook来转发报警内容到企…...

安全防御之可信计算技术

可信计算技术是一种计算机安全体系结构&#xff0c;旨在提高计算机系统在面临各种攻击和威胁时的安全性和保密性。它通过包括硬件加密、受限访问以及计算机系统本身的完整性验证等技术手段&#xff0c;确保计算机系统在各种攻击和威胁下保持高度安全和保密性。 一、可信计算基…...

FPGA引脚物理电平(内部资源,Select IO)-认知2

引脚电平 The SelectIO pins can be configured to various I/O standards, both single-ended and differential. • Single-ended I/O standards (e.g., LVCMOS, LVTTL, HSTL, PCI, and SSTL) • Differential I/O standards (e.g., LVDS, Mini_LVDS, RSDS, PPDS, BLVDS, and…...

PBR材质纹理下载

03:10 按照视频里的顺序 我们从第6个网站开始倒数 点击本行文字或下方链接 进入查看 6大网站地址 网址查看链接&#xff1a; http://www.uzing.net/community_show-1962-48-48-35.html 06 Tectures Wood Fence 001 | 3D TEXTURES 简介&#xff1a;最大的纹理网站之一&#x…...

mac PyCharm 使用conda环境

1 使用conda创建虚拟环境 conda create -n test6 python3.9 -y conda activate test62 选择conda环境 本地 选择已经存在的conda环境 右下角会显示现在的环境。...

10个常用的正则表达式

1 电话号码 let r1 /^1[3-9]\d{9}$/g console.log(r1.exec(18596932371)) 2 qq号 let r2 /^[1-9][0-9]{4,9}$/g console.log(r2.exec(123456)) 3 十六进制的方式表示颜色 let r3 /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g // # 可能可有可无&#xff0c;如果不需要#&a…...

对一手游的自定义 luajit 字节码的研究

对一手游的自定义 luajit 字节码的研究 前言 最近闲下来之后无聊研究起了一个unity手游 大量使用了 lua &#xff08;或者说就是 lua 写的 &#xff09; 看到网上已有的一些针对方案 都觉得太不方便 于是深入研究了一下 他自定义的 luajit 情况研究 首先 这是一个 unity的 传…...

1125. 牛的旅行 (Floyd算法,最短路)

1125. 牛的旅行 - AcWing题库 农民John的农场里有很多牧区&#xff0c;有的路径连接一些特定的牧区。 一片所有连通的牧区称为一个牧场。 但是就目前而言&#xff0c;你能看到至少有两个牧区不连通。 现在&#xff0c;John想在农场里添加一条路径&#xff08;注意&#xff…...

oracle “Interested Transaction List”(ITL)的概念

“Interested Transaction List”&#xff08;ITL&#xff09;的概念。让我们逐点理解&#xff1a; 块头和ITL&#xff1a; 每个数据库段块的块头都包含一个Interested Transaction List&#xff08;ITL&#xff09;。ITL用于确定数据库开始修改块时某个事务是否未提交。 ITL的…...

kali下-MSF-ftp_login模块破解FTP账号及密码

一、环境准备 两台设备在同一个网络内 一台kali系统&#xff1a;192.168.10.128 一台winserver2016&#xff1a;192.168.10.132 二、MSF介绍 metasploit 全称是The Metasploit Framework&#xff0c;又称MSF&#xff0c;是Kali 内置的一款渗透测试框架&#xff0c;也是全球…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

Android Wi-Fi 连接失败日志分析

1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分&#xff1a; 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析&#xff1a; CTR…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...