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

completeUnitOfWork


1 )概述

  • 各种不同类型组件的一个更新过程对应的是在执行 performUnitOfWork 里面的 beginWork 阶段
  • 它是去向下遍历一棵 fiber 树的一侧的子节点,然后遍历到叶子节点为止,以及 return 自己 child 的这种方式
  • performUnitOfWork 里面,还有一个方法叫做 completeUnitOfWork
  • completeUnitOfWork 中会根据是否有中断,调用不同的一个处理方法
  • 什么叫中断呢?
    • 就是我们在渲染一个组件的时候,出现了错误的场景进行处理
    • 或者优先级被更高优先级打断,低优先级转移到下一次处理
  • renderRoot 当中, 使用try catch去调用 workLoop 去循环每一个节点的
  • workLoop 里面调用的就是 performUnitOfWork
  • performUnitOfWork 里面调用了 beginWork
  • 它里面就是执行各种不同组件的一个update的一个过程
  • 在后面 completeUnitOfWork ,是处理到一侧的子节点遍历到最下层的时候,它没有子节点可以返回了
  • 因为 beginWork 里面调用了各种update,它们都是return自己的child
  • 比如说,有这么一棵 fiber 树的一个情况的时候
  • 我们先去对 RootFiber 执行了 beginWork,然后执行了之后 return 的是它的 child,也就是 App
  • 一层一层这么执行下来之后,到了有分叉节点,也就是说一个节点,下面有多个子节点的情况
  • 它 return 的还是它的第一个 child 节点,所以这个时候,后面的节点是没有被执行到更新的
  • 因为 inpput 它返回了之后,返回的是它里面的 input child 这个节点
  • 到这里为止,这个 dom 的 input 节点,已经没有child了,这个时候就 return null
  • 因为它的 child是 null,所以在这里首先执行的是 beginWork
  • 如果有child,它就会一直往child方向进行一个查找
  • 等到 next 等于 null 的情况的时候,就会去执行 completeUnitOfWork
  • completeUnitOfWork 就是对当前这个input界定 执行它的complete,具体执行什么
  • beginWork 的过程当中,如果我们去update某一个节点的时候,有报错了
  • 或者是 throw 了一个 promise, 就是 suspend 的情况, 它会执行一定的标记
  • renderRoot 当中,我们可以看到它如果有报错,它的catch是要在 renderRoot 里面被捕获的这个错误
  • 对这个错误补获之后,它是有一定的处理的, 处理完之后,它仍然处于这个 while 循环当中,它里面并没有 break
    • 位置: packages/react-reconciler/src/ReactFiberScheduler.js#L1276
  • 这个时候它还是会往下进行一个执行的,只不过它会在里面给我们当前这个节点去执行一个标记
  • completeUnitOfWork 的阶段,会对有各种不同标记的一个节点,执行不同的方法
  • 主要的是我们正常流程走的是叫 completeWork 这么一个方法
  • 而对于有异常的节点,它的一个流程是调用一个叫 unwindWork 的方法
  • 然后会判断是否有兄弟节点来执行不同的操作
    • 比如执行到上图 input 这个节点的 completeUnitOfWork之后,它没有兄弟节点
    • 它直接去执行它的父节点,也就是 Input 组件的 completeUnitOfWork
    • 在这里,我们发现它是有兄弟节点的,因为第一次update的过程,是往下的
    • 对于旁边的 List 节点是没有任何更新的, 这个时候我们就会返回它的兄弟节点 List
  • 对于这个兄弟节点, 继续对它执行一个update的一个过程,也就是 beginWork 的一个过程
  • 这就是在 completeUnitOfWork 里面非常重要的一个判断条件
    • 就是如果有兄弟节点,它会直接返回这个兄弟节点
  • 完成节点之后要赋值整个effect链
    • effect是非常重要的,后面即将要把所有的更新结束的节点,挂载到真正的dom上面
    • 这一个阶段叫做commit阶段,它要去执行每一个节点不同的跟dom有关的操作
    • 在前面 beginWorkcompleteUnitOfWork,对每一个节点的更新过程标记了
    • 有哪些 SideEffect 最终要被commit的一个过程
    • completeUnitOfWork 里面会完成这一步,把所有的effect节点进行一个串联
    • commitWork 的阶段可以非常方便的根据这个链去执行每一个节点的最终的操作

2 ) 源码

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L939
找到 completeUnitOfWork

// 这个API 是在 workInProgress 的基础上进行的一个操作
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
  // Attempt to complete the current unit of work, then move to the
  // next sibling. If there are no more siblings, return to the
  // parent fiber.

  // 进来首先就是一个 while true 的一个循环
  while (true) {
    // The current, flushed, state of this fiber is the alternate.
    // Ideally nothing should rely on this, but relying on it here
    // means that we don't need an additional field on the work in
    // progress.
    // 对于这个循环, 它首先获取current
    const current = workInProgress.alternate;
    if (__DEV__) {
      ReactCurrentFiber.setCurrentFiber(workInProgress);
    }
    // 然后 获取 returnFiber 和 siblingFiber,也就是它的父节点以及它的兄弟节点
    // 这个在后面我们判断是否要返回兄弟节点的时候就会用到
    const returnFiber = workInProgress.return;
    const siblingFiber = workInProgress.sibling;

    // 这里首先一进来就有一个大的判断,它是一个整个方法里面最大的一个判断
    // Incomplete 就是这个节点它是出现了错误,然后被捕获的, 并标记这个 sideEffect
    // 逻辑与操作来判断某一个属性上面它是否有某一个特性的一个方式
    if ((workInProgress.effectTag & Incomplete) === NoEffect) {
      if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
        // Don't replay if it fails during completion phase.
        mayReplayFailedUnitOfWork = false;
      }
      // This fiber completed.
      // Remember we're completing this unit so we can find a boundary if it fails.
      nextUnitOfWork = workInProgress;
      if (enableProfilerTimer) {
        if (workInProgress.mode & ProfileMode) {
          startProfilerTimer(workInProgress);
        }
        nextUnitOfWork = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );
        if (workInProgress.mode & ProfileMode) {
          // Update render duration assuming we didn't error.
          stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
        }
      } else {
        nextUnitOfWork = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );
      }
      if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
        // We're out of completion phase so replaying is fine now.
        mayReplayFailedUnitOfWork = true;
      }
      stopWorkTimer(workInProgress);
      resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
      if (__DEV__) {
        ReactCurrentFiber.resetCurrentFiber();
      }

      if (nextUnitOfWork !== null) {
        // Completing this fiber spawned new work. Work on that next.
        return nextUnitOfWork;
      }

      // 接下去这个个判断,是要构造一个所有 有 SideEffect 的节点的一个链状的结构
      // 这个链状结构最终是用于 commitWork 的时候用来进行对这些有 SideEffect的节点进行 commit 的一个操作,
      // 这边它的一个判断条件,returnFiber不等于null,并且returnFiber它不是一个 Incomplete 的一个节点
      // 因为对于一个 Incomplete 的节点,它唯一可以具有的一个SideEffect,就是这个节点已经被捕获了
      // 因为对于有 Incomplete 错误的节点是不会渲染正常的子节点的
      if (
        returnFiber !== null &&
        // Do not append effects to parents if a sibling failed to complete
        (returnFiber.effectTag & Incomplete) === NoEffect
      ) {
        // Append all the effects of the subtree and this fiber onto the effect
        // list of the parent. The completion order of the children affects the
        // side-effect order.
        // 对于正常的一个情况, 首先要判断一下 returnFiber.firstEffect 是否等于 null
        // 符合判断就代表 现在这个 returnFiber 上还没有记录任何它的子节点的有副作用的子节点
        // 这个时候, 直接把当前节点的firstEffect赋值给 returnFiber.firstEffect
        // 因为它之前是没有任何一个的嘛,我们这边真正要做的是把当前节点的 firstEffect 到 lastEffect的一个链条
        // 这个单项链表,给它挂载到它的父节点的同样的一个 firstEffect到lastEffect的单项链表的最后
        if (returnFiber.firstEffect === null) {
          returnFiber.firstEffect = workInProgress.firstEffect;
        }
        // 就是下面这一段判断,就是来做这个事情的, 如果returnFiber.lastEffect不等于null,那说明它已经有了
        // 那么对于returnFiber上面有记录过别的 SideEffect 的节点之后
        // 我们当前节点是挂载到整个 SideEffect 链的最后,就是下面这样,就是把它连到最后的上面
        if (workInProgress.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
          }
          // 还要操作如下,因为它的这个(returnFiber.lastEffect)指针目前还指向它原来的那个lastEffect
          // 在 赋值 nextEffect之后,它的最后一个就是这个链的最后一个已经变成 workInProgress.lastEffect,所以这边要执行这么一个操作
          // 当然这个条件是要建立在 workInProgress.lastEffect 是有值的情况
          // 这是把它们各自的firsteffect到lasteffect,这个链给它进行一个串联的过程
          returnFiber.lastEffect = workInProgress.lastEffect;
        }

        // If this fiber had side-effects, we append it AFTER the children's
        // side-effects. We can perform certain side-effects earlier if
        // needed, by doing multiple passes over the effect list. We don't want
        // to schedule our own side-effect on our own list because if end up
        // reusing children we'll schedule this effect onto itself since we're
        // at the end.
        // 对于returnFiber来说,当前这个节点也可能是有副作用的,那么这边就接下去就会做这个操作
        // 如果当前节点的 effectTag  > PerformedWork 的,因为 PerformedWork 是一个给 DEVTool 用的一个 sideEffect
        // 对于真正的react更新是没有任何意义的, 所以如果它仅仅只有 PerformedWork ,它就不是一个有效的 SideEffect 的节点
        const effectTag = workInProgress.effectTag;
        // Skip both NoWork and PerformedWork tags when creating the effect list.
        // PerformedWork effect is read by React DevTools but shouldn't be committed.
        if (effectTag > PerformedWork) {
          // 如果它有 SideEffect,就把当前节点作为父节点的 SideEffect 链的最后一个给它挂载上去
          // 或者如果是当前父节点没有任何记录的 SideEffect,它就是第一个
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = workInProgress;
          } else {
            returnFiber.firstEffect = workInProgress;
          }
          returnFiber.lastEffect = workInProgress;
        }
      }

      if (__DEV__ && ReactFiberInstrumentation.debugTool) {
        ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
      }

      if (siblingFiber !== null) {
        // If there is more work to do in this returnFiber, do that next.
        return siblingFiber;
      } else if (returnFiber !== null) {
        // If there's no more work in this returnFiber. Complete the returnFiber.
        workInProgress = returnFiber;
        continue;
      } else {
        // We've reached the root.
        return null;
      }
    } else {
      if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
        // Record the render duration for the fiber that errored.
        stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);

        // Include the time spent working on failed children before continuing.
        let actualDuration = workInProgress.actualDuration;
        let child = workInProgress.child;
        while (child !== null) {
          actualDuration += child.actualDuration;
          child = child.sibling;
        }
        workInProgress.actualDuration = actualDuration;
      }

      // This fiber did not complete because something threw. Pop values off
      // the stack without entering the complete phase. If this is a boundary,
      // capture values if possible.
      const next = unwindWork(workInProgress, nextRenderExpirationTime);
      // Because this fiber did not complete, don't reset its expiration time.
      if (workInProgress.effectTag & DidCapture) {
        // Restarting an error boundary
        stopFailedWorkTimer(workInProgress);
      } else {
        stopWorkTimer(workInProgress);
      }

      if (__DEV__) {
        ReactCurrentFiber.resetCurrentFiber();
      }

      if (next !== null) {
        stopWorkTimer(workInProgress);
        if (__DEV__ && ReactFiberInstrumentation.debugTool) {
          ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
        }

        // If completing this work spawned new work, do that next. We'll come
        // back here again.
        // Since we're restarting, remove anything that is not a host effect
        // from the effect tag.
        next.effectTag &= HostEffectMask;
        return next;
      }

      if (returnFiber !== null) {
        // Mark the parent fiber as incomplete and clear its effect list.
        returnFiber.firstEffect = returnFiber.lastEffect = null;
        returnFiber.effectTag |= Incomplete;
      }

      if (__DEV__ && ReactFiberInstrumentation.debugTool) {
        ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
      }

      // 存在 sibling 节点, 注意这边是return,也就是跳出while循环
      if (siblingFiber !== null) {
        // If there is more work to do in this returnFiber, do that next.
        return siblingFiber;
      } else if (returnFiber !== null) {
        // If there's no more work in this returnFiber. Complete the returnFiber.
        // 如果returnFiber不等于null,那么 workInProgress = returnFiber
        // 比如对于最上面示例图的 input节点,它的 completeUnitOfWork
        // 如果它没有兄弟节点,那么它就继续执行Input的 completeUnitOfWork
        // 这个循环是在 completeUnitOfWork 内部进行的一个过程
        workInProgress = returnFiber;
        continue;
      } else {
        // 如果 returnFiber 也等于null,那么它直接 return null
        // 说明已经到达顶点了,就到达 RootFiber 了,我们的更新过程呢已经完成了
        // 只需要在接下去 commitRoot 就可以了
        return null;
      }
    }
  }

  // Without this explicit null return Flow complains of invalid return type
  // TODO Remove the above while(true) loop
  // eslint-disable-next-line no-unreachable
  return null;
}
  • 对于这个while循环里面,其实它的所有代码都是在if else里面的
  • if else它们最大的一个区别是什么呢?
    • 在if里面我们可以看这边这个特殊情况
    • 就是 nextUnitOfWork 等于 completeWork,然后传入一些属性
    • 对于 else 的情况,这边调用了 next = unwindWork
    • 这就是对于一个是否有抛出过错误的一个节点,执行的一个不同的操作
  • completeWork,是没有任何一个错误的一个节点,它的一个complete的一个过程
    • 调用了completework,就是把这个节点的更新给它完成了
  • unwindWork 是有节点的错误,被捕获,如何去处理的一个过程
  • 在上面的 SideEffect 到底有什么意义呢?
  • 比如有如下场景:
    • 有个列表上有值为 1, 2, 3的三个节点
    • 点击 button 按钮,会让上面三个节点内的值乘上自身
    • 对于2,3是4和9,对于1还是1
    • 这里 1 是一个没有变化的过程
    • 按照正常逻辑来说,只需要更新原本值是 2 和 3 的节点
    • 实际上 react 里面也是这样做的
  • 参考demo如下
    import React, { Component } from 'react'
    import './App.css'
    
    class List extends Component {
      state = {
        a: 1,
        b: 2,
        c: 3,
      }
    
      handleClick = () => {
        this.setState(oldState => {
          const { a, b, c } = oldState
          return {
            a: a * a,
            b: b * b,
            c: c * c,
          }
        })
      }
    
      render() {
        const { a, b, c } = this.state
        return [
          <span key="a">{a}</span>,
          <span key="b">{b}</span>,
          <span key="c">{c}</span>,
          <button key="button" onClick={this.handleClick}>
            click me
          </button>,
        ]
      }
    }
    
    class Input extends Component {
      state = {
        name: 'wang',
      }
    
      handleChange = e => {
        // 这里如果使用方法设置`state`
        // 那么需要现在外面读取`e.target.value`
        // 因为在React走完整个事件之后会重置event对象
        // 以复用event对象,如果等到方法被调用的时候再读取`e.target.value`
        // 那时`e.target`是`null`
        this.setState({
          name: e.target.value,
        })
      }
    
      render() {
        return (
          <input
            type="text"
            style={{ color: 'red' }}
            onChange={this.handleChange}
            value={this.state.name}
          />
        )
      }
    }
    
    class App extends Component {
      render() {
        return (
          <div className="main">
            <Input />
            <List />
          </div>
        )
      }
    }
    
    export default App
    
  • 这个时候我们在 浏览器中针对 react-dom.development.js 在 commitRoot 上面打了一个断点
  • 这个断点是看最终要进行commit的时候,它是如何去获取哪几个节点是要更新的
  • 也就是我们的 SideEffect 链是怎么样的一个形式, 点一下,断点被捕获,捕获了之后
  • 可以看到它接收的两个参数,一个是root,一个是finishedWork,那么它们其实是一个对应的关系
    • root.current.alternate 就等于 finishedWork
    • 因为 finishedWork 对应的是 workInProgress
    • finishedWork 上面就会记录我们当前这一次更新,它所有需要去执行的,就是要更新到dom上面的一些内容
      • 它的记录就在 firstEffect 到 lastEffect 的一个链上
      • 它这边 firstEffect 记录的 elementType 是一个 span
      • 所谓 effect 它最终是一个fiber节点,就是告诉我们哪一个fiber节点需要更新
      • 对于我们这边第二个span,它的值是要被更新成4的
      • 所以可以看到它的 firstEffect 的第一个节点是 span
      • 它的 effectTag是 4,然后它的 updateQueen 的属性里面是一个数组 updateQueue: ["children", "4"]
      • 这个大致意思可以看出,我们要把这个span标签对应的dom节点, 它的children里面显示的文字内容,从2变成4
      • 还有就是下一个节点, 也是一个span, 看 nextEffect, 它的 elementType 也是span
      • 它的 updateQueen 是 updateQueue: ["children", "9"],也就是原值为 3 的节点
  • 对于commitWork,只会关心 firstEffect 到 lastEffect 这个链条上面的对应的 fiber 节点
    • 它只需要更新的就是这两个dom节点的children的一个变化
  • 这就是react里面的 vdom,它以最小化的程度去更新 dom
  • 而不需要对其他的任何节点再进行一个操作,以此来提升一个性能
  • 这个过程, 就是在 v16.6.3/packages/react-reconciler/src/ReactFiberScheduler.js#L1026 这个 if 里面被实现的
  • 这个 demo 示例对应最最上面的流程图来说
    • 这边点了 button之后,在这里创建了一个update
    • 就是这个List上面, 通过调用 this.setState 去创建了这个 update
    • 最终是要从 RootFiber 上面往下进行一个更新,更新到 List 中的第一个span,以及后续的span
    • 然后发现只有第2和第3个span,它们的 children从 2 变成了 4,从 3 变成了 9
    • 它们两个对应的 SideEffect 是 Update 对应值为4 (二进制的表示形式, 参考 ReactSideEffectTags.js),它们要把内容挂载到dom上面
    • 第一个span的内容因为是1乘以1,所以它没有变化,就不需要执行真正的更新
    • 首先执行 completeUnitOfWork 的是 第一个 span, 接下去它的 sibling
    • 因为第一个 span 没有 SideEffect,在这里,它的 returnFiber 就是 List
    • 当前的 workInProgress 就是这第一个 span,这个span没有任何的 SideEffect
    • 这个 List 自己也没有 SideEffect,所以赋值List的时候,对List也是没有任何的一个更新的, 如下
      if (returnFiber.firstEffect === null) {
        returnFiber.firstEffect = workInProgress.firstEffect;
      }
      
    • 这时候的 List 本身的 firstEffect 到 lastEffect是没有任何内容的,也就是null
    • 进行到第2个span节点,它自己有 SideEffect, 而它没有子节点
    • 所以它本身上面的 firstEffect到lastEffect是没有任何内容的
    • 唯一需要操作的是要把它作为List的第一个 SideEffect 增加到这个链上面
    • 因为是 Update, 值为 4 肯定是大于 Performedwork 值为 1的
      if (effectTag > PerformedWork) {
        if (returnFiber.lastEffect !== null) {
          returnFiber.lastEffect.nextEffect = workInProgress; // 情况1,标记为 L1, 以便下面引用说明
        } else {
          returnFiber.firstEffect = workInProgress; // 情况2, 标记为 L2
        }
        returnFiber.lastEffect = workInProgress; // 通用设定3,标记为 L3
      }
      
    • 它自身(workInProgress)要作为 returnFiber 的第一个 SideEffect
    • 给它增加到 List 的firstEffect到lastEffect的链上
    • 所以returnFiber的 firstEffect 和 lastEffect,都等于这个Fiber对象(第二个span) workInProgress
    • 对应的执行语句是 L2L3
    • 当第2个span执行complete完了,又要开始它的sibling, 也就是第三个span
    • 第三个span也是有更新的,所以它也要增加到 List上面
    • 这个时候List上面已经有变化,因为List.lastEffect(姑且这样表示)已经不等于null了
    • 这个时候,执行的是 else 的情况,即 L1
    • 因为在上一个节点的complete过程当中,已经指定了 firstEffect 和 lastEffect 都为 workInProgress(第2个span)
    • 所以第二个span节点的 nextEffect 指向的是第三个span的节点
    • 它们更新完之后又要更新 button,因为它没有任何的变化,所以 button 也没有更新
    • 这个时候对于List来说它的 SideEffect 链是等于 span指向span的这么一个过程
    • List的firstEffect指向第二个span对应的Fiber对象,lastEffect指向第三个span对应的Fiber对象
    • List本身执行 completeUnitOfWork 的时候,自己没有任何的更新
    • 因为它的state更新对于真正的dom操作,没有任何的关系,而且它也没有生命周期方法
    • 所以 List 后期没有任何要做的事情了, 自己本身是没有 SideEffect,只有子节点的 SideEffect
    • 在 List 执行 completeUnitOfWork 的时候, 代码中的 returnFiber 对应是 div
    • 而 workInProgress 对应的是 List, 而 List 要赋值它的 firstEffect 和 lastEffect
    • 它的链指向returnFiber, 就是要放到 returnFiber 它的链的最后面
    • returnFiber 这个 div,它目前是肯定没有任何的 SideEffect的, 所以它直接赋值成 workInProgress.firstEffect
    • 也就是说,对于 div 它的 firstEffect 跟 lastEffect 已经变成 List 的 firstEffect 跟 lastEffect
    • 因为上面节点都是没有任何更新的, 所以一层一层往上之后, 在 RootFiber 上面记录的
    • firstEffect 跟 lastEffect 也就变成了最开始的两个span
    • 所以这就是通过 completeUnitOfWork,最终能够赋值到 RootFiber 上面
    • 在它的 firstEffect 到 lastEffect,单项链表上面去记录整个应用当中
    • 所有需要去更新的最终的dom节点以及组件的fiber对象的一个过程
    • 这里涉及到后期 commitRoot 的时候, 调用这个单项链表一个过程,这里先跳过
  • 对于 unwindWork 的一个过程, 也是相似的, 先跳过 unwindWork 以及 completeWork
  • 目前只关心它的一个遍历过程,跳过具体节点做的事情, 关注它的大致流程
  • 它真正帮我们去实现了把所有需要更新的节点进行一个串联
  • 让最终 commitRoot 的时候,能够方便获取到所有需要更新的节点的一个过程

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

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

相关文章

vConsole 与 Vue中未定义变量而引发的Maximum call stack size exceeded异常问题

一、问题描述 前段时间有个前端小伙伴反馈在打包发布正式环境后调用VantUI的<van-popup>组件显示时&#xff0c;显示空白&#xff0c;并且在控制台看到一个Maximum call stacksize exceeded&#xff08;超出最大调用堆栈大小&#xff09;,而本地开发环境正常&#xff1a…

Redis应用(1)缓存(1.2)------Redis三种缓存问题

三者出现的根本原因是&#xff1a;Redis缓存命中率下降&#xff0c;请求直接打到DB上了。 一、 缓存穿透&#xff1a; 1、定义&#xff1a; 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据库。…

编程语言MoonBit新增矩阵函数的语法糖

MoonBit更新 1. 新增矩阵函数的语法糖 新增矩阵函数的语法糖&#xff0c;用于方便地定义局部函数和具有模式匹配的匿名函数&#xff1a; fn init {fn boolean_or { // 带有模式匹配的局部函数true, _ > true_, true > true_, _ > false}fn apply(f, x) {f(x)}le…

第二篇【传奇开心果短博文系列】Python的OpenCV库技术点案例示例:图像处理

传奇开心果短博文系列 系列短博文目录Python的OpenCV库技术点案例示例短博文系列 博文目录一、项目目标二、第一个示例代码三、第二个示例代码四、第三个示例代码五、第四个示例代码六、第五个示例代码七、知识点归纳总结 系列短博文目录 Python的OpenCV库技术点案例示例短博文…

阿里云 SAE 2.0 正式商用:极简易用、百毫秒弹性效率,降本 40%

作者&#xff1a;黛忻 本文主要介绍阿里云 Serverless 应用引擎&#xff08;以下简称 SAE &#xff09;如何帮助企业跨越技术鸿沟&#xff0c;从传统应用架构无感升级到 Serverless 架构&#xff0c;以更高效、更经济的方式进行转型&#xff0c;快速进入云原生快车道&#xff0…

【android】 android 里写jni

目录 &#xff08;1&#xff09; 环境准备 (2) 关联c文件到gradle文件 &#xff08;3&#xff09; 生成了 (4) 书写 &#xff08;5&#xff09; 使用 &#xff08;6&#xff09;业务调用 参考文档 &#xff08;1&#xff09; 环境准备 ndk, cmake (2) 关联c文件到gr…

实战Arthas:常见命令与最佳实践

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 微信公众号&#xff1a;Java随想录 文章目录 前言常用命令类命令getstaticjadretransform 监测排查命令monitorstackthreadtracettwatch JVM命令heapdumpjfrmemorydashboardclassloaderloggerscmbeanprofile…

《移动通信原理与应用》实验一——QPSK调制与解调实验(实验箱)

目录 一、实验目的 二、实验主要内容及原理 2.1、主要内容 2.2、基本原理 三、实验器材 3.1、移动通信原理实验箱&#xff1a; 3.2、实验框图及电路说明 四、实验步骤 五、实验过程原始记录(数据、图表、计算等) 六、实验结果及分析 1、结果分析&#xff1a; 2、思…

docker compose安装milvus

下载对应版本的milvus-standalone-docker-compose.yml wget https://github.com/milvus-io/milvus/releases/download/v2.3.5/milvus-standalone-docker-compose.yml重新命令为docker-compose.yml mv milvus-standalone-docker-compose.yml docker-compose.yml启动milvus doc…

深入理解Linux中的动态库与静态库

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;I Wish My Mind Would Shut Up—Ivoris 0:21━━━━━━️&#x1f49f;──────── 2:04 &#x1f504; ◀️ …

Java基于沙箱环境实现支付宝支付

一、支付宝沙箱环境介绍 沙箱环境是支付宝开放平台为开发者提供的安全低门槛的测试环境&#xff0c;开发者在沙箱环境中调用接口无需具备所需的商业资质&#xff0c;无需绑定和开通产品&#xff0c;同时不会对生产环境中的数据造成任何影响。合理使用沙箱环境&#xff0c;可以…

【深度学习:集中偏差】减少计算机视觉数据集中偏差的 5 种方法

【深度学习&#xff1a;集中偏差】减少计算机视觉数据集中偏差的 5 种方法 有偏差的计算机视觉数据集会导致哪些问题&#xff1f;如何减少计算机视觉数据集中偏差的示例观察并监控带注释样本的类别分布确保数据集代表模型适用的人群明确定义对象分类、标记和注释的流程为标签质…

鸿蒙开发-UI-布局-列表

鸿蒙开发-UI-布局 鸿蒙开发-UI-布局-线性布局 鸿蒙开发-UI-布局-层叠布局 鸿蒙开发-UI-布局-弹性布局 鸿蒙开发-UI-布局-相对布局 鸿蒙开发-UI-布局-格栅布局 文章目录 前言 一、基本概念 二、开发布局 1.布局约束 2.开发布局 三、应用特性 1.列表数据显示 2.列表数据迭代 3.列…

216. 组合总和 III - 力扣(LeetCode)

题目描述 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#xff0c;组合可以以任何顺序返回。 输入示例 k 3, n 7输出示例 [[1,2,…

云风网(www.niech.cn)个人网站搭建(八)服务器部署接口测试请求

将之前测试的 html 文件中http://127.0.0.1:8090/地址改成云服务器外网地址&#xff0c;同时需要安全组需要开放 8090 端口 接下来根据步骤进行测试 一、注册用户 注册请求成功&#xff0c;接下来查看数据库&#xff0c;数据库表也更新了数据 二、登录账户 账号登录成功&a…

cdn-工作笔记

问题 pc端打开login页面很慢&#xff0c;进入后切换菜单速度很快&#xff0c;但小程序打开速度正常&#xff0c;就接排除了网络和接口的问题 原因 发现是 这个接口影响了 cdn该充钱了 或者 替换一个 解决 这个项目原本用的是Staticfile CDN 想换一个 打开网址BootCDN - B…

数据仓库-相关概念

简介 数据仓库是一个用于集成、存储和管理大量数据的系统。它用于支持企业决策制定过程中的数据分析和报告需求。数据仓库从多个来源收集和整合数据&#xff0c;并将其组织成易于查询和分析的结构。 数据仓库的主要目标是提供高性能的数据访问和分析能力&#xff0c;以便…

Unity - 简单音频视频

“Test_04” 音频 使用AudioTest脚本控制Audio Source组件&#xff0c;在脚本中声明"music"和"se"之后&#xff0c;在unity中需要将音频资源拖拽到对应位置。 AudioTest public class AudioTest : MonoBehaviour {// 声明音频// AudioClippublic AudioC…

Java 基础 - 反射

Java 基础 - 反射 文章目录 Java 基础 - 反射1 什么是类对象2 获取类对象的方法3 反射通用操作【重点】3.1 常见方法3.2 通用操作 4 内省 1 什么是类对象 类的对象&#xff1a;基于某个类 new 出来的对象&#xff0c;也称为实例对象。 类对象&#xff1a;类加载的产物&#x…

ThinkPHP5.0.0~5.0.23路由控制不严谨导致的RCE

本次我们继续以漏洞挖掘者的视角&#xff0c;来分析thinkphp的RCE 敏感函数发现 在调用入口函数&#xff1a;/ThinkPHP_full_v5.0.22/public/index.php 时 发现了框架底层调用了\thinkphp\library\think\App.php的app类中的incokeMethod方法 注意传递的参数&#xff0c;Refle…