我是三钻,一个在《技术银河》中等你们一起来终生漂泊学习。 是力量,关注是认可,评论是关爱!下期再见 👋! 浏览器工作原理是一块非常重要的内容,我们经常看到的 通过自己实现一遍简单的浏览器,我们会对浏览器的基本原理有更为深刻的理解。 我们主要需要完成的是从 URL 请求到 Bitmap 页面展示的整个流程就可以了。 浏览器流程: 因为这个处理字符串是整个的浏览器里面贯穿使用的技巧,如果不会用这个状态机,后面实现和读浏览器实现的代码会非常吃力。所以这里我们先讲讲什么是有限状态机。 Mealy 状态机: 我们首先了解一下,在不使用状态机的情况下来实现一些字符串的处理方式: 第一问题:在一个字符串中,找到字符“a” 第二个问题:不准使用正则表达式,纯粹用 JavaScript 的逻辑实现:在一个字符串中,找到字符“ab” 「直接寻找 第三个问题:不准使用正则表达式,纯粹用 JavaScript 的逻辑实现:在一个字符串中,找到字符“abcdef” 方法一:「使用暂存空间,移动指针来检测」 方法二:「使用 方法三:「逐个查找,直到找到最终结果」 这里我们使用状态机的方式来实现:在一个字符串中,找到字符“abcdef” 问题升级:用状态机实现字符串“abcabx”的解析 HTTP TCP Internet 4G/5G/Wi-Fi 在我们编写自己的浏览器之前,我们首先建立一个node.js服务端。 首先我们编写一个 在编写我们的客户端代码之前,我们需要先了解一下 HTTP 协议。 我们先来看看 HTTP 协议的 request 部分: POST / HTTP/1.1 Host: 127.0.0.1 Content-Type: application/x-www-form-urlencoded field1=aaa&code=x%3D1 接下来我们就可以开始编写代码了! 在接下来的部分,我们需要在代码中解析 HTTP Response 中的内容,所以我先来了解一下 HTTP Response 中的内容。 HTTP/1.1 200 OK Content-Type: text/html Date: Mon, 23 Dec 2019 06:46:19 GMT Connection: keep-alive Transfer-Encoding: chunked 26 Hello World
0 这里我们开始实战,通过实现 send 函数中的逻辑来真正发送请求到服务端。 通过以上思路,我们来实现代码: 现在我们来具体实现 RequestParser类的代码。 最后我们来实现 Body 内容的解析逻辑。 这里我们就实现了浏览器的 HTTP Request 请求,HTTP Response 解析的过程的代码。 下一篇文我们来一起实现 HTTP 解析并且构建 DOM 树,然后进行 CSS 计算。 小伙伴们可以查看或者订阅相关的专栏,从而集中阅读相关知识的文章哦。 📖 《数据结构与算法》 — 到了如今,如果想成为一个高级开发工程师或者进入大厂,不论岗位是前端、后端还是AI,算法都是重中之重。也无论我们需要进入的公司的岗位是否最后是做算法工程师,前提面试就需要考算法。 📖 《FCC前端集训营》 — 根据FreeCodeCamp的学习课程,一起深入浅出学习前端。稳固前端知识,一起在FreeCodeCamp获得证书 📖 《前端星球》 — 以实战为线索,深入浅出前端多维度的知识点。内含有多方面的前端知识文章,带领不懂前端的童鞋一起学习前端,在前端开发路上童鞋一起燃起心中那团火🔥前沿
重绘
、重排
或者一些讲解CSS属性的时候,都会用到一些浏览器工作原理的知识来讲解。理论化学习浏览器工作原理,效果不是很大,而且很枯燥,所以这里我们从零开始用 JavaScript
来实现一个浏览器。浏览器基础渲染流程
URL
部分,经过 HTTP
请求,然后解析返回内容,然后提取 HTML
内容HTML
后,我们可以通过文本分析(parse),然后把HTML的文本编程一个 DOM
树DOM
树是光秃秃的,下一步我们进行 CSS 计算(CSS computing),最终把 CSS 挂载在这个 DOM 树上有限状态机去处理字符串
JavaScript 中如何实现
// 每个函数是一个状态 function state (input) { // 函数参数就是输入 // 在函数中,可以自由地编写代码,处理每个状态的逻辑 return next; // 返回值作为下一个状态 } /** ========= 以下是调试 ========= */ while (input) { // 获取输入 state = state(input); // 把状态机的返回值作为下一个状态 }
input
state = state(input)
,来让状态机接收输入来完成状态切换Mealy
型状态机,返回值一定是根据 input
返回下一个状态Moore
型状态机,返回值是与 input
没有任何关系,都是固定的状态返回不使用状态机处理字符串
function match(string) { for (let letter of string) { if (letter == 'a') return true; } return false; } console.log(match('I am TriDiamond'));
a
和 b
,都找到时返回」/** * 直接寻找 `a` 和 `b`,都找到时返回 * @param {*} string 被匹配的字符 */ function matchAB(string) { let hasA = false; for (let letter of string) { if (letter == 'a') { hasA = true; } else if (hasA && letter == 'b') { return true; } else { hasA = false; } } return false; } console.log( matchAB('hello abert'));
/** * 使用暂存空间,移动指针来检测 * @param {*} match 需要匹配的字符 * @param {*} string 被匹配的字符 */ function matchString(match, string) { const resultLetters = match.split(''); // 需要匹配的字符拆解成数组来记录 const stringArray = string.split(''); // 把被匹配的字符串内容也拆解成数组 let index = 0; // 匹配字符串的指针 for (let i = 0; i <= stringArray.length; i++) { // 因为要保证字符的绝对匹配,如 “ab” 不能是 "abc",不能是 "a b" // 所以这里需要两个字符必须是有顺序的关系的 if (stringArray[i] == resultLetters[index]) { // 如果找到一个字符是吻合的,就 index + 1 找下一个字符 index++; } else { // 如果下一个字符不吻合,就重置重新匹配 index = 0; } // 如果已经匹配完所有的字符了,直接可以返回 true // 证明字符中含有需要寻找的字符 if (index > resultLetters.length - 1) return true; } return false; } console.log('方法1', matchString('abcdef', 'hello abert abcdef'));
substring
和匹配字符的长度来截取字符,看是否等于答案」/** * 通用字符串匹配 - 参考方法2(使用substring) * @param {*} match 需要匹配的字符 * @param {*} string 被匹配的字符 */ function matchWithSubstring(match, string) { for (let i = 0; i < string.length - 1; i++) { if (string.substring(i, i + match.length) === match) { return true; } } return false; } console.log('方法2', matchWithSubstring('abcdef', 'hello abert abcdef'));
/** * 逐个查找,直到找到最终结果 * @param {*} string 被匹配的字符 */ function match(string) { let matchStatus = [false, false, false, false, false, false]; let matchLetters = ['a', 'b', 'c', 'd', 'e', 'f']; let statusIndex = 0; for (let letter of string) { if (letter == matchLetters[0]) { matchStatus[0] = true; statusIndex++; } else if (matchStatus[statusIndex - 1] && letter == matchLetters[statusIndex]) { matchStatus[statusIndex] = true; statusIndex++; } else { matchStatus = [false, false, false, false, false, false]; statusIndex = 0; } if (statusIndex > matchLetters.length - 1) return true; } return false; } console.log('方法3', match('hello abert abcdef'));
使用状态机处理字符
start
和 end
matchedA
就是已经匹配中 a
字符了,以此类推start
f
字符,所以 matchedE
成功后,可以直接返回 结束状态end
end
这个结束状态,也被称为陷阱方法 (Trap),因为状态转变结束了,所以让状态一直停留在这里,知道循环结束/** * 状态机字符串匹配 * @param {*} string */ function match(string) { let state = start; for (let letter of string) { state = state(letter); // 状态切换 } return state === end; // 如果最后的状态函数是 `end` 即返回 true } function start(letter) { if (letter === 'a') return matchedA; return start; } function end(letter) { return end; } function matchedA(letter) { if (letter === 'b') return matchedB; return start(letter); } function matchedB(letter) { if (letter === 'c') return matchedC; return start(letter); } function matchedC(letter) { if (letter === 'd') return matchedD; return start(letter); } function matchedD(letter) { if (letter === 'e') return matchedE; return start(letter); } function matchedE(letter) { if (letter === 'f') return end(letter); return start(letter); } console.log(match('I am abcdef'));
/** * 状态机匹配字符串 * @param {*} string 被匹配的字符 */ function match(string) { let state = start; for (let letter of string) { state = state(letter); } return state === end; } function start(letter) { if (letter === 'a') return matchedA; return start; } function end(letter) { return end; } function matchedA(letter) { if (letter === 'b') return matchedB; return start(letter); } function matchedB(letter) { if (letter === 'c') return matchedC; return start(letter); } function matchedC(letter) { if (letter === 'a') return matchedA2; return start(letter); } function matchedA2(letter) { if (letter === 'b') return matchedB2; return start(letter); } function matchedB2(letter) { if (letter === 'x') return end; return matchedB(letter); } console.log('result: ', match('abcabcabx'));
HTTP 协议解析基础知识
ISO-OSI 七层网络模型
require('http')
TCP与IP的基础知识
require('net')
HTTP
HTTP请求 —— 服务端环境准备
node.js
的服务端:const http = require('http'); http .createServer((request, response) => { let body = []; request .on('error', err => { console.error(err); }) .on('data', chunk => { body.push(chunk.toString()); }) .on('end', () => { body = Buffer.concat(body).toString(); console.log('body', body); response.writeHead(200, { 'Content-Type': 'text/html' }); response.end(' Hello Worldn'); }); }) .listen(8080); console.log('server started');
了解 HTTP Request 协议
request line
,包含了三个部分:
Headers
key: value
格式body
部分:
Content-Type
来决定的实现 HTTP 请求
Request 类
class Request { constructor(options) { // 首先在 constructor 赋予需要使用的默认值 this.method = options.method || 'GET'; this.host = options.host; this.port = options.port || 80; this.path = options.path || '/'; this.body = options.body || {}; this.headers = options.headers || {}; if (!this.headers['Content-Type']) { this.headers['Content-Type'] = 'application/x-www-form-urlencoded'; } // 根据 Content-Type 转换 body 的格式 if (this.headers['Content-Type'] === 'application/json') { this.bodyText = JSON.stringify(this.body); } else if (this.headers['Content-Type'] === 'application/x-www-form-urlencoded') { this.bodyText = Object.keys(this.body) .map(key => `${key}=${encodeURIComponent(this.body[key])}`) .join('&'); } // 自动计算 body 内容长度,如果长度不对,就会是一个非法请求 this.headers['Content-Length'] = this.bodyText.length; } // 发送请求的方法,返回 Promise 对象 send() { return new Promise((resolve, reject) => { //...... }); } }
请求方法
/** * 请求方法 */ void (async function () { let request = new Request({ method: 'POST', host: '127.0.0.1', port: '8080', path: '/', headers: { ['X-Foo2']: 'custom', }, body: { name: 'tridiamond', }, }); let response = await request.end(); console.log(response); })();
Request 类中的 send 函数编写
// 发送请求的方法,返回 Promise 对象 send() { return new Promise((resolve, reject) => { const parser = new ResponseParser(); resolve(''); }); }
设计 ResponseParser
recieveChar
函数来对每个字符进行处理class ResponseParser { constructor() {} receive(string) { for (let i = 0; i < string.length; i++) { this.receiveChar(string.charAt(i)); } } receiveChar(char) {} }
了解 HTTP Response 协议
status line
与 request line 相反
chunked body
(是 Node 默认返回的一种格式)实现发送请求
// 发送请求的方法,返回 Promise 对象 send(connection) { return new Promise((resolve, reject) => { const parser = new ResponseParser(); // 先判断 connection 是否有传送过来 // 没有就根据,Host 和 port 来创建一个 TCP 连接 // `toString` 是把请求参数按照 HTTP Request 的格式组装 if (connection) { connection.write(this.toString()); } else { connection = net.createConnection( { host: this.host, port: this.port, }, () => { connection.write(this.toString()); } ); } // 监听 connection 的 data // 然后原封不动传给 parser // 如果 parser 已经结束的话,我们就可以进行 resolve // 最后断开连接 connection.on('data', data => { console.log(data.toString()); parser.receive(data.toString()); if (parser.isFinished) { resolve(parser.response); connection.end(); } }); // 监听 connection 的 error // 如果请求出现错误,首先 reject 这个promise // 然后断开连接,避免占着连接的情况 connection.on('error', err => { reject(err); connection.end(); }); }); } /** * 组装 HTTP Request 文本内容 */ toString() { return `${this.method} ${this.path} HTTP/1.1r ${Object.keys(this.headers) .map(key => `${key}: ${this.headers[key]}`) .join('rn')}rr ${this.bodyText}`; }
实现 RequestParser 类
/** * Response 解析器 */ class ResponseParser { constructor() { this.state = this.waitingStatusLine; this.statusLine = ''; this.headers = {}; this.headerName = ''; this.headerValue = ''; this.bodyParser = null; } receive(string) { for (let i = 0; i < string.length; i++) { this.state = this.state(string.charAt(i)); } } receiveEnd(char) { return receiveEnd; } /** * 等待状态行内容 * @param {*} char 文本 */ waitingStatusLine(char) { if (char === 'r') return this.waitingStatusLineEnd; this.statusLine += char; return this.waitingStatusLine; } /** * 等待状态行结束 * @param {*} char 文本 */ waitingStatusLineEnd(char) { if (char === 'n') return this.waitingHeaderName; return this.waitingStatusLineEnd; } /** * 等待 Header 名 * @param {*} char 文本 */ waitingHeaderName(char) { if (char === ':') return this.waitingHeaderSpace; if (char === 'r') return this.waitingHeaderBlockEnd; this.headerName += char; return this.waitingHeaderName; } /** * 等待 Header 空格 * @param {*} char 文本 */ waitingHeaderSpace(char) { if (char === ' ') return this.waitingHeaderValue; return this.waitingHeaderSpace; } /** * 等待 Header 值 * @param {*} char 文本 */ waitingHeaderValue(char) { if (char === 'r') { this.headers[this.headerName] = this.headerValue; this.headerName = ''; this.headerValue = ''; return this.waitingHeaderLineEnd; } this.headerValue += char; return this.waitingHeaderValue; } /** * 等待 Header 行结束 * @param {*} char 文本 */ waitingHeaderLineEnd(char) { if (char === 'n') return this.waitingHeaderName; return this.waitingHeaderLineEnd; } /** * 等待 Header 内容结束 * @param {*} char 文本 */ waitingHeaderBlockEnd(char) { if (char === 'n') return this.waitingBody; return this.waitingHeaderBlockEnd; } /** * 等待 body 内容 * @param {*} char 文本 */ waitingBody(char) { console.log(char); return this.waitingBody; } }
实现 Body 内容解析器
/** * Response 解析器 */ class ResponseParser { constructor() { this.state = this.waitingStatusLine; this.statusLine = ''; this.headers = {}; this.headerName = ''; this.headerValue = ''; this.bodyParser = null; } get isFinished() { return this.bodyParser && this.bodyParser.isFinished; } get response() { this.statusLine.match(/HTTP/1.1 ([0-9]+) ([sS]+)/); return { statusCode: RegExp.$1, statusText: RegExp.$2, headers: this.headers, body: this.bodyParser.content.join(''), }; } receive(string) { for (let i = 0; i < string.length; i++) { this.state = this.state(string.charAt(i)); } } receiveEnd(char) { return receiveEnd; } /** * 等待状态行内容 * @param {*} char 文本 */ waitingStatusLine(char) { if (char === 'r') return this.waitingStatusLineEnd; this.statusLine += char; return this.waitingStatusLine; } /** * 等待状态行结束 * @param {*} char 文本 */ waitingStatusLineEnd(char) { if (char === 'n') return this.waitingHeaderName; return this.waitingStatusLineEnd; } /** * 等待 Header 名 * @param {*} char 文本 */ waitingHeaderName(char) { if (char === ':') return this.waitingHeaderSpace; if (char === 'r') { if (this.headers['Transfer-Encoding'] === 'chunked') { this.bodyParser = new ChunkedBodyParser(); } return this.waitingHeaderBlockEnd; } this.headerName += char; return this.waitingHeaderName; } /** * 等待 Header 空格 * @param {*} char 文本 */ waitingHeaderSpace(char) { if (char === ' ') return this.waitingHeaderValue; return this.waitingHeaderSpace; } /** * 等待 Header 值 * @param {*} char 文本 */ waitingHeaderValue(char) { if (char === 'r') { this.headers[this.headerName] = this.headerValue; this.headerName = ''; this.headerValue = ''; return this.waitingHeaderLineEnd; } this.headerValue += char; return this.waitingHeaderValue; } /** * 等待 Header 行结束 * @param {*} char 文本 */ waitingHeaderLineEnd(char) { if (char === 'n') return this.waitingHeaderName; return this.waitingHeaderLineEnd; } /** * 等待 Header 内容结束 * @param {*} char 文本 */ waitingHeaderBlockEnd(char) { if (char === 'n') return this.waitingBody; return this.waitingHeaderBlockEnd; } /** * 等待 body 内容 * @param {*} char 文本 */ waitingBody(char) { this.bodyParser.receiveChar(char); return this.waitingBody; } } /** * Chunked Body 解析器 */ class ChunkedBodyParser { constructor() { this.state = this.waitingLength; this.length = 0; this.content = []; this.isFinished = false; } receiveChar(char) { this.state = this.state(char); } /** * 等待 Body 长度 * @param {*} char 文本 */ waitingLength(char) { if (char === 'r') { if (this.length === 0) this.isFinished = true; return this.waitingLengthLineEnd; } else { // 转换十六进制长度 this.length *= 16; this.length += parseInt(char, 16); } return this.waitingLength; } /** * 等待 Body line 结束 * @param {*} char 文本 */ waitingLengthLineEnd(char) { if (char === 'n') return this.readingTrunk; return this.waitingLengthLineEnd; } /** * 读取 Trunk 内容 * @param {*} char 文本 */ readingTrunk(char) { this.content.push(char); this.length--; if (this.length === 0) return this.waitingNewLine; return this.readingTrunk; } /** * 等待新的一行 * @param {*} char 文本 */ waitingNewLine(char) { if (char === 'r') return this.waitingNewLineEnd; return this.waitingNewLine; } /** * 等待新的一行结束 * @param {*} char 文本 */ waitingNewLineEnd(char) { if (char === 'n') return this.waitingLength; return this.waitingNewLineEnd; } }
最后
推荐专栏
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算