-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.json
1 lines (1 loc) · 44 KB
/
index.json
1
[{"categories":null,"content":"2024年 11 月 24 号,并不像往常的周日一样躺在床上睡懒觉,这是一个令人期待的早晨。昨天晚上定了今天早上八点的闹钟,准时起床,洗脸刷牙,随后吃了一片披萨🍕,换上过时的老款跑鞋,准备下楼做热身运动。 对,今天准备挑战一下自己跑个十公里,这将是我第一个十公里慢跑,心里还是有一点点的兴奋和激动。从十月份开始想要培养一个运动的爱好,一方面可以强身健体,另一方面还能把自己从繁忙的工作中抽离出来。所以选择了开始慢慢跑步,上个月只跑了三次,但还是对跑步这项运动有了兴趣,没什么成本,也不需要刻意学习和训练,唯一的成本也就是时间了。由于通勤时间比较长,工作日加班次数也不固定,所以决定早起晨跑,不过早起确实比较困难,但是当我第一天早起时还挺兴奋的,六点半的闹钟开始震动,脑子里是又困又清醒。 于是我就开启了跑步的旅程。 这周二和周四分别跑了 6.01 公里和 4.91 公里,心率一直很高,平均心率冲到了 170 ,让我有点不知所措,上周跑的几次平均心率都在 150 左右,后来咨询了一下沨哥的建议,让我不要太在意心率,也有可能是天气影响的,冬天降温时跑步心率也会增高,放慢速度让心率慢慢降下来就好了,不要刻意追求速度,跑前热身更久一些,还分享了很专业的拉伸动作,感谢沨哥。带着这些疑惑调整了心态,开始了今天的十公里。 热身完毕,蓄势待发,跑起来之后看着心率还是在 170 左右,但是体感舒适,所以也就没放在心上。 继续跑,今天的耳机里播着法兹的新专辑《东方101 与未来马场》,这张旧曲新编的合辑,已经霸占了我最近播放量的前列,非常有诚意的一张新作品,把以前的老歌重新制作,赋予了新的能量和生命力,编曲也更加浑厚和丰富,贝斯简直是太拨动我的心弦了。回想四年前在杭州第一次看法兹的现场,被深深震撼,已经迫不及待想要看下个月在摩登天空的演出了。关于新专辑的详情介绍:过往十五年间,我们只是微不足道的烟尘,迷失、搏击、幻想成为一体。握着彼此手,化身一粒石子奋力坠入水中,涟漪如生命般荡开。告诉自己,继续走,再慢一点。虽未到达草原,但我们的歌谣从不单调贫瘠。 2020.08.20 法兹演出后的现场\" 2020.08.20 法兹演出后的现场 跑了差不多七公里的时候,配速还是七分多钟,心率也一直没降下来,我就抬起胳膊保持静止,盯着手环上的数据四五秒钟,我惊人地发现心率在直线下降至 135 左右,令我非常诧异,心想是不是手环有 bug 啊!然后我就突然兴奋起来,不管三七二十一加速往前冲,跑到了 616 的配速,兴奋劲儿还没降下来就一直跑,跑着跑着膝盖有点不舒服也直接忽略不计了,继续跑完剩下的两公里就完成了今天的目标,跑着跑着感觉膝盖又没有不舒服了,觉得自己能就这么一直跑下去,左右脚轮流向前一直循环。最后两圈跑到了 552 的配速,终于,第一个十公里解锁成功。心里有了莫大的满足和开心,跑完之后开始做拉伸活动,回到家里冲完澡,吃了鸡蛋、牛奶、披萨补充能量,开启了精神抖擞的一天。 希望自己能够坚持一直跑下去,为自己加油。 我很想知道,跑步的真谛究竟是什么。 我也是,灰二哥,我也想知道,虽然我一直在跑,但现在我还是不知道这个问题的答案。直到现在,我跑步时都仍会思考这个问题,今后也会不停问自己。 我真的很想知道。 所以,让我们一起跑吧,跑到天涯海角。 信念发出的光芒,永远存在我们心里。在黑暗中照亮延伸向前的道路,清楚地为我们指引方向。 ———《强风吹拂》 以前我总是会有迷茫和不知所措的时候,也想要一直寻找一个答案,但是随着岁月流逝,后知后觉的我才意识到,所有的问题好像并没有什么标准答案,只要我们不停下脚步,一直向前走,迟早都会发现,答案也许就在风中飘扬着。 心率/配速\" 心率/配速 ","date":"2024-11-28","objectID":"/posts/take-up-running/:0:0","tags":["跑步"],"title":"十公里","uri":"/posts/take-up-running/"},{"categories":null,"content":"上传静态资源 首先把本地的一个 index.html 文件上传至服务器,然后发布进行访问。 首先进入到 root 目录下新建 www 文件夹 cd /root \u0026\u0026 mkdir www 然后在本地执行以下命令,将 index.html 文件上传至 /root/www 目录下: // 上传本地文件 scp index.html [email protected]:/root/www 其他文件传输操作: 上传本地目录到服务器:scp -r dist [email protected]:/root/www 从服务器下载文件:scp [email protected]:/root/www/index.html 从服务器下载目录:scp -r [email protected]:/root/www /var/www ","date":"2023-03-28","objectID":"/posts/nginx-conf/:0:1","tags":["nginx"],"title":"使用 nginx 部署一个 hello world","uri":"/posts/nginx-conf/"},{"categories":null,"content":"安装 nginx 执行命令以检查 yum 源中是否存在 nginx 包: yum list nginx 已安装的示例图\" 已安装的示例图 使用 yum 安装 nginx: yum install -y nginx 出现 Complete! 提示即安装成功: 已安装的示例图\" 已安装的示例图 此时我们可以通过浏览器访问服务器地址,会看到 nginx 默认页面,如下: nginx 默认页面\" nginx 默认页面 ","date":"2023-03-28","objectID":"/posts/nginx-conf/:0:2","tags":["nginx"],"title":"使用 nginx 部署一个 hello world","uri":"/posts/nginx-conf/"},{"categories":null,"content":"配置 nginx 执行以下命令进入 nginx 目录,并修改配置: cd /etc/nginx vim nginx.conf 在 server 中配置静态服务 location ,同时 nginx 还可以对网站的静态资源在传输前进行压缩,提高页面加载速度。经过 gzip 压缩后页面大小可以变为原来的 30% 甚至更小。使用时仅需开启 gzip 压缩功能即可。完整配置如下: user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] \"$request\" ' '$status $body_bytes_sent \"$http_referer\" ' '\"$http_user_agent\" \"$http_x_forwarded_for\"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; # 开启gzip压缩功能 gzip on; # 设置允许压缩的页面最小字节数; 这里表示如果文件小于10k,压缩没有意义. gzip_min_length 10k; # 设置压缩比率,最小为 1,处理速度快,传输速度慢; # 最大压缩比可设置为 9,但是处理速度较慢,推荐设置为 6 gzip_comp_level 6; # 设置需要压缩的文件, 通常情况下文本、css、js建议压缩 gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { root /root/www; index index.html index.htm; try_files $uri $uri/ /index.html; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } } 配置完成之后,执行 nginx -s reload 重启 nginx 后通过浏览器访问,发现报 500 内部服务器错误: 500 Internal Server Error\" 500 Internal Server Error ","date":"2023-03-28","objectID":"/posts/nginx-conf/:0:3","tags":["nginx"],"title":"使用 nginx 部署一个 hello world","uri":"/posts/nginx-conf/"},{"categories":null,"content":"排查问题 通过上文中配置可以看到,第 3 行中的错误日志路径为:error_log /var/log/nginx/error.log; 此时我们通过命令查看 cat /var/log/nginx/error.log 错误日志,由 Permission denied 可以看出是权限问题导致。 权限错误\" 权限错误 1⃣️ 首先,我们通过 ps aux | grep nginx 命令查看当前 nginx 的运行进程: 当前进程\" 当前进程 2⃣️ 其次,我们修改上文配置中的第一行: # 将用户名 nginx 改为 root user root; 3⃣️ 最后,执行 nginx -s reload 重启 nginx ,再次打开页面看到已经可以正常访问: hello-world\" hello-world ","date":"2023-03-28","objectID":"/posts/nginx-conf/:0:4","tags":["nginx"],"title":"使用 nginx 部署一个 hello world","uri":"/posts/nginx-conf/"},{"categories":null,"content":"常用命令 nginx 启动服务 nginx -s reload 重启,每次修改配置文件后都需要重新启动才能生效 nginx -s stop 或 nginx -s quit 关闭服务 以上📝 ","date":"2023-03-28","objectID":"/posts/nginx-conf/:0:5","tags":["nginx"],"title":"使用 nginx 部署一个 hello world","uri":"/posts/nginx-conf/"},{"categories":null,"content":"Git 版本仓库提交过多,会导致项目根目录下的 .git 文件夹体积巨大,原因可能不只是单纯的提交历史过多,而是历史提交中包含有对大文件的引用,即使现在的项目中已经不存在这些文件了,但其引用关联依旧会被 git 保留下来。 ","date":"2022-12-20","objectID":"/posts/git-slim/:0:0","tags":["git"],"title":"清理 Git 文件夹过期引用","uri":"/posts/git-slim/"},{"categories":null,"content":"踩坑方案一 git filter-branch 命令可以改写历史中大量的提交,但是它有很多陷阱,而且官方文档中已经不推荐使用它来重写历史了,当然,这个坑是我踩过之后才知道的。 ","date":"2022-12-20","objectID":"/posts/git-slim/:1:0","tags":["git"],"title":"清理 Git 文件夹过期引用","uri":"/posts/git-slim/"},{"categories":null,"content":"识别查询大文件 找到项目根目录下的 .idx 文件,路径: .git/objects/pack/pack-************.idx 执行: git rev-list --objects --all | grep -f \u003c(git verify-pack -v .git/objects/pack/******.idx| sort -k 3 -n | cut -f 1 -d \" \" | tail -10) 运行结果大致如下: c0b33abdf3af4f0a4ae82d6243954eeb344432d9 src/components/Emoji/emoji.png a08b3b0f766d26729cbaf0b7e86212b0ca4a5569 dist/js/2a10361c.async.js 1d26f0da81c885c676badb026367a47183013fb5 dist/js/84ad94bf.async.js eb07071cdea7e019953a3a6778a4bb6e728ea13d dist/js/4922a65f.async.js 7ddf057a0e26f300137c84cf03dbe088a69da488 dist/js/62f9d99c.async.js ","date":"2022-12-20","objectID":"/posts/git-slim/:1:1","tags":["git"],"title":"清理 Git 文件夹过期引用","uri":"/posts/git-slim/"},{"categories":null,"content":"删除文件 将该文件从历史记录的所有 tree 中移除,执行: git filter-branch --index-filter 'git rm --cached --ignore-unmatch src/components/Emoji/emoji.png' 注:需要依次执行该命令去删除这些大文件,可能会有很多 jpg/js.map/gif 等类型的文件。 ","date":"2022-12-20","objectID":"/posts/git-slim/:1:2","tags":["git"],"title":"清理 Git 文件夹过期引用","uri":"/posts/git-slim/"},{"categories":null,"content":"推送到远程 rm -rf .git/refs/original/ git reflog expire --expire=now --all git fsck --full --unreachable git repack -A -d git gc --aggressive --prune=now git push --force 依次执行完以上命令后,此时去检查远程仓库,结果令人诧异,居然…没有…生效…本地确实是清理掉了一部分空间,从 1.2G 瘦身到 600M 左右,但是推送后并没有作用,于是继续寻找其他方案,结果看到 stackoverflow 上有一个一摸一样的问题,按 Date modified (newest first) 排序之后,看到👇 \" 也就是说 2022 了,git filter-branch 已经不好使了,推送到远程之后没有任何作用,方案一以失败告状。 ","date":"2022-12-20","objectID":"/posts/git-slim/:1:3","tags":["git"],"title":"清理 Git 文件夹过期引用","uri":"/posts/git-slim/"},{"categories":null,"content":"终极方案二 git 官方文档推荐了这个库: git-filter-pro ,它是一个可以重写 git 历史的多功能 Python 脚本,即使现在在项目当中找不到那些历史文件,它依然可以找到它们并进行清理(看来这才是我想要的东西)。 ","date":"2022-12-20","objectID":"/posts/git-slim/:2:0","tags":["git"],"title":"清理 Git 文件夹过期引用","uri":"/posts/git-slim/"},{"categories":null,"content":"安装 brew install git-filter-repo ❌ 报错 No such file or directory @ rb_sysopen -xxxxx ✅ 解决方案 原因是使用国内镜像但是该镜像未完全同步的问题,临时去除镜像即可: export HOMEBREW_BOTTLE_DOMAIN='' ","date":"2022-12-20","objectID":"/posts/git-slim/:2:1","tags":["git"],"title":"清理 Git 文件夹过期引用","uri":"/posts/git-slim/"},{"categories":null,"content":"使用 这里可以搭配此命令来找到那些历史中的大文件,然后逐一清理。 git rev-list --objects --all | grep -f \u003c(git verify-pack -v .git/objects/pack/******.idx| sort -k 3 -n | cut -f 1 -d \" \" | tail -10) 假设要清理全部的 *.jpg 文件,只需要如下命令,需要注意的是这样也会把项目中现有的所有 .jpg 文件清理掉。 git filter-repo --path-glob '*.jpg' --invert-paths 之后,强制推送到远程👇 git push --all --force git push --tags --force 废了一些功夫,效果一级棒👍 清理前\" 清理前 清理后\" 清理后 参考链接: Git-工具-重写历史 git-filter-repo ","date":"2022-12-20","objectID":"/posts/git-slim/:2:2","tags":["git"],"title":"清理 Git 文件夹过期引用","uri":"/posts/git-slim/"},{"categories":null,"content":"vue 相关 mixin 相关场景以及利弊,拥有多个属性时的执行顺序, v-model 原理,在下拉选择器组件中是怎么实现的 filter 怎么实现,使用场景 插槽的实现原理,如何传值 v-for 中 key 的作用,延伸到 diff 实现,不同位置的比较 vnode 概念 v-if 控制 dom 显隐时, vdom 是如何处理的 watch 监听是的 deep: true 实现原理 nextTick 原理 computed 和 watch 的区别 Vue.use 和 Vue.install Vue 中手动 extend data 中的属性是定义在 MVVM 中的哪个环节 ","date":"2022-07-13","objectID":"/posts/interview/:0:1","tags":["notes"],"title":"面经记录","uri":"/posts/interview/"},{"categories":null,"content":"css 相关 回流和重绘的概念,绝对定位是否会导致回流 flex: 1 的分别代表什么属性以及作用 伪元素和伪类的区别,分别有哪些 visibility: hidden 和 opacity 是否触发点击事件 ","date":"2022-07-13","objectID":"/posts/interview/:0:2","tags":["notes"],"title":"面经记录","uri":"/posts/interview/"},{"categories":null,"content":"js 相关 let/const/var 区别,用 const 声明对象时,是否可以改变对象的属性值,为什么 深度优先搜索和广度优先搜索 如何证明 js 对象里存在循环引用 类型判断的方法,typeof 对象的类属性 实例对象是否是 Function ,instanceof Function 结果 promise 执行顺序,事件循环 es5 模拟 class 的实现 localStorage 如何实现过期时间 手机号正则 ","date":"2022-07-13","objectID":"/posts/interview/:0:3","tags":["notes"],"title":"面经记录","uri":"/posts/interview/"},{"categories":null,"content":"工程化相关 介绍 webpack 常用的配置以及功能 webpack 多页面有多个入口时如何配置 source-map 分类,如何配置和调试 webpack 中怎么隔离 css 性能优化方案,代码/打包/资源加载方面 热更新原理 ","date":"2022-07-13","objectID":"/posts/interview/:0:4","tags":["notes"],"title":"面经记录","uri":"/posts/interview/"},{"categories":null,"content":"概念类问题 http/https 区别 介绍 options 请求的意义 MVVM/MVC 的区别,如何实现一个 MVVM 简述 XSS 和 CSRF 的概念,并列举几种安全防范策略 ","date":"2022-07-13","objectID":"/posts/interview/:0:5","tags":["notes"],"title":"面经记录","uri":"/posts/interview/"},{"categories":null,"content":"编程题 编写一个函数,大小写字母取反 const fn = (string) =\u003e { let ans = '' for (let v of string) { ans += v.toLowerCase() === v ? v.toUpperCase() : v.toLowerCase() } return ans } 编写一个函数,输入 int 型,返回整数逆序后的字符串 二维数组扁平化,返回一个去重且是升序的一维数组 ","date":"2022-07-13","objectID":"/posts/interview/:0:6","tags":["notes"],"title":"面经记录","uri":"/posts/interview/"},{"categories":null,"content":"场景题 数据多、加载慢的解决方式 缓存 懒加载 生产环境发布后,发现客户那边并没有更新到最新版,从哪些方面排查? 时间戳保持最新 强缓存/协商缓存 禁止缓存 axios 需要对所有接口进行增加请求头之类的字段时,怎么处理? 一个按钮含有异步请求,如何防止多次点击重复提交? 防抖及其实现,引申节流,描述区别及使用场景 增加禁用属性,loading/disabled a 页面跳转到 b 页面然后重定向到 c 页面,可能会在 b 页面做登录逻辑或者记录数据(相当于一个中转页), 但是用户可能直接在地址栏里输入 c 页面,怎么防止这个情况? 路由守卫 导航拦截如何实现 同一个浏览器,不同标签之间数据传递的方法,简单列举 ","date":"2022-07-13","objectID":"/posts/interview/:0:7","tags":["notes"],"title":"面经记录","uri":"/posts/interview/"},{"categories":null,"content":"发散性问题 职业规划 怎么制定技术方案,从哪几个方面考虑 ","date":"2022-07-13","objectID":"/posts/interview/:0:8","tags":["notes"],"title":"面经记录","uri":"/posts/interview/"},{"categories":null,"content":"深度优先搜索 DFS 深度优先搜索(Depth-First-Search)和广度优先搜索(Breadth-First-Search)都是一种用来遍历或者搜索树或图这种数据结构的算法。以树为例,深度优先搜索的过程会从根节点出发,尽可能深地遍历每个子节点,而且每个节点只能访问一次,是一个不断回溯的过程。 实现方法大致如下: 首先将根节点放入栈中 从栈中取出第一个节点,并检验它是否为目标 如果找到目标,则结束搜索并返回结果 否则将它某一个尚未检验过的直接子节点放入栈中 重复步骤 2 如果不存在未检测过的直接子节点 将上一级节点放入栈中 重复步骤 2 重复步骤 4 若栈为空,表示整张图都已检查过并且没有要查找的目标,结束搜索 另外需要我们了解的一种数据结构:栈。 我们可以把盏比喻成一包手帕纸,每张纸巾都是按序一张一张放进去的,使用的时候是一张一张从最上边开始拿出来的。所以栈是一种后进先出(Last In First Out, LIFO)的数据结构。 示例:通过 DFS 实现复制对象的复制 const copyDFS = function(obj) { if (obj === null || typeof obj !== \"object\") { return obj; } const copyObj = Array.isArray(obj) ? [] : {}; for (const key in obj) { copyObj[key] = copyDFS(obj[key]); } return copyObj; } 示例:获取对象的所有键 const obj = { a: 1, b: 2, c: { d: 4, e: 5} } function dfs(obj, keys = []) { Object.entries(obj).forEach(([k, v]) =\u003e { keys.push(k) if (typeof v === 'object') dfs(v, keys) else return }) return keys } // ['a', 'b', 'c', 'd', 'e', 'f'] console.log(dfs(obj)) ","date":"2022-07-07","objectID":"/posts/dfs-bfs/:0:1","tags":["算法"],"title":"算法之深度优先搜索与广度优先搜索","uri":"/posts/dfs-bfs/"},{"categories":null,"content":"广度优先搜索 BFS 广度优先搜索(Breadth-First-Search)也会沿着树的宽度进行遍历,通常用来解决两种问题: 从节点 A 出发,有前往节点 B 的路径吗? 从节点 A 出发,前往节点 B 的哪条路径最短? 举个例子,假如今天的你心血来潮突然想看《假面骑士》,当你打开腾讯视频的时候发现只有会员才能看,真扫兴(胜利的法则变得不太确定)。这时你又不想自费充会员,所以就想找朋友借个用用。于是去挨个去问你的朋友,如果你的朋友里都没有会员,那就必须要找你朋友的朋友,别忘了你的目标是找来一个会员账号。首先,将你的朋友列入一个名单中依次查找,如果你的朋友李白没有会员,其次再将李白的朋友也加入到名单中,所以为了这个目标,你需要在你的朋友、朋友的朋友中查找,这种方式会搜遍你的整个人际关系网络,直到借来一个会员账号(胜利的法则已经决定✌️)或者一个会员都没有为止,这个过程就是广度优先搜索算法。 回到刚才的两个问题: 从节点 A 出发,有前往节点 B 的路径吗?(在你的人际关系网中,有会员吗?) 从节点 A 出发,前往节点 B 的哪条路径最短?(哪个拥有会员的朋友和你的关系最近?) 第一个问题已经在上例中说明;第二个问题就是怎么找到关系最近的朋友。 假设你的朋友是一层关系,朋友的朋友是二层关系。很显然,一层关系优于二层关系,二层关系优于三层关系,以此类推。需要注意的是,一层关系是在二层关系之前加入名单的,这就意味着我们依次对名单按照顺序进行查找出来的结果一定是关系最近的朋友,所以,广度优先搜索不仅查找 A 到 B 的路径,而且找到的是最短路径。 对于 BFS 算法,我们还需要了解另外一个数据结构:队列。 队列的工作原理和现实生活中的队列一样,假设你和朋友一起在排队买咖啡,如果你排在他前面,那就是你先买到咖啡。队列只支持两种操作:入队和出队,是一种先进先出(First In First Out, FIFO)的数据结构, 至此,我们可以使用队列来表示这份朋友名单,因为先加入的朋友(一层关系)将会先出队列并而且先被检查。 实现方法大致如下: 首先将根节点放入队列中 从队列中取出第一个节点,并检查它是否为目标 如果找到目标,则结束搜索并返回结果 否则将它所有尚未检查过的子节点加入队列中 若队列为空,表示整张图都检查过了,即没有查询的目标 重复步骤 2 示例:通过 DFS 实现对象深拷贝: function deepCopy(obj) { const queue = [obj] const newObject = {} while (queue.length \u003e 0) { const currentObj = queue.shift() Object.keys(currentObj).forEach((key) =\u003e { const value = currentObj[key] if (typeof value === 'object' \u0026\u0026 value !== null) { queue.push(value) newObject[key] = Array.isArray(value) ? [] : {} } else { newObject[key] = value } }) } return newObject } const obj = { a: 1, b: 2 } const copy = deepCopy(obj) copy.a = 3 console.log(copy) // {a: 3, b: 2} console.log(copy === obj) // false ","date":"2022-07-07","objectID":"/posts/dfs-bfs/:0:2","tags":["算法"],"title":"算法之深度优先搜索与广度优先搜索","uri":"/posts/dfs-bfs/"},{"categories":null,"content":"导航栏里都发生了些什么 这是 4 篇博客系列中的第 2 篇,来窥探 Chrome 的内部工作原理。在上一篇文章中,我们探讨了进程和线程在处理浏览器不同模块时的区别。本篇文章我们将会更深层次地挖掘线程和进程为了渲染一个网站是如何进行有序通信的。 我们来看一个浏览器里简单的例子:在浏览器中输入一个 URL 地址,接着浏览器会从网络中请求数据然后渲染出一个页面。在这篇文章中,我们将会聚焦于用户请求一个站点随后浏览器准备渲染页面这一部分,也就是我们熟知的导航。 ","date":"2022-05-28","objectID":"/posts/inside-look-at-modern-web-browser-2/:1:0","tags":["翻译"],"title":"深入了解现代网络浏览器(2/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-2/"},{"categories":null,"content":"始于一个浏览器进程 正如我们已经在 上篇中阐述过的 GPU/CPU/内存/多进程架构 一样,浏览器进程处理了其每个标签页的所有任务。浏览器进程拥有像 UI 线程这样绘制按钮或者输入框的进程,还有网络线程从网络中接收数据,存储线程控制文件的访问等等这些进程。当我们在地址栏中输入一个 URL 地址时,我们的输入就会被浏览器进程里的 UI 线程处理。 图1:上方是浏览器的用户界面,下方则是浏览器进程的示意图,其中包含了用户界面、网络和存储线程。\" 图1:上方是浏览器的用户界面,下方则是浏览器进程的示意图,其中包含了用户界面、网络和存储线程。 ","date":"2022-05-28","objectID":"/posts/inside-look-at-modern-web-browser-2/:2:0","tags":["翻译"],"title":"深入了解现代网络浏览器(2/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-2/"},{"categories":null,"content":"一个简单的导航 ","date":"2022-05-28","objectID":"/posts/inside-look-at-modern-web-browser-2/:3:0","tags":["翻译"],"title":"深入了解现代网络浏览器(2/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-2/"},{"categories":null,"content":"第一步:处理输入 当用户开始在地址栏中输入的时候,UI 线程首要询问的是“这是一个搜索关键词还是一个地址咧?”。在 Chrome 中,地址栏同样是一个搜索框,所以 UI 线程需要解析并且决定是要把输入发送给搜索引擎,还是直接显示这个你请求的网站。 图2:UI 线程询问此输入是搜索关键字还是一个地址\" 图2:UI 线程询问此输入是搜索关键字还是一个地址 ","date":"2022-05-28","objectID":"/posts/inside-look-at-modern-web-browser-2/:3:1","tags":["翻译"],"title":"深入了解现代网络浏览器(2/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-2/"},{"categories":null,"content":"第二步:开始导航 当用户敲了回车键,UI 线程会初始化一个网络命令去获取网站内容,此时在标签页的左上角会显示一个转圈圈的 loading ,随后网络线程会通过合适的协议,比如 DNS 查询为该请求建立 TLS 链接。 图3:UI 线程与网络线程通信,以导航到 mysite.com 站点\" 图3:UI 线程与网络线程通信,以导航到 mysite.com 站点 此时网络进程可能会收到一个服务器的重定向头信息,比如 HTTP 301 ,这种情况下,网络线程就会和 UI 线程对于服务器正在请求重定向进行通信,然后向另一个 URL 发起请求。 ","date":"2022-05-28","objectID":"/posts/inside-look-at-modern-web-browser-2/:3:2","tags":["翻译"],"title":"深入了解现代网络浏览器(2/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-2/"},{"categories":null,"content":"第三步:读取响应 当响应正文开始返回的时候,网络线程就会在必要时查看字节流的前几个字节,响应头中的 Content-Type 字段说明了数据的类型,但是由于它可能会出错或是不准确,所以此处会有一个 MIME Type Sniffing 检查来确认该数据是什么类型。这算是 源码 中提到的一个“小花招”,具体可以参见文章中的注释以了解不同的浏览器是如何处理 content-type/payload 的。 图4:响应头包含了 Content-Type 用以说明数据类型,而 payload 表示真实传输的数据\" 图4:响应头包含了 Content-Type 用以说明数据类型,而 payload 表示真实传输的数据 如果服务器返回的是一个 HTML 文件,那么下一步浏览器就会向渲染进程传递数据以渲染页面,但如果是一个 zip 文件或其他格式的文件,就意味着这是一个下载请求,此时浏览器需要把数据传递给下载管理器以下载文件。 图5:网络线程询问响应的数据是否是一个来自安全站点的 HTML\" 图5:网络线程询问响应的数据是否是一个来自安全站点的 HTML 这也正是 SafeBrowsing 发生的时机。如果域名和响应数据看起来像个恶意的网站,这时网络线程会警惕性地显示一个提示页面,而且也会触发 Cross Origin Read Blcoking(CORB) 检测,以确保被攻击的垮站数据不会被传递给渲染进程。 ","date":"2022-05-28","objectID":"/posts/inside-look-at-modern-web-browser-2/:3:3","tags":["翻译"],"title":"深入了解现代网络浏览器(2/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-2/"},{"categories":null,"content":"第四步:查找渲染器进程 当所有的检测工作都已完成,并且网络线程也能够确信浏览器可以处理这个请求站点时,网络线程就会告诉 UI 线程数据已就绪,UI 线程就会寻找一个渲染器进程继续渲染这个网页。 图6:网络线程告诉 UI 线程去寻找一个渲染器进程\" 图6:网络线程告诉 UI 线程去寻找一个渲染器进程 ","date":"2022-05-28","objectID":"/posts/inside-look-at-modern-web-browser-2/:3:4","tags":["翻译"],"title":"深入了解现代网络浏览器(2/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-2/"},{"categories":null,"content":" 📌 本篇原文发表于 2018.09.05,正文中出现的有关于时间概念的语句,阅读时请注意切换语境。 👉🏻原文链接 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:0:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"中央处理器/图形处理器/内存/多进程架构 在这个包含 4 篇文章的博客系列中,我们将从高阶架构到具体的渲染细节来深入 Chrome 浏览器内部了解其是怎么运作的。如果你曾经好奇浏览器是怎么把代码转变成一个功能齐备的网站,或者你并不确定为什么一个具体的技术细节能够带来性能提升,那么这个系列正好是为你准备的。 作为系列第一篇,我们将会了解一些核心的计算机术语和 Chrome 浏览器的多进程架构。如果你对 CPU/GPU 和 进程/线程已经很熟悉,可以直接跳到 浏览器架构 章节。 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:1:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"计算机的核心 - CPU 和 GPU 为了理解浏览器所运行的环境,我们需要先了解计算机的一些部件以及它们是做什么的。 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:2:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"中央处理器 CPU 首先是中央处理器(CPU, Central Processing Unit)。CPU 可以被看作是一台计算机的大脑,一个 CPU 内核可以想象成一位办公室工作人员,能够处理一个接一个被安排的多个任务,可以处理一切从数学甚至到艺术领域的难题,而且它知道如何去响应一个用户的指令。以前的大多数 CPU 都是单个芯片,一个内核就相当于是一个 CPU 被嵌入到一个芯片上。在现代的硬件条件下,出现了多核处理器,也赋予了手机、笔记本更强的计算能力。 图 1:四个 CPU 内核就像彼此相邻的办公室职员一样处理多个任务\" 图 1:四个 CPU 内核就像彼此相邻的办公室职员一样处理多个任务 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:2:1","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"图形处理器 GPU 图形处理器是计算机里的另一个部件。和 CPU 不同的是,GPU 更擅长在多核之间处理一些简单的任务。顾名思义,GPU 最初是被开发成处理图形任务的模块,这也是为什么在很多图形计算的背景下,会把“使用 GPU”或“支持 GPU”和快速渲染、流畅的交互体验相关联在一起。近些年来,有了 GPU 加速的计算能力,也使 GPU 独立承载越来越多的计算成为可能。 图2:多个带着扳手🔧️的 GPU 内核,表明它们只能处理有限的任务\" 图2:多个带着扳手🔧️的 GPU 内核,表明它们只能处理有限的任务 当你在手机或电脑上启动一个应用时,是 CPU 和 GPU 在给应用提供运行能力。通常情况下,操作系统提供了一套运行机制供应用程序在 CPU 和 GPU 运行。 图 3: 计算机的三层架构示意图;机械硬件在最底层,操作系统在中间层,应用程序在最上层\" 图 3: 计算机的三层架构示意图;机械硬件在最底层,操作系统在中间层,应用程序在最上层 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:2:2","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"在进程和线程里执行程序 图 4: 进程就像一个鱼缸,线程就像鱼儿🐟️在鱼缸里游泳🏊🏻♀️\" 图 4: 进程就像一个鱼缸,线程就像鱼儿🐟️在鱼缸里游泳🏊🏻♀️ 在深入了解浏览器架构之前,另一个需要掌握的概念是 进程 和 线程。一个进程可以被看作是一个应用的执行程序,线程不仅是进程的一部分,而且可以执行其所在进程里的任意部分的程序代码。 当启动一个应用时,就等于创建了一个进程。程序可能会创建一个或多个线程来保障其正常工作,当然也可能不会创建。操作系统为进程提供了一个内存管理器,所有的应用状态都被保存在私密的内存空间中,当应用被关闭的时候,该进程也相应消失,同时操作系统也会释放内存。 图 5: 进程使用内存空间存储应用数据\" 图 5: 进程使用内存空间存储应用数据 一个进程可以请求让操作系统去启动另外一个进程以运行不同的任务,如此一来,不同的内存空间也会被分配给新的进程。不同的进程之间可以通过 IPC (进程间通信 Inter Process Communication) 进行通信,许多应用都是使用这种设计方式开发的,以便于当一个工作进程没有响应的时候,该进程可以在不影响其他进程的情况下重新启动。 图 6: 不同的进程之间通过 IPC 进行通信\" 图 6: 不同的进程之间通过 IPC 进行通信 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:3:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"浏览器架构 那么现代浏览器是如何利用进程和线程构建的呢?好吧 =。= 可能是一个进程和很多个不同的线程,也可能是很多不同的进程和一些通过 IPC 通信的线程。 图 7: 进程与线程间的不同浏览器架构\" 图 7: 进程与线程间的不同浏览器架构 需要注意的一个重要信息是,这些不同的架构主要是实现上的细节,构建一个浏览器并没有标准的规范,一个浏览器的实现方式也可能和其他的浏览器完全不同。 在本博客系列中,我们将用下文中的图示来描述 Chrome 浏览器最近更新的的架构。 图中最上层是浏览器进程在统一协调其他进程,它们分别负责应用程序内不同模块的代码执行。对于渲染器进程来说,它会创建多个进程并将其分配给每一个标签页,直到最近,Chrome 会在可能的情况下会给每个标签页分配一个进程;现在,Chrome 正在尝试着给每一个站点分配它自己的进程,包括 iframes(查看 网站隔离)。 图 8: Chrome 的多进程架构示意图。可以看到渲染器进程下是有多个层级的,也表明着 Chrome 在每个标签页中执行着多个渲染器进程\" 图 8: Chrome 的多进程架构示意图。可以看到渲染器进程下是有多个层级的,也表明着 Chrome 在每个标签页中执行着多个渲染器进程 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:4:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"不同的进程及其功能 进程 功能 浏览器进程 控制 Chrome 浏览器应用本身,包括地址栏、书签、返回和前进按钮。同时也处理网页浏览器中那些看不到的、比较特殊的任务,比如网络请求和文件访问等 渲染器进程 控制标签页中一个网页显示的所有内容 插件进程 控制网页中使用的插件,比如 flash GPU 进程 和其他进程相对独立,负责处理图形任务。GPU 进程会被划分为不同的进程,因为它会处理多个应用程序的请求并把它们绘制到同一个界面上。 图 9: 不同的进程负责浏览器界面的不同部分\" 图 9: 不同的进程负责浏览器界面的不同部分 还有很多像扩展和通用的进程。如果你想要查看在 Chrome 中运行了多少进程,可以依次点击右上角的「**···**选项」菜单 →「更多工具」→「任务管理」,之后会打开一个窗口,显示当前运行进程的列表和 CPU 及内存的占用情况。 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:5:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"Chrome 浏览器中多进程的优势 上文中,我们提到 Chrome 使用了多个渲染器进程。举个最简单的例子来讲,你可以想象一下每个标签页都有自己的渲染器进程,让我们假设你已经打开了 3 个标签页,而且每个标签页都由一个独立的渲染器进程负责运行,这时如果其中一个标签页没有响应或是卡死,那你可以关闭这个无响应的页面,同时还能保证其他页面正常运行。但如果所有的标签页都运行在同一个进程上,当有其中一个页面无响应时,那所有的页面也都会死翘翘,着实令人难过。🥲 图 10: 每个标签页运行多个进程\" 图 10: 每个标签页运行多个进程 将浏览器的工作分离成多个进程的另一个好处是安全和沙盒化。由于操作系统提供了一种限制进程的方法,浏览器可以对某些进程进行沙盒化处理,使其与某些功能隔离起来。例如,浏览器限制了任意用户的输入进程对访问文件的权限,就像渲染器进程一样。 由于进程都有自己私有的内存空间,它们通常包含了一些通用基础架构的副本(如 V8 , Chrome 的 JS 引擎)。这意味着会有更多的内存占用,因为它们不能像在同一进程中的线程那样共享。为了节省内存,Chrome 限制了它可以启动的进程数量,限制的进程数取决于设备的内存大小和 CPU 功率,当到达了限制数的上限时,它就会开始将同一站点的多个标签页运行在一个进程上。 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:6:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"节省更多内存 - Chrome 中的服务化 浏览器进程的实现方式是大致相同的。Chrome 正在改变它的架构,将浏览器程序的每个部分作为服务运行,从而可以轻松地将浏览器的各个模块拆分为不同的进程或者聚合为一个进程。 通常情况下,当 Chrome 运行在一个配置强悍的硬件上时, 它可能会将每个服务分离成不同的进程,以获取更好的稳定性,但是如果是一台配置有限的设备上,Chrome 会为了节省内存占用将服务合并为一个进程。这与之前安卓平台的实现方式大同小异,都是通过合并进程来换来更少的内存占用。 图 11: Chrome 将不同的服务转化为多个进程和一个浏览器进程\" 图 11: Chrome 将不同的服务转化为多个进程和一个浏览器进程 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:7:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"单个 frame 渲染器进程 - 网站隔离 网站隔离 是 Chrome 最近介绍的一项新功能,该功能可以为每一个跨域的 iframe 运行一个单独的渲染器进程。我们已经阐述过单个标签页对应单个渲染器进程的模式,这种模式允许跨域的 iframe 运行在一个单独的渲染器进程里,同时和其他网站共享内存空间。这也使得在同一个渲染器进程里运行 a.com 和 b.com 看起来是没什么问题的。同源策略 是浏览器里核心的安全模型,它保证了一个网站不能在没有获取另一个网站的允许下访问该网站的数据。绕过这个策略是很多攻击网站安全的主要目标,而进程隔离是隔离网站最有效的方式,有了 幽灵漏洞 ,可以更明显地意识到,我们需要使用进程来隔离网站。自从 Chrome 67 之后,默认情况下隔离功能是开启的,这样一来,每个跨域的网站都会有一个单独的渲染器进程。 图 12: 网站隔离,一个网页中有多个渲染器进程指向 iframe\" 图 12: 网站隔离,一个网页中有多个渲染器进程指向 iframe 支持网站隔离功能是开发者多年以来努力的成果,网站隔离并不像合并多个不同的渲染器进程那样简单,它从根本上改变了 iframes 之间相互通信的方式。在不同进程中运行 iframe 的页面上打开 devtools,意味着 devtools 不得不执行一些幕后工作,才能使其看起来像无缝衔接。甚至是一个简单的 Ctrl+F 查找功能都要横跨数个不同的渲染器进程进行搜索。现在你能明白为什么浏览器开发工程师们说网站隔离的发布可以看作是一个里程碑的实现了吧!(哈哈哈真不容易🤓)。 ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:8:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"总结 本篇文章中,我们已经介绍了从高阶视角来看待浏览器架构和多进程架构的优势,还涉及到了 Chrome 中与多进程架构有深深关联的服务化和网站隔离。在下一篇文章中,我们将开始深入了解使网站呈现出来时,进程和线程之间都发生了什么事情。 下一篇:导航栏里发生了什么事情? ","date":"2022-05-18","objectID":"/posts/inside-look-at-modern-web-browser-1/:9:0","tags":["翻译"],"title":"深入了解现代网络浏览器(1/4)【译】","uri":"/posts/inside-look-at-modern-web-browser-1/"},{"categories":null,"content":"新建本地分支并切换到新分支 git checkout -b feat/abc 本地分支推送到远程(x) git push origin feat/abc:feat/abc 设置跟踪(y) git branch --set-upstream-to=origin/feat/abc feat/abc 举例:如果想要把新建的 abc 本地分支推送到远程并建立跟踪,需要使用以上 x 和 y 两条命令,也可以使用以下一条命令: git push -u origin feat/abc 删除本地分支 git branch -d abc 如果一个分支还没有被推送或合并,强制删除使用 -D git branch -D abc 删除远程分支 git push origin --delete abc 或 git push origin :abc ","date":"2019-07-18","objectID":"/posts/git-command/:0:0","tags":["git"],"title":"Git 常用命令","uri":"/posts/git-command/"},{"categories":null,"content":" A CHAMPAGNE SUPERNOVA IN THE SKY :D ","date":"2019-04-02","objectID":"/about/:0:0","tags":null,"title":"","uri":"/about/"}]