「如何从零到一实现一个玩具浏览器🌏」

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 「如何从零到一实现一个玩具浏览器🌏」

image.png


大家好,我是速冻鱼🐟,一条水系前端💦,喜欢花里胡哨💐,持续沙雕🌲,是隔壁寒草🌿的好兄弟,刚开始写文章。 如果喜欢我的文章,可以关注➕点赞,为我注入能量,与我一同成长吧~


阅读本文 📖



1.您将了解到什么是有限状态机

2.您将了解到浏览器渲染基本流程与原理

3.您将和我一起完成一个玩具浏览器的编写

本文仓库地址:toy-browser

前言 🌵



最近在学习浏览器渲染原理,光知道理论还不行🌝,我们得动手实践才能更深入的了解浏览器渲染背后的点点滴滴💧,下面分享给大家


前置知识 💻


1.什么是有限状态机 ⭐


有限状态机(Finite-state machine)是一个非常有用的模型,可以模拟世界上大部分事物。

  • 每一个状态都是一个机器
  • 在每一个机器里,我们可以做计算、存储、输出......
  • 所有的这些机器接受的输入是一致的
  • 状态机的每一个机器本身没有状态,如果我们用函数来表示的话,它应该是纯函数(无副作用)
  • 每一个机器知道下一个状态
  • 每个机器都有确定的下一个状态(Moore)
  • 每个机器根据输入决定下一个状态(Mealy)

image.png


简单说,它有三个特征:


  1. 状态总数(state)是有限的。
  2. 任一时刻,只处在一种状态之中。
  3. 某种条件下,会从一种状态转变(transition)到另一种状态。

举例来说,网页上有一个菜单元素。鼠标悬停的时候,菜单显示;鼠标移开的时候,菜单隐藏。如果使用有限状态机描述,就是这个菜单只有两种状态(显示和隐藏),鼠标会引发状态转变。现在还不太了解没关系,后边我们看代码就好理解多了,这里对状态机不做过多描述。


感兴趣可以看看阮一峰老师的文章JavaScript与有限状态机

极客时间Winter大佬的重学前端也有相关内容


2.我们使用有限状态机来解决什么问题 🌟


有限状态机的写法,逻辑清晰,表达力强,有利于封装事件。一个对象的状态越多、发生的事件越多,就越适合采用有限状态机的写法。


比如使用有限状态机处理字符串


在一个字符串中,如何使用状态机找到字符“abcdef”


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文本进行解析构建我们的DOM树


3.浏览器渲染的大致流程 💫

image.png


image.pngimage.pngimage.pngimage.pngimage.png

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

image.png

下面不说废话直接开搞^_^


实现流程 🌊


tips☀️:以下代码有点长,不想查看细节的小伙伴可以直接看后边总结,也可以到toy-browser查看源码


这里我不会很详细的去介绍代码的每一步实现,重要的想让大家对整个渲染流程有个全面的认识🍎


1.用node模拟我们的服务端 🐻


接收请求,返回我们的HTML就是我们的node服务要做的事情,就这么简单^_^

server.js

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(
`<html uname=sudongyu>
<head>
   <style>
    #container {
    width: 500px;
    height: 300px;
    display: flex;
    background-color: rgb(255,255,255);
}
#container #myid{
    width: 200px;
    height: 100px;
    background-color: rgb(255,0,0);
}
#container .c1{
    flex: 1;
    background-color: rgb(0,255,0);
}
   </style>
</head>
<body style="background: black">
     <div id="container">
        <div id="myid"></div>
        <div class="c1"></div>
    </div>
</body>
</html>`
        );
      });
  })
  .listen("8088");
console.log("server started");
复制代码

2.客户端编写 🐼


在客户端我们会发送http请求->获取响应报文->解析响应体->获取html文本信息->对html文本进行解析->获取dom树->计算css属性->获取渲染树->layout->获取有位置的dom树->render->Bitmap->浏览器展示我们的画面


image.png


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


client.js

68c8521695434de3aa0abdd895d471c9.png

2.1 发送http请求获取html


image.png

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


parser.js

image.png

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


image.png

这里就要开始用到我们上文提到的有限状态机对html进行解析了哦


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


对html的每一个字符使用有限状态机进行词法分析,形成token(token指有效部分,这里可以理解为一个htm标签,eg:

、就算是一个


image.png

parser.js


image.png

由于状态太多,这里只例举了部分状态,我们要通过这个状态机对html的每个字符进行词法分析得到token好进行后边的语法分析


parser.js

5a7191042e76438bb308866082648df3.jpg

2.3 对获取到的token进行语法分析构建dom树

image.png

我们拿到每一个词法分析过后的token进行语法解析,根据每个token的属性执行不同的逻辑来构建我们的语法树🌳(其实我们css计算也会在最初emittoken的时候进行)


使用栈这个数据结构来维护我们的dom树🌴,根据每个tokentype来对Node节点进行入栈和出栈的操作,最后遍历完每个token,对每个token进行逻辑处理后,栈顶只剩下我们的document对象,这个document对象就是我们dom树的对象表现形式,它的children属性就保存了dom树的层级结构

)


image.png



parser.js

let stack = [{ type: "document", children: [] }]; //doms树解析用的栈

根据每个token不同的type,执行不同的逻辑,添加Node到我们的dom树🌴上


parser.js

bfe7cfab2c9344738b64a401b34a3ceb.png


当遇到type为style的token时,使用一个数组rules[]来维护这个样式规则


parser.js


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计算并获取渲染树


image.png


其实这一步我们是在获取到token并emit的时候就会进行css计算,为了方便理解,所以单独划分一步。


可以看到这里我们拿到token后,进行语法分析的时候就会进行css计算


parser.js

8721bb6b4d794379b2d2a7f3a83e814b.png


dom树的每个元素节点进行css计算,计算完成后,每个元素节点对象上就会维护一个computedStyle属性,这样我们的dom树就变成了一颗带有css样式渲染树了🎄

parser.js

68d09a1a966f4a50a49c5860154eb1a0.png

image.png

2.5 对渲染树的每个元素进行位置的计算


这里根据浏览器的排版规则来对我们设置的属性进行位置的计算,这里我们只实现了flex这个排版的算法,因为它比较容易实现,能力又不是太差,这里只是为了感受排版的过程🍃。


浏览器排版规则包括🌻:


第一代就是 正常流 —— 包含了 position, display,flow;

第二代就是 flex —— 这个就比较接近人的自然思维;

第三代就是 grid —— 是一种更强大的排版模式;

第四代可能是 Houdini —— 是一组底层 API,它们公开了 CSS 引擎的各个部分,从而使开发人员能够通过加入浏览器渲染引擎的样式和布局过程来扩展 CSS。


同样是在parser.js解析语法树的时候调用layout函数对我们的dom元素进行位置计算


parser.js

image.png

解析dom元素上的computedStyle属性然后根据我们的flex排版规则计算出dom元素的位置大小获得一颗带有位置的dom树🎄

layout.js

2f6653f9a61e4e6faf9c6ecf859a4e81.png

2.6 万事俱备,只欠东风!有了一棵带样式、带位置的dom树,我们就可以进行最后一步渲染啦


经历千辛万苦的dom树解析,我们终于拥有一棵带样式、带位置的dom树,这里我们使用images这个开源库模拟我们的浏览器渲染,最终会在src目录生成一张图片来模拟浏览器渲染。


使用npm或者yarn安装images开源库🍉,这个库可以帮助我们生成图片,在client.js中调用render函数进行我们的渲染过程,最终生成图片🖼。

client.js

image.png


render函数中,我们遍历元素的属性,获取宽高背景颜色,调用images库提供的API完成渲染逻辑

render.js

image.png

2.7 最后展示我们渲染过后生成的图片🖼


image.png


总结 🍁


终于终于终于历经千辛万苦🌈,我们终于从客户端发送http请求到服务端响应请求解析响应报文获取html文本,通过词法语法解析html文本获取dom树🎄,在解析html过程中进行了css属性计算样式匹配位置计算最终获取到了一棵带有样式,有位置dom树🎄,最后完成渲染的完整过程,不知道小伙伴们是不是感觉收获满满🍉,也对整个浏览器渲染流程有了一个完整的认识,如果你还是有很多疑问❓,您可以到toy-browser下载这个项目,在本地跑一下,自己感受一下整个过程🤓,我相信效果可能会更好~


image.png



toy-browser源代码仓库地址:toy-browser👣


滴滴:本文是通过自己的学习与理解,以及查阅资料最终完成的,在语言表达上面肯定有很多不严谨的地方,或者表达错误的地方,对知识的理解可能不是很全面,希望大家保持一颗辩证的心来阅读,更好的是自己去实践一下,通过我的源码,自己去深入研究一下,效果肯定是极好的,感谢您的阅读~~~


参考文献 📚

结束语 🌞

image.png


那么我的第三篇文章就结束了,文章的目的其实很简单,就是对日常工作的总结和输出,输出一些觉得对大家有用的东西,菜不菜不重要,但是热爱🔥,希望大家能够喜欢我的文章,我真的很用心在写,也希望通过文章认识更多志同道合的朋友,如果你也喜欢折腾,欢迎加我好友,一起沙雕,一起进步

相关文章
|
JavaScript 前端开发 容器
从零开始实现一个玩具版浏览器渲染引擎(三)
从零开始实现一个玩具版浏览器渲染引擎(三)
49 0
|
前端开发 JavaScript
从零开始实现一个玩具版浏览器渲染引擎(二)
从零开始实现一个玩具版浏览器渲染引擎(二)
77 0
|
前端开发 JavaScript API
从零开始实现一个玩具版浏览器渲染引擎(四)
从零开始实现一个玩具版浏览器渲染引擎(四)
92 0
|
Rust 自然语言处理 前端开发
从零开始实现一个玩具版浏览器渲染引擎(一)
从零开始实现一个玩具版浏览器渲染引擎
73 0
|
11天前
|
JSON 移动开发 JavaScript
在浏览器执行js脚本的两种方式
【10月更文挑战第20天】本文介绍了在浏览器中执行HTTP请求的两种方式:`fetch`和`XMLHttpRequest`。`fetch`支持GET和POST请求,返回Promise对象,可以方便地处理异步操作。`XMLHttpRequest`则通过回调函数处理请求结果,适用于需要兼容旧浏览器的场景。文中还提供了具体的代码示例。
在浏览器执行js脚本的两种方式
|
8天前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
|
1月前
|
机器学习/深度学习 自然语言处理 前端开发
前端大模型入门:Transformer.js 和 Xenova-引领浏览器端的机器学习变革
除了调用API接口使用Transformer技术,你是否想过在浏览器中运行大模型?Xenova团队推出的Transformer.js,基于JavaScript,让开发者能在浏览器中本地加载和执行预训练模型,无需依赖服务器。该库利用WebAssembly和WebGPU技术,大幅提升性能,尤其适合隐私保护、离线应用和低延迟交互场景。无论是NLP任务还是实时文本生成,Transformer.js都提供了强大支持,成为构建浏览器AI应用的核心工具。
396 1
|
28天前
|
JavaScript API
深入解析JS中的visibilitychange事件:监听浏览器标签间切换的利器
深入解析JS中的visibilitychange事件:监听浏览器标签间切换的利器
79 0
|
2月前
|
JavaScript 前端开发
js之浏览器对象|28
js之浏览器对象|28
|
3月前
|
机器学习/深度学习 人工智能 前端开发
【人工智能】利用TensorFlow.js在浏览器中实现一个基本的情感分析系统
使用TensorFlow.js在浏览器中进行情感分析是一个非常实用的应用场景。TensorFlow.js 是一个用于在JavaScript环境中训练和部署机器学习模型的库,使得开发者能够在客户端直接运行复杂的机器学习任务。对于情感分析,我们可以使用预先训练好的模型来识别文本中的积极、消极或中性情感。
103 4
【人工智能】利用TensorFlow.js在浏览器中实现一个基本的情感分析系统

热门文章

最新文章