1 JavaScript 的 Event Loop(事件循环)

首先,得知道 JavaScript 是单线程的
单线程就是说,同一时间只能做一件事。那如果有特别耗时的操作,比如请求网络数据、等待用户点击按钮,总不能让程序一直卡着吧?所以就得有个机制,让这些耗时操作 "先等着",等准备好了再处理,这时候 Event Loop 就派上用场啦。
然后看 Event Loop 的核心逻辑(就是图里的 while True
部分)
代码里写的是 while True
,意思是这个循环会一直运行,不断检查有没有要处理的 "事件" 或者 "Promise 相关的代码"。
- 先看
if len(events) > 0
:events
是一个存储 "事件和对应的处理函数" 的队列。如果队列里有事件(长度大于 0),就取出队列最前面的那个事件和它的处理函数(events.pop(0)
),然后执行这个处理函数(handler(event)
)。 - 再看
const nextCodeToRun = waitingPromises[randomInteger()]; run(nextCodeToRun());
:waitingPromises
是存储 "Promise 相关待执行代码" 的地方。这里是随机从waitingPromises
里选一个待执行的代码,然后运行它(run(nextCodeToRun())
)。不过实际中,Promise 的执行顺序不是随机的,这里是为了简化讲解~
接下来讲 Promises(承诺 / 期约)
Promise 是用来处理异步操作的。
比如,当我们需要等待一个异步操作(像请求服务器数据)完成,但是在等待的时候又不能让程序卡住,这时候就用 Promise。
- 图里的注释:"A function promises call when they're waiting for something and can't progress." 翻译过来就是:当一个函数在等待某些东西(比如网络响应),没法继续往下执行的时候,就会用到 Promise。
- 函数
wait(promise)
:把传入的promise
放到waitingPromises
这个队列里(waitingPromises.push(promise)
),然后运行事件循环(runEventLoop()
)。这一步就是把需要等待的异步任务先存起来,等合适的时候再处理。
再讲 Events(事件)
事件就是当某些特定的事情发生时,要执行的操作。比如用户点击了按钮、页面加载完成等。
- 图里的注释:"We run this function when events happen!" 意思是当事件发生时,我们运行这个函数。
- 函数
notifyOfEvent(event, eventHandler)
:把事件event
和对应的处理函数eventHandler
放到events
队列里(events.push({ event, eventHandler })
)。这样,当事件循环运行到检查events
队列时,就会取出并执行这个事件处理函数。
最后看那两行关键的话
"它们像是两个独立的队列,一个用于承诺,一个用于事件"。
也就是说,waitingPromises
是专门存 Promise 相关待执行代码的队列,events
是专门存事件和对应处理函数的队列。
事件循环会不断地去检查这两个队列,有内容就拿出来执行,这样就实现了在单线程下处理异步操作和事件的能力。
总结一下,Event Loop 就像一个勤劳的小管家,不停地看有没有 "事件" 或者 "Promise 任务" 要处理,有的话就安排执行,这样 JavaScript 虽然是单线程,但也能很好地处理各种异步的情况了。

这张图讲的是 JavaScript 里的 Event Loop(事件循环),它是 JavaScript 处理各种操作(像按钮点击、输入这些)的核心机制。下面分两部分解释:
左边:Synchronous functions(同步函数)
同步函数就像图里带 "STOP" 标志的循环。
- 同步函数的特点是 "阻塞式" ,就是说,代码会 从上到下依次执行,如果一个同步函数在运行,后面的代码必须等它执行完才能继续。
- 举个例子:你写了一段代码,先执行
console.log("第一步")
,再执行console.log("第二步")
,那肯定是先打印 "第一步",再打印 "第二步",中间不会插其他操作。要是有个同步函数特别耗时(比如复杂的循环计算),那整个程序就会 "卡住",直到它执行完。
右边:Asynchronous functions(异步函数)
异步函数就像图里没有 "STOP"、还能分支出去的循环。
- 异步函数的特点是 "非阻塞式" ,当遇到异步操作(比如发送网络请求、设置定时器、监听按钮点击)时,JavaScript 不会干等着,而是会继续执行后面的代码。等异步操作完成了,再通过 Event Loop 来处理这个异步操作的结果。
- 举个例子:你设置了一个定时器
setTimeout(() => console.log("两秒后"), 2000)
,然后接着写console.log("现在")
。这时候,"现在" 会立刻打印,而 "两秒后" 会等定时器到时间后,由 Event Loop 安排执行。
总结 Event Loop 的作用
Event Loop 就像一个 "调度员",它会不断检查:
- 同步代码:按顺序执行,执行完一个再下一个。
- 异步操作:等异步操作(比如定时器到时间、网络请求返回数据)完成后,把对应的回调函数(比如定时器里的
() => console.log("两秒后")
)放到 "任务队列" 里,然后 Event Loop 会在同步代码都执行完后,从任务队列里取出回调函数执行。
这样就保证了 JavaScript 虽然是单线程(同一时间只能做一件事),但能高效处理各种操作,不会因为一个耗时的异步操作就卡住整个程序。
2 JavaScript 中 async
和 await
相关知识的总结

- 使用
await
关键字来等待 Promise 解析完成。 - 使用
async
关键字来定义异步函数。 - 必须将每个
await
包裹在async
函数内部。 - 任何用 Promise 实现的功能都可以用
async/await
语法重写 ------ 根据具体任务选用最适合的方式即可。

当我们不知道某件事会花费多长时间时,使用 async
(异步),比如:
- 客户端与服务器之间的通信(比如网页向服务器请求数据,不知道网络传输等要多久)。
- 服务器与数据库之间的通信(服务器从数据库取数据,不确定数据库操作耗时)。
- React 前端相关操作(前端处理一些可能耗时的交互、数据获取等)。
- 后台任务(程序在后台执行的、耗时不确定的任务)。
并且提到,这样做能让程序保持响应性,同时也能关注其他操作的进行,不用在之后去等待它(指异步操作)。
3 git stash
相关的工作流程

要理解 git stash
相关的工作流程,咱们可以把它想象成 "临时存放代码改动的小仓库",下面一步步详细解释,还会做些扩展。
1. git stash
:把当前改动 "藏" 起来
当你在一个分支(比如 feature
分支)上做了一些代码修改,但还没完成,也没提交(git commit
),这时候你需要去处理其他事,比如切换到 main
分支拉取最新代码(git pull
),或者去另一个分支修复 bug。
可如果直接切换分支,Git 会因为有未提交的改动而不让你顺利切换(除非强制,但容易出问题)。
这时候就用 git stash
。
- 它的作用是:把你当前工作目录里**还没保存(未
git add
)、未提交(未git commit)
**的所有代码改动,临时存到 Git 的 "stash 栈" 里,然后把工作目录恢复到 "干净" 的状态(就像你没做这些改动之前的样子)。 - 举个例子:你在
feature-login
分支改了登录页面的样式和逻辑,改到一半,领导说要紧急修复main
分支的一个 bug。你就可以git stash
,把这些未完成的登录相关改动藏起来,这样工作目录就干净了,能顺利切换到main
分支去修 bug。
2. "Do anything":自由进行其他操作
这一步就是说,现在工作目录是干净的了,你可以随便做你需要的操作:
- 切换分支(
git checkout
) :比如切换到main
分支、bugfix
分支都可以。 - 拉取远程代码(
git pull
) :在当前分支获取远程仓库的最新代码。 - 提交代码、创建分支:甚至可以在当前分支提交已经完成的工作,或者创建新的分支,都不会受到之前 "未提交改动" 的干扰。
- 扩展:其实不止这些,像执行
git merge
(合并分支)、git rebase
(变基,整理提交历史)等操作,也可以在这一步进行,因为工作目录是干净的,不会有冲突风险。
3. git stash pop
:把藏起来的改动 "取" 回来
等你把其他事情做完了,比如在 main
分支修完 bug 并提交了,现在想回到之前的 feature-login
分支继续开发。这时候就用 git stash pop
。
- 它的作用是:从 "stash 栈" 的顶部(因为
git stash
可以多次执行,每次的改动会像叠罗汉一样存在栈里)取出最上面那次 stash 的改动,把这些改动重新应用到当前的工作目录里。同时,会把这次 stash 的记录从 "stash 栈" 里移除(相当于 "取出并删除")。 - 对应图里的文字:"所有未保存、未提交的本地更改都会重新回到(工作目录的)最上层"。比如你之前藏起来的登录页面改动,现在就会重新出现在工作目录里,你可以接着之前的进度继续开发。
- 扩展:如果 stash 栈里有多个 stash 记录(比如你多次
git stash
了不同的改动),想指定恢复某一个,可以用**git stash apply stash@{n}
(n
是 stash 的序号,比如stash@{0}
是最近一次,stash@{1}
是上一次,以此类推)** 。这种方式不会删除 stash 记录,要是想删除,可以用git stash drop stash@{n}
;如果想一次性清空所有 stash 记录,用git stash clear
。
额外扩展:git stash
的其他常用命令
git stash list
:查看 stash 栈里都存了哪些 stash 记录。执行后会列出类似stash@{0}: WIP on feature-login: abc1234... 改动相关的描述
这样的内容,能清楚看到每次 stash 的信息。git stash save "描述信息"
:和git stash
类似,但可以给这次 stash 加一个自定义的描述,方便后续通过git stash list
识别。比如git stash save "login page style changes"
,这样列表里就会显示这个描述,知道这次 stash 存的是登录页面样式的改动。
简单总结整个工作流程:临时藏起未完成的改动 → 自由处理其他分支 / 操作 → 把藏的改动取回来继续开发 ,git stash
就是为了解决 "未完成改动时需要切换分支或做其他操作" 的场景,让代码管理更灵活。
4 Git相关术语和工作流程

一、Terminology(术语)
1. Staging(暂存)
- 解释:"暂存" 是 Git 里很关键的一步。当你修改了文件后,需要先把这些修改放到 "暂存区",这一步是在告诉 Git:"我准备把这些文件的改动提交(
git commit
)了"。 - 举例:比如你改了
index.js
和style.css
两个文件,用git add index.js
git add style.css
,就是把这两个文件的改动放到暂存区,标记为 "要提交的改动"。如果不暂存,直接git commit
的话,Git 不会把未暂存的改动包含到提交里。
2. Commit(提交)
- 解释:提交可以理解为 "把暂存区里的改动,保存成一个版本快照"。每个提交都对应一个特定的任务(比如 "修复登录页 bug""添加用户注册功能"),并且会有一个唯一的哈希值(像一串乱码的字符,比如
a1b2c3d
),方便追踪版本。 - 举例:你完成了登录功能的代码编写,把相关文件暂存后,执行
git commit -m "完成登录功能开发"
,这就生成了一个提交,里面包含了这次关于登录功能的所有代码改动。之后如果代码出问题,还能通过这个提交的哈希值回退到这个版本。
3. Branch(分支)
- 解释:分支就像 "代码仓库的分叉路线"。默认情况下,Git 会有一个
main
(或以前的master
)分支,作为主分支。**当你要开发新功能或者修复 bug 时,创建一个新分支(比如feature-user
或bugfix-login
),这样新分支的提交历史就会和主分支 "分叉" 开。**目的是把相关的提交(比如某个功能的所有开发提交)分组,方便管理,也避免直接在主分支上改代码带来的风险。 - 举例:要开发用户管理功能,就**
git checkout -b feature-user
创建并切换到feature-user
分支。** 在这个分支上做的所有提交,都属于 "用户管理功能" 相关,等功能开发完,再把这个分支合并(git merge
)到main
分支。
4. Remote/Origin(远程仓库 / 源)
- 解释:
Remote
指的是 "远程的 Git 仓库",通常是存放在互联网上的(比如 GitHub、GitLab 这些平台上的仓库)。 而Origin
是默认的远程仓库别名,当你用git clone
从远程仓库克隆代码到本地时,Git 会自动把远程仓库命名为origin
。 - 举例:你在 GitHub 上创建了一个仓库
my-project
,然后git clone https://github.com/yourname/my-project.git
到本地,本地仓库的origin
就指向 GitHub 上的这个远程仓库。之后你可以通过git push origin main
把本地main
分支的代码推送到远程origin
的main
分支。
5. Upstream(上游分支)
- 解释:上游分支是 "本地分支对应的远程分支",也叫 "跟踪分支(tracking branch)"。 当你把本地分支和远程分支关联后,就可以更方便地执行
git pull
(拉取远程分支代码到本地)和git push
(推送本地分支代码到远程),因为 Git 知道本地分支对应的远程分支是哪个。 - 举例:你在本地创建了
feature-user
分支,然后执行git push -u origin feature-user
,这里的-u
就是把本地feature-user
分支的上游分支设置为远程origin
的feature-user
分支。之后再执行git pull
或git push
时,不用再指定远程仓库和分支,直接git pull
或git push
就行,Git 会自动和上游分支交互。
二、General Workflows(通用工作流程)
1. Create new local branch(创建新的本地分支)
- 解释:这是开发新功能或修复 bug 的第一步,创建一个独立的本地分支,和主分支隔离,避免影响主分支的稳定。
- 命令:
git checkout -b 分支名
(比如git checkout -b feature-comment
,创建并切换到feature-comment
分支)。这样你就在新分支上工作了,主分支的代码不会受到这个分支改动的影响,除非后续合并。
2. Set upstream (Create corresponding remote branch)(设置上游分支,创建对应的远程分支)
- 解释:当你在本地创建了新分支,需要把它推送到远程仓库,让团队里的其他人也能看到或协作。第一次推送时,用
-u
参数,既会把本地分支推送到远程,也会设置上游分支关系。 - 命令:
git push -u origin 本地分支名
(比如git push -u origin feature-comment
)。执行后,远程仓库(origin
)会创建一个和本地分支同名的远程分支,同时本地分支的上游分支就设置为这个远程分支了。
3. Create commits, pull & push often(创建提交,经常拉取和推送)
- 解释:在分支开发过程中,要频繁地提交代码(把完成的小功能或修复的小 bug 做成提交),这样能保留代码的历史记录,方便回退。同时,经常执行
git pull
(拉取远程分支的最新代码,避免和队友的代码冲突)和git push
(把本地提交推送到远程,备份代码,也让队友能看到你的进度)。 - 举例:在
feature-comment
分支,你完成了评论组件的一部分代码,就git add .
(暂存所有改动),git commit -m "完成评论组件的模板部分"
,然后git pull
(看看远程有没有新提交,有的话合并过来),再git push
(把自己的提交推到远程)。
4. Merge into main (either directly or Pull Request)(合并到主分支,直接合并或通过拉取请求)
- 解释:当分支上的功能开发完成,经过测试没问题后,需要把这个分支的代码合并到
main
分支,让主分支包含新功能。- 直接合并 :如果是小团队或者个人项目,可以在本地切换到
main
分支,然后git merge 功能分支名
(比如git merge feature-comment
),解决可能的冲突后,再git push origin main
推送到远程主分支。 - Pull Request(PR,拉取请求):在团队协作中更常用。你把功能分支推送到远程后,在 GitHub、GitLab 等平台上,发起一个 "拉取请求",告诉团队里的人 "我要把这个分支合并到主分支啦"。队友可以在平台上查看你的代码改动,提出评论或建议,确认没问题后,再由有权限的人合并到主分支。这样能保证代码质量,多人协作更规范。
- 直接合并 :如果是小团队或者个人项目,可以在本地切换到