「如何从零到一实现一个玩具浏览器🌏」
大家好,我是速冻鱼🐟,一条水系前端💦,喜欢花里胡哨💐,持续沙雕🌲欢迎小伙伴们加我:拉你进群关注我的:一起进步,期待与大家共同成长🥂
阅读本文 📖
1.您将了解到什么是有限状态机
2.您将了解到浏览器渲染基本流程与原理
3.您将和我一起完成一个玩具浏览器的编写
前言 🌵
最近在学习浏览器渲染原理,光知道理论还不行🌝,我们得动手实践才能更深入的了解浏览器渲染后背的点点滴滴💧,下面分享给大家
前置知识 💻
1.什么是有限状态机 ⭐
有限状态机(Finite-state machine)是一个非常有用的模型,可以模拟世界上大部分事物。
每一个状态都是一个机器
在每一个机器里,我们可以做计算、存储、输出......
所有的这些机器接受的输入是一致的
状态机的每一个机器本身没有状态,如果我们用函数来表示的话,它应该是纯函数(无副作用)
每一个机器知道下一个状态
每个机器都有确定的下一个状态(Moore)
每个机器根据输入决定下一个状态(Mealy)

简单说,它有三个特征:
举例来说,网页上有一个菜单元素。鼠标悬停的时候,菜单显示;鼠标移开的时候,菜单隐藏。如果使用有限状态机描述,就是这个菜单只有两种状态(显示和隐藏),鼠标会引发状态转变。现在还不太了解没关系,后边我们看代码就好理解多了,这里对状态机不做过多描述。
Winter大佬的重学前端也有相关内容
2.我们使用有限状态机来解决什么问题 🌟
有限状态机的写法,逻辑清晰,表达力强,有利于封装事件。一个对象的状态越多、发生的事件越多,就越适合采用有限状态机的写法。
比如使用有限状态机处理字符串
function findStr(str) {
let state= start;
for (const c of str) {
state=state(c)
}
return state===end
}
function start(c) {
if (c === 'a') {
return findA
} else return start
}
function end(c) {
return end
}
function findA(c) {
if (c === 'b') {
return findB
} else return start(c)
}
function findA2(c) {
if (c === 'b') {
return findB2
} else return start(c)
}
function findA3(c) {
if (c === 'b') {
return findB3
} else return start(c)
}
function findB(c) {
if (c === 'a') {
return findA2
} else return start(c)
}
function findB2(c) {
if (c === 'a') {
return findA3
} else return start(c)
}
function findB3(c) {
if (c === 'x') {
return end
} else return start(c)
}
console.log(findStr('aaabxababx'))
后边我们实战中也将会使用状态机来对html文本进行解析构建我们的抽象语法树(AST)
3.浏览器渲染的大致流程 💫

发送HTTP请求获取HTML
对获取到的HMTL进行解析成一颗光秃秃的DOM树
对获取到的CSS进行计算,将计算出来的值添加到DOM树上,形成一棵带有CSS样式的渲染树
有了渲染树,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置,大小
有了每个dom的位置大小信息后,浏览器就可以将各个节点绘制到屏幕上了

下面不说废话直接开搞^_^
实现流程 🌊
tips☀️:以下代码有点长,不想查看细节的小伙伴可以直接看后边,也可以到查看
这里我不会很详细的去介绍代码的每一步实现,重要的想让大家对整个渲染流程有个全面的认识🍎
1.用node模拟我们的服务端 🐻
接收请求,响应我们的HTML就是我们的node服务要做的事情,就这么简单^_^
const http = require("http");
http
.createServer((req, res) => {
let body = [];
req
.on("error", (err) => {
console.error(err);
})
.on("data", (chunk) => {
console.log("chunk", chunk);
body.push(chunk);
})
.on("end", () => {
body = Buffer.concat(body).toString();
console.log("body", body);
res.writeHead(200, { "Content-Type": "text/html" });
res.end(
`
`
);
});
})
.listen("8088");
console.log("server started");
2.客户端编写 🐼
在客户端我们会发送->->->->->->->->->->

先从整体看看我们client需要做什么,看不懂没关系,我会分开解释每一个流程在代码中的具体实现

2.1 发送http请求获取html

tips⭐:以下代码只展示了核心调用部分,想要看全部实现的小伙伴可以查看我的源码

2.2 对获取到的html文本进行词法分析获取token

👨🏫这里就要开始用到我们上文提到的对html进行解析了哦
tips⭐:以下代码只展示了核心调用部分,想要看全部实现的小伙伴可以查看我的源码
对html的每一个字符使用进行词法分析,形成。(指有效部分,这里可以理解为一个htm标签,eg:
、就算是一个)由于状态太多,这里只例举了部分状态,我们要通过这个状态机对html的每个字符进行词法分析得到token好进行后边的语法分析
2.3 对获取到的token进行语法分析构建dom树
我们拿到每一个词法分析过后的,根据每个的属性执行不同的逻辑来构建我们的语法树🌳(其实我们css计算也就会在最初生成dom树的时候进行)
使用栈这个数据结构来维护我们的dom树🌴,根据的来对进行入栈和出栈的操作,最后遍历完每个token,对每个token进行逻辑处理后,栈顶只剩下我们的对象,这个对象就是我们dom树的对象表现形式
)
let stack = [{ type: "document", children: [] }]; //doms树解析用的栈
根据每个token不同的type,执行不同的逻辑,添加到我们的dom树🌴上
当遇到type为style的token时,使用一个数组来维护这个样式规则
let rules=[]; /** * 添加样式规则的方法 * @param text */ function addCSSRules(text){ //调用css这个现成的库对css样式文本进行词法语法分析获取css的Ast var ast=css.parse(text); // console.log(JSON.stringify(ast,null,4)); rules.push(...ast.stylesheet.rules); }
2.4 对dom树进行css计算并获取渲染树
其实这一步我们是在获取到token的时候就会进行css计算,为了方便理解,所以单独划分一步。
可以看到这里我们拿到token后,进行语法分析的时候就会进行css计算
对的每个进行,计算完成后,每个对象上就会维护一个属性,这样我们的就变成了一颗带有的了🎄
2.5 对渲染树的每个元素进行位置的计算
这里根据浏览的排版规则来对我们设置的属性进行位置的计算,这里我们只实现了flex这个排版的算法,因为它比较容易实现,能力又不是太差,这里只是为了感受排版的过程🍃。
浏览器排版规则包括🌻:
第一代就是 正常流 —— 包含了 position, display,flow;
第二代就是 flex —— 这个就比较接近人的自然思维;
第三代就是 grid —— 是一种更强大的排版模式;
第四代可能是 Houdini —— 是一组底层 API,它们公开了 CSS 引擎的各个部分,从而使开发人员能够通过加入浏览器渲染引擎的样式和布局过程来扩展 CSS。
同样是在解析语法树的时候调用函数对我们的元素进行位置计算
解析元素上的属性然后根据我们的计算出元素的,获得一颗带有位置的🎄
2.6 万事俱备,只欠东风!有了一棵带样式、带位置的dom树,我们就可以进行最后一步渲染啦
经历千辛万苦的dom树解析,我们终于拥有一棵带样式、带位置的dom树,这里我们使用这个来我们的,最终会在生成一张我们渲染过后的。
使用npm或者yarn安装🍉,这个库可以帮助我们生成图片,在client.js中调用进行我们的渲染过程,最终生成🖼。
在中,我们遍历元素的属性,获取,,调用提供的完成渲染逻辑
2.7 最后展示我们渲染过后生成的图片🖼
总结 🍁
终于终于终于,历经千辛万苦🌈,我们终于从客户端发送到服务端,,,通过解析获取🎄,在解析过程中进行了,,最终获取到了一棵带有,有的🎄,最后完成的完整,不知道小伙伴们是不是感觉收获满满🍉,也对整个浏览器渲染流程有了一个,如果你还是有很多疑问❓,您可以到下载这个项目,在本地跑一下,自己感受一下整个过程🤓,我相信效果可能会更好~
参考文献 📚
结束语 🌞
那么我的就结束了,文章的目的其实很简单,就是对日常工作的,输出一些觉得对大家有用的东西,菜不菜不重要,但是热爱🔥,希望大家能够喜欢我的文章,我真的很用心在写,也希望通过文章认识更多志同道合的朋友,如果你也喜欢,欢迎加我,一起,一起。
vx👦:sudongyuer
伙伴们,如果喜欢我的给🐟🐟点一个赞👍或者关注➕都是对我最大的支持。