1
0
Fork 0
Browse Source

Merge remote-tracking branch 'origin/master'

master
YuJian920 2 years ago
parent
commit
ff7ce8ffc6
  1. 2
      .obsidian/appearance.json
  2. 698
      .obsidian/themes/Minimal.css
  3. 1
      FrontEnd 那些事/JavaScript - 路过.md
  4. 0
      React 的深入探索/React 源码的碎片记录.md
  5. 0
      React 的深入探索/React 的流程解析 - Fiber 递归/Fiber 数据结构.md
  6. 0
      React 的深入探索/React 的流程解析 - Fiber 递归/React 的流程解析 - Fiber 递归.md
  7. 250
      React 的深入探索/React 的流程解析 - Fiber 递归/React 的深入探索 - beginWork.md
  8. 0
      React 的深入探索/React 的流程解析 - Fiber 递归/React 的深入探索 - completeWork.md
  9. 2
      React 的深入探索/React 的流程解析 - Fiber 递归/React 的深入探索 - createWorkInProgress.md
  10. 0
      React 的深入探索/React 的流程解析 - Fiber 递归/React 的深入探索 - effectList 链表.md
  11. 0
      React 的深入探索/React 的流程解析 - commit 阶段/React 的流程解析 - commit阶段.md
  12. 0
      React 的深入探索/React 的流程解析 - commit 阶段/React 的深入探索 - commitBeforeMutationEffects.md
  13. 0
      React 的深入探索/React 的流程解析 - commit 阶段/React 的深入探索 - commitLayoutEffects.md
  14. 0
      React 的深入探索/React 的流程解析 - commit 阶段/React 的深入探索 - commitMutationEffects.md
  15. 0
      React 的深入探索/React 的流程解析 - commit 阶段/React 的深入探索 - commitRootImpl.md
  16. 0
      React 的深入探索/奇怪的疑惑.md
  17. 85
      React 的源码深入/React 的流程解析 - Fiber 递归/React 的深入探索 - beginWork.md
  18. 8
      React 的那些事/React 性能优化碎片记录.md
  19. 2
      React 的那些事/奇怪的疑惑.md
  20. 2
      React 设计模式与最佳实践/React 设计模式与最佳实践的碎片记录.md
  21. 1
      React 设计模式与最佳实践/奇怪的疑惑.md
  22. 4
      React 设计模式与最佳实践/组件实践.md
  23. 2
      数据结构与算法之美/奇怪的疑惑.md
  24. 6
      数据结构与算法之美/数据结构 - 栈.md
  25. 4
      数据结构与算法之美/数据结构 - 队列.md
  26. 3
      数据结构与算法之美/数据结构和算法的碎片记录.md
  27. 14
      数据结构与算法之美/算法之美 - 排序.md
  28. 10
      数据结构与算法之美/算法之美 - 递归.md
  29. 2
      深入探索 JavaScript/JavaScript 对象.md
  30. 2
      随时随地/Next.js 迁移日志.md
  31. 13
      随时随地/关于 bind 的使用探索.md

2
.obsidian/appearance.json

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
{
"baseFontSize": 11,
"baseFontSize": 16,
"theme": "obsidian",
"cssTheme": "Minimal"
}

698
.obsidian/themes/Minimal.css

File diff suppressed because one or more lines are too long

1
FrontEnd 那些事/JavaScript - 路过.md

@ -0,0 +1 @@ @@ -0,0 +1 @@
- 正则表达式匹配所有中文:`^[\u4e00-\u9fa5]{2}$`

0
React 的源码深入/React 源码的碎片记录.md → React 的深入探索/React 源码的碎片记录.md

0
React 的源码深入/React 的流程解析 - Fiber 递归/Fiber 数据结构.md → React 的深入探索/React 的流程解析 - Fiber 递归/Fiber 数据结构.md

0
React 的源码深入/React 的流程解析 - Fiber 递归/React 的流程解析 - Fiber 递归.md → React 的深入探索/React 的流程解析 - Fiber 递归/React 的流程解析 - Fiber 递归.md

250
React 的深入探索/React 的流程解析 - Fiber 递归/React 的深入探索 - beginWork.md

@ -0,0 +1,250 @@ @@ -0,0 +1,250 @@
>beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点根据类型创建第一个子 Fiber 节点
[代码位置](https://github.com/facebook/react/blob/bd4784c8f8c6b17cf45c712db8ed8ed19a622b26/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3818)
```javascript
function beginWork(current, workInProgress, renderLanes) {
// 前 beginWork 阶段
if (current !== null) {
......
} else {
......
}
// 正式 beginWork 阶段
switch (workInProgress.tag) {
......
}
}
```
beginWork 函数接受三个参数,分别是 current 节点,workInProgress 节点和 renderLanes 优先级,根据 beginWork 函数的结构,我们可以分成两个阶段,分别是:前 beginWork 阶段和正式 beginWork 阶段
## 前 beginWork 阶段
```javascript
if (current !== null) {
......
} else {
didReceiveUpdate = false;
}
```
在进入正式 beginWork 阶段之前,会先对传入的 current 节点进行空值判断,根据 current 是否为空进入不同的处理逻辑。
**那么为什么需要判断 current 是否为空呢?答案是为了性能,前面说到 beginWork 函数的主要任务就是给当前传入的 Fiber 节点创建它的第一个子 Fiber 节点,要是在上次更新和本次更新中当前的 Fiber 节点并没有发生变化,那么还需要再次创建一个新的 Fiber 节点吗?肯定是不需要的,所以我们只需要将这个已经存在的并没有发生变化的 Fiber 节点拿过来复用就行了,而这段逻辑正是在前 beginWork 阶段中判断并执行的**
这一段代码的主要目的是为了赋值 didReceiveUpdate 变量,这个变量表示在本次更新中当前 current 节点是否存在变化
### current 不为空
在 current 不为空的逻辑中,会先取出先前存储在 Fiber 节点中的新旧 props,连同其他几个判断条件一起做 if 判断,判断条件如下:
1. 用三等判断 props 是否发生变化
2. 检查 context 是否发生变化
3. 检查新旧 Fiber type 是否发生变化
如果判断条件为 true 则会给 didReceiveUpdate 变量赋值为 true,进入正式 beginWork 阶段
```javascript
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (
// 对比新旧 props
oldProps !== newProps ||
// 检查 context 是否发生变化
hasContextChanged() ||
// 判断 Fiber type 是否发生变化
(workInProgress.type !== current.type )
) {
didReceiveUpdate = true;
} else {
......
}
```
如果判断条件为 false,会进入后续判断逻辑
此时会先调用 checkScheduledUpdateOrContext 函数检查 current 是否存在优先级相关的更新,关于 React 优先级相关我们先暂且按表不谈,进入 if 判断
- 不存在优先级相关的更新且 workInProgress 节点不存在 DidCapture flag
- True:跳过后续的正式 beginWork 阶段,进入 baliout 也就是组件复用逻辑
- False:判断当前 current 节点是否存在 ForceUpdateForLegacySuspense flag
- True:didReceiveUpdate 赋值为 true
- False:didReceiveUpdate 赋值为 false
```javascript
// props 和 context 都没有发生变化,检查优先级相关
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);
if (
// 不存在优先级相关的更新c
!hasScheduledUpdateOrContext &&
// workInProgress 节点上不存在 DidCapture flag
(workInProgress.flags & DidCapture) === NoFlags
) {
didReceiveUpdate = false;
// 这里会跳过正式 beginWork 阶段,进入 baliout 逻辑也就是组件复用
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes
);
}
// current 节点不存在 ForceUpdateForLegacySuspense flag
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
```
### current 为空
首先 didReceiveUpdate 会被赋值为 false,紧接着会进入一个似乎关于 SSR 服务端渲染的判断逻辑,代码内容如下,这一段代码的行为还不清楚,暂时先跳过
```javascript
didReceiveUpdate = false;
// 检查 hydrate 状态 和 是否存在 Forked flag
if (getIsHydrating() && isForkedChild(workInProgress)) {
// 后续的逻辑似乎和 SSR 服务端渲染有关
// 根据官方的注释,这边的代码似乎是为了给 React 的并发模式铺路
var slotIndex = workInProgress.index;
var numberOfForks = getForksAtLevel();
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
```
至此整个前 beginWork 阶段就结束了,我们可以在这里做个小小的总结:**在前beginWork 阶段主要是判断当前组件是否发生变化需要更新, 是否可以复用,在满足复用条件情况下会跳过 正式beginWork 阶段进入 baliout 逻辑而不再创建新的 Fiber 节点,从中可以看到对于特殊组件如 Suspense 而言可能并不会进入 baliout 逻辑**
在此之外我们也可以看到一些很有意思的点:**对于组件新旧 props 对比使用的是简单的三等判断**
## 正式 beginWork 阶段
正式 beginWork 阶段开始,会将 workInProgress 的 lanes 清空,接着会进入一个 switch 逻辑,根据 tag 不同进入对应的 case 处理逻辑,代码如下:
```javascript
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
case IndeterminateComponent: ...
case LazyComponent: ...
// Function Component 处理逻辑
case FunctionComponent: ...
// Class Component 处理逻辑
case ClassComponent: ...
case HostRoot: ...
case HostComponent: ...
case HostText: ...
// Suspense 处理逻辑
case SuspenseComponent: ...
case HostPortal: ...
case ForwardRef: ...
case Fragment: ...
case Mode: ...
case Profiler: ...
case ContextProvider: ...
case ContextConsumer: ...
case MemoComponent: ...
case SimpleMemoComponent: ...
case IncompleteClassComponent: ...
case SuspenseListComponent: ...
case ScopeComponent: ...
case OffscreenComponent: ...
case LegacyHiddenComponent: ...
case CacheComponent: ...
}
```
switch 中的 case 逻辑太多了,全部都写出来会让笔记显得特别繁杂,这里只写出几个常用的处理逻辑如 HostComponent、FunctionComponent,其他处理逻辑后边有时间再另起一篇文章阐述
### updateHostRoot
首先会执行 pushHostRootContext 函数,这个函数与 context 有关,暂且不谈
然后会接着执行 cloneUpdateQueue 方法
**cloneUpdateQueue**
这个方法比较简单,会判断 current 和 workInProgress 中的 updateQueue 是否相同,如果相同会创建新的对象重复赋值以清除引用,这里是为了保证后续对 workInProgress 的操作不会影响到 current
```javascript
function cloneUpdateQueue(current, workInProgress) {
var queue = workInProgress.updateQueue;
var currentQueue = current.updateQueue;
// 用三等对比更新队列,如果为 true 表示 queue 还未清除引用
if (queue === currentQueue) {
// 清除引用
var clone = {
baseState: currentQueue.baseState,
firstBaseUpdate: currentQueue.firstBaseUpdate,
lastBaseUpdate: currentQueue.lastBaseUpdate,
shared: currentQueue.shared,
effects: currentQueue.effects
};
// 赋值
workInProgress.updateQueue = clone;
}
}
```
从 cloneUpdateQueue 函数出来回到 updateHostRoot 调用栈,会紧接着执行 processUpdateQueue 方法,这个方法和更新队列有关,暂时不展开讲
```javascript
function updateHostRoot(current, workInProgress, renderLanes) {
// context 相关
pushHostRootContext(workInProgress);
// 取出 updateQueue
var updateQueue = workInProgress.updateQueue;
if (current === null || updateQueue === null) {
throw new Error("...");
}
// 取出组件新 props
var nextProps = workInProgress.pendingProps;
// 取出组件 state
var prevState = workInProgress.memoizedState;
// 从 state 中取出 element
var prevChildren = prevState.element;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
var nextState = workInProgress.memoizedState;
var root = workInProgress.stateNode;
{
var nextCache = nextState.cache;
pushRootCachePool(root);
pushCacheProvider(workInProgress, nextCache);
if (nextCache !== prevState.cache) {
propagateContextChange(
workInProgress,
CacheContext,
renderLanes
);
}
}
......
}
```
再往后是一些针对服务端渲染的一些处理逻辑,服务端渲染也不是这次讨论的目的,也先跳过
最终调用 reconcileChildren 为 FIber 创建一个子 Fiber 节点并返回
至此一个节点的 beginWork 流程就走完了,下一次会根据是否存在子 Fiber 节点判断是执行当前 WorkInProgress Fiber 节点的 completeWork ,还是继续对子节点执行 beginWork
### beginWork
第一次触发更新,进入这个函数的逻辑前边和首屏渲染是都是一样的,但是在判断组件是否发生修改的时候逻辑和首屏渲染并不同,在首屏渲染,beginWork 会走到 switch 逻辑,并根据 Fiber tag 不同分别执行不同的 mount l逻辑,在这里会经过一些判断最终进入 attemptEarlyBailoutIfNoScheduledUpdate 这个函数,这个函数内部又有着一个 switch 逻辑,也是根据 workInProgress tag 属性分别进入不同的 case,在这个例子中进入的是 HostRoot 对应的 case,首先会执行 pushHostRootContext 函数,Context 相关 ~~不谈~~,最后继续处理服务端渲染的逻辑最后返回交由 bailoutOnAlreadyFinishedWork 函数处理,bailoutOnAlreadyFinishedWork 会对 WorkInProgress 赋上 current 的 dependencies 属性,然后就是 bailoutOnAlreadyFinishedWork 最终的目的: 调用 cloneChildFibers
### cloneChildFibers
这个函数从名字上就能看出来,目的是为了 clone Fiber 的 child 属性,这个函数会调用 createWorkInProgress 函数,从上边可以知道,这个函数可以根据传入的 current 和 WorkInProgress 进行判断是否创建一个新的 Fiber 节点或者复用已有的 current Fiber 节点,并将其互相链接,最终将结果返回,cloneChildFiber 接收到返回的 Fiber 子节点,将其链接到当前 WorkInProgress Fiber 的 child 属性上,并把 Fiber 子节点的 return 赋值 WorkInProgress Fiber
也就是说,如果在 beginWork 中当前,如果组件并没有发生变化且没有要执行的逻辑,那么他会直接 clone 已有的 Fiber 节点,而不会走到 reconcileChildren 函数创建新的 Fiber 节点
最后返回到 bailoutOnAlreadyFinishedWork 在由它返回到 beginWork,beginWork 流程执行结束

0
React 的源码深入/React 的流程解析 - Fiber 递归/React 的深入探索 - completeWork.md → React 的深入探索/React 的流程解析 - Fiber 递归/React 的深入探索 - completeWork.md

2
React 的源码深入/React 的流程解析 - Fiber 递归/React 的深入探索 - createWorkInProgress.md → React 的深入探索/React 的流程解析 - Fiber 递归/React 的深入探索 - createWorkInProgress.md

@ -74,4 +74,4 @@ switch (workInProgress.tag) { @@ -74,4 +74,4 @@ switch (workInProgress.tag) {
}
```
后返回创建好的 WorkInProgress Fiber 树,至此 createWorkInProgress 的逻辑就结束了。
后返回创建好的 WorkInProgress Fiber 树,至此 createWorkInProgress 的逻辑就结束了。

0
React 的源码深入/React 的流程解析 - Fiber 递归/React 的深入探索 - effectList 链表.md → React 的深入探索/React 的流程解析 - Fiber 递归/React 的深入探索 - effectList 链表.md

0
React 的源码深入/React 的流程解析 - commit 阶段/React 的流程解析 - commit阶段.md → React 的深入探索/React 的流程解析 - commit 阶段/React 的流程解析 - commit阶段.md

0
React 的源码深入/React 的流程解析 - commit 阶段/React 的深入探索 - commitBeforeMutationEffects.md → React 的深入探索/React 的流程解析 - commit 阶段/React 的深入探索 - commitBeforeMutationEffects.md

0
React 的源码深入/React 的流程解析 - commit 阶段/React 的深入探索 - commitLayoutEffects.md → React 的深入探索/React 的流程解析 - commit 阶段/React 的深入探索 - commitLayoutEffects.md

0
React 的源码深入/React 的流程解析 - commit 阶段/React 的深入探索 - commitMutationEffects.md → React 的深入探索/React 的流程解析 - commit 阶段/React 的深入探索 - commitMutationEffects.md

0
React 的源码深入/React 的流程解析 - commit 阶段/React 的深入探索 - commitRootImpl.md → React 的深入探索/React 的流程解析 - commit 阶段/React 的深入探索 - commitRootImpl.md

0
React 的源码深入/奇怪的疑惑.md → React 的深入探索/奇怪的疑惑.md

85
React 的源码深入/React 的流程解析 - Fiber 递归/React 的深入探索 - beginWork.md

@ -1,85 +0,0 @@ @@ -1,85 +0,0 @@
>beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点根据类型创建第一个子 Fiber 节点
[代码位置](https://github.com/facebook/react/blob/bd4784c8f8c6b17cf45c712db8ed8ed19a622b26/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3818)
```javascript
function beginWork(current, workInProgress, renderLanes) {
// 前 beginWork 阶段
if (current !== null) {
......
} else {
......
}
// 正式 beginWork 阶段
switch (workInProgress.tag) {
......
}
}
```
beginWork 函数接受三个参数,分别是 current 节点,workInProgress 节点和 renderLanes 优先级,根据 beginWork 函数的结构,我们可以分成两个阶段,分别是:前 beginWork 阶段和正式 beginWork 阶段
## 前 beginWork 阶段
```javascript
if (current !== null) {
......
} else {
didReceiveUpdate = false;
}
```
在进入正式 beginWork 阶段之前,会先对传入的 current 节点进行空值判断,根据 current 是否为空进入不同的处理逻辑。
**那么为什么需要判断 current 是否为空呢?答案是为了性能,前面说到 beginWork 函数的主要任务就是给当前传入的 Fiber 节点创建它的第一个子 Fiber 节点,要是在上次更新和本次更新中当前的 Fiber 节点并没有发生变化,那么还需要再次创建一个新的 Fiber 节点吗?肯定是不需要的,所以我们只需要将这个已经存在的并没有发生变化的 Fiber 节点拿过来复用就行了,而这段逻辑正是在前 beginWork 阶段中判断并执行的**
这一段代码的主要目的是为了赋值 didReceiveUpdate 变量,
### current 不为空
### current 为空
didReceiveUpdate 赋值为 false
## 正式 beginWork 阶段
### beginWork
>beginWork 执行在递归节点的 Fiber 创建之前,主要是为传入的 Fiber 节点根据类型创建第一个子 Fiber 节点
首先会对 current 做空值判断,因为对于首屏渲染而言,当前传入的 current Fiber 节点是 FiberNode,所以是存在的,会进入 current !== null 的逻辑,然后会判断当前 Fiber 节点是否发生变化,然后赋值 `didReceiveUpdate` 做更新标识,判断的条件如下:
1. 新旧 props 是否相同
2. Context 是否发生变化
3. Fiber 节点 type 是否发生变化
在本次首屏渲染中,以上的条件都为否,会进入后续条件判断,在后续的条件判断中主要是为了应付一些特殊的场景,例如错误边界和 legacy 模式,可以看到官方留下的代码注释,在我们现在的场景下,最终会走到 didReceiveUpdate 为 false 的情况下,也就是当前 Fiber 并没有变化,因为在之前的 createWorkInProgress 中 WorkFiberProgress 被赋值上了 current Fiber 的同名属性,所以它们会是相同的两个 FIber 节点,在这里的代码可以看到:beginWork 中还有对优先值 lanes 的一些处理,在这里先不展开,后续再补充
最后会走入 switch 环节,这了主要根据传入的 WorkInProgress Fiber 节点的类型进入不同的处理逻辑,这也是 beginWork 最主要的任务:为当前 WorkInProgress Fiber 节点创建它的第一个 Fiber 子节点,其内部根据 Fiber 的 tag 不同有对应的:
- updateClassComponent 处理 Class Component
- updateHostComponent 处理 Host Component
- updateSuspenseComponent 处理 Suspense Component
本次的是首屏渲染,传入的 WorkInProgress Fiber 为 FiberNode,所以会进入 HostRoot 的 case,执行 updateHostRoot 方法
### updateHostRoot
updateHostRoot 函数中首先会执行 pushHostRootContext 方法,这个方法和 Context 相关,现在暂且不谈
在 updateHostRoot 逻辑一开始,它会将 current 和 WorkInProgress 传递给 cloneUpdateQueue 这个函数,这个函数会将 Fiber 中的 queue 属性进行 clone,除非它们已经是 clone 之后的属性,那么具体是怎么做的判断是否是 clone 过的属性呢?其实很简单,它只是使用了 === 三个等于判断属性的引用是否相同,也就是说这个函数实际上做的是清除引用,保证在之后对 WorkInProgress 的操作不会影响到 current Fiber 树,判断两个 Fiber 树的 updateQueue 属性的引用完全相同,那么就会创建一个新的对象并重复赋值达到清除应用的目的,最后赋值给 WorkInProgress Fiber 节点
之后,再把 WorkInProgress Fiber 节点交给 processUpdateQueue 函数,这个函数主要是处理更新队列,在这里先不展开,TODO
再往后是一些针对服务端渲染的一些处理逻辑,服务端渲染也不是这次讨论的目的,也先跳过
最终调用 reconcileChildren 为 FIber 创建一个子 Fiber 节点并返回
至此一个节点的 beginWork 流程就走完了,下一次会根据是否存在子 Fiber 节点判断是执行当前 WorkInProgress Fiber 节点的 completeWork ,还是继续对子节点执行 beginWork
### beginWork
第一次触发更新,进入这个函数的逻辑前边和首屏渲染是都是一样的,但是在判断组件是否发生修改的时候逻辑和首屏渲染并不同,在首屏渲染,beginWork 会走到 switch 逻辑,并根据 Fiber tag 不同分别执行不同的 mount l逻辑,在这里会经过一些判断最终进入 attemptEarlyBailoutIfNoScheduledUpdate 这个函数,这个函数内部又有着一个 switch 逻辑,也是根据 workInProgress tag 属性分别进入不同的 case,在这个例子中进入的是 HostRoot 对应的 case,首先会执行 pushHostRootContext 函数,Context 相关 ~~不谈~~,最后继续处理服务端渲染的逻辑最后返回交由 bailoutOnAlreadyFinishedWork 函数处理,bailoutOnAlreadyFinishedWork 会对 WorkInProgress 赋上 current 的 dependencies 属性,然后就是 bailoutOnAlreadyFinishedWork 最终的目的: 调用 cloneChildFibers
### cloneChildFibers
这个函数从名字上就能看出来,目的是为了 clone Fiber 的 child 属性,这个函数会调用 createWorkInProgress 函数,从上边可以知道,这个函数可以根据传入的 current 和 WorkInProgress 进行判断是否创建一个新的 Fiber 节点或者复用已有的 current Fiber 节点,并将其互相链接,最终将结果返回,cloneChildFiber 接收到返回的 Fiber 子节点,将其链接到当前 WorkInProgress Fiber 的 child 属性上,并把 Fiber 子节点的 return 赋值 WorkInProgress Fiber
也就是说,如果在 beginWork 中当前,如果组件并没有发生变化且没有要执行的逻辑,那么他会直接 clone 已有的 Fiber 节点,而不会走到 reconcileChildren 函数创建新的 Fiber 节点
最后返回到 bailoutOnAlreadyFinishedWork 在由它返回到 beginWork,beginWork 流程执行结束

8
React 的那些事/React 性能优化碎片记录.md

@ -1,7 +1,5 @@ @@ -1,7 +1,5 @@
父组件命中性能优化,子组件才有可能命中性能优化
该如何比较 props
- 父组件命中性能优化,子组件才有可能命中性能优化
- 该如何比较 props
1. 全等比较 --- 高效、不易命中
2. 浅比较 --- 不高效,易命中
没有传递 props 的时候,子组件接收到的 props 实际上是一个空对象
React.memo 的作用和实际发生了什么
useMemo 的前提是当前组件必须是浅比较,为什么?
- 没有传递 props 的时候,子组件接收到的 props 实际上是一个空对象

2
React 的那些事/奇怪的疑惑.md

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
- [ ] React.memo 的作用和实际发生了什么?
- [ ] useMemo 的前提是当前组件必须是浅比较,为什么?

2
React 设计模式与最佳实践/React 设计模式与最佳实践的碎片记录.md

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
- 组件最好是独立存在,与其他代码之间的纠葛越少越好。
- 组件可以用于实现副作用,而并不一定需要显示页面内容。

1
React 设计模式与最佳实践/奇怪的疑惑.md

@ -0,0 +1 @@ @@ -0,0 +1 @@
- [ ] 为什么说父组件直接调用子组件的函数,会让执行过程处于生命周期以外

4
React 设计模式与最佳实践/组件实践.md

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
在设计 React 组件时,要注意以下原则:
1. 保持接口小,props 数量要少;
2. 根据数据边界来划分组件,充分利用组合 (composition);
3. 状态提升,让下层组件尽量只是一个纯函数

2
数据结构与算法之美/奇怪的疑惑.md

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
- [ ] 什么是排序算法的稳定性
- [ ] 什么是原地排序算法

6
数据结构与算法之美/数据结构 - 栈.md

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
>栈是一种逻辑结构,从栈的操作特性来看,它是一种“操作受限”的线性表。
>后进者先出,先进者后出,这就是典型的“栈”结构。
当某个数据集合只涉及在一端插入和删除,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构。
由于栈只是一个逻辑结构,所以它既可以用数组实现,也可以用链表来实现,数组实现的栈,叫做**顺序栈**,而链表实现的栈,叫做**顺序栈**。

4
数据结构与算法之美/数据结构 - 队列.md

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
>队列跟栈一样,也是一种**操作受限的线性表数据结构**
用数组实现的队列叫作**顺序队列**,用链表实现的队列叫作**链式队列**。
## 用两个栈实现一个队列
```javascript

3
数据结构与算法之美/数据结构和算法的碎片记录.md

@ -3,4 +3,5 @@ @@ -3,4 +3,5 @@
- 凡有序,必二分
- 凡二分,时间复杂度必包含 O(logn)
- 优化嵌套循环,可以考虑双指针
- 二叉搜素树可以使用二分法快速查找
- 二叉搜素树可以使用二分法快速查找
- 分治算法一般都是用递归来实现的。分治是一种解决问题的处理思想,递归是一种编程技巧

14
数据结构与算法之美/算法之美 - 排序.md

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
对于排序算法执行效率的分析,我们一般会从这几个方面来衡量:
1. 最好情况、最坏情况、平均情况时间复杂度
2. 时间复杂度的系数、常数 、低阶
3. 比较次数和交换(或移动)次数
## 冒泡排序
## 插入排序
## 选择排序
## 归并排序 (Merge Sort)
## 快速排序 (Quick Sort)

10
数据结构与算法之美/算法之美 - 递归.md

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
只要同时满足以下三个条件,就可以用递归来解决:
1. 一个问题的解可以分解为几个子问题的解
2. 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
3. 存在递归终止条件
写递归代码最关键的是写出递推公式,找到终止条件
编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤
递归代码虽然简洁高效,但是,递归代码也有很多弊端。比如,堆栈溢出、重复计算、函数调用耗时多、空间复杂度高等,所以,在编写递归代码的时候,一定要控制好这些副作用。

2
深入探索 JavaScript/JavaScript 对象.md

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
## 固有对象
>固有对象在任何 JS 代码执行就已经被创建出来了,它们通常扮演着类似基础库的角色,“类”其实就是固有对象的一种。
>固有对象在任何 JS 代码执行就已经被创建出来了,它们通常扮演着类似基础库的角色,“类”其实就是固有对象的一种。
固有对象构造器创建的对象多数使用了私有字段,这些字段使得原型继承方法无法正常工作,这里的无法正常工作指的是无法继承这一些私有字段,所以我们可以认为所有这些原生对象都是为了特定能力或者性能而设计出来的 “特权对象”。

2
随时随地/Next.js 迁移日志.md

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
- less 迁移 scss
- \_app.tsx

13
随时随地/关于 bind 的使用探索.md

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
在看 Antd 的源码时看到下面这一段,是有关 bind 的使用方法,代码不多但是深入研究的话还是有不少知识盲区的
```typescript
const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/;
const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar);
isTwoCNChar("提交")
```
1. 为什么需要使用 `bind` 方法改变作用域?
`rxTwoCNChar.test` 这一行赋值相当于 `RegExp.prototype.test` 直接从 RegExp 原型中取 test 方法赋值 ,而不是通过 RegExp 实例 `rxTwoCNChar` 中调用函数,所以执行时的作用域并不会指向 `rxTwoCNChar`,需要使用 `bind` 函数生成并返回一个具有指定作用域的新函数。
1. 不使用 bind 时 `rxTwoCNChar.test` 作用域指向谁?
1. 严格模式下 `this` 指向 `undefined`
2. 非严格模式下 `this` 指向 `global`
Loading…
Cancel
Save