likes
comments
collection
share

浏览器渲染原理-模拟请求->渲染流程

作者站长头像
站长
· 阅读数 40

请求报文格式

  • 起始行:[方法][空格][请求URL][HTTP版本][换行符]
  • 首部: [首部名称][:][空格][首部内容][换行符]
  • 首部结束:[换行符]
  • 实体

响应报文格式

  • 起始行:[HTTP版本][空格][状态码][空格][原因短语][换行符]
  • 首部:[首部名称][:][空格][首部内容][换行符]
  • 首部结束: [换行符]
  • 实体

浏览器渲染原理-模拟请求->渲染流程

浏览器渲染原理-模拟请求->渲染流程

1.基于TCP发送HTTP请求

const net = require('net')
class HTTPRequest {
    constructor(options) {
        this.method = options.method || 'GET';
        this.host = options.host || '127.0.0.1';
        this.port = options.port || 80;
        this.path = options.path || '/';
        this.headers = options.headers || {}
    }
    send(body) {
        return new Promise((resolve, reject) => {
            body = Object.keys(body).map(key => (`${key}=${encodeURIComponent(body[key])}`)).join('&');
            if (body) {
                this.headers['Content-Length'] = body.length;
            };

            const socket = net.createConnection({
                host:this.host,
                port:this.port
            },()=>{
                const rows = [];
                rows.push(`${this.method} ${this.path} HTTP/1.1`);
                Object.keys(this.headers).forEach(key=>{
                    rows.push(`${key}: ${this.headers[key]}`);
                });
                let request = rows.join('\r\n') + '\r\n\r\n' + body;
                socket.write(request)
            });

            socket.on('data',function(data){
                // data 为发送请求后返回的结果
            })
            
        })

    }
}
async function request() {
    const request = new HTTPRequest({
        method: 'POST',
        host: '127.0.0.1',
        port: 3000,
        path: '/',
        headers: {
            name: 'zhufeng',
            age: 11
        }
    });
    let { responseLine, headers, body } = await request.send({ address: '北京' });
}

request();

2.解析响应结果

const parser = new HTTPParser()
socket.on('data',function(data){
    // data 为发送请求后返回的结果
    parser.parse(data);
    if(parser.result){
        resolve(parser.result)
    }
});

3.解析HTML

let stack = [{ type: 'document', children: [] }];
const parser = new htmlparser2.Parser({
    onopentag(name, attributes) {
        let parent = stack[stack.length - 1];
        let element = {
            tagName: name,
            type: 'element',
            children: [],
            attributes,
            parent
        }
        parent.children.push(element);
        element.parent = parent;
        stack.push(element);
    },
    ontext(text) {
        let parent = stack[stack.length - 1];
        let textNode = {
            type: 'text',
            text
        }
        parent.children.push(textNode)
    },
    onclosetag(tagname) {
        stack.pop();
    }
});
parser.end(body)

4.解析CSS

const cssRules = [];
const css = require('css');
function parserCss(text) {
    const ast = css.parse(text);
    cssRules.push(...ast.stylesheet.rules);
}
const parser = new htmlparser2.Parser({
    onclosetag(tagname) {
        let parent = stack[stack.length - 1];
        if (tagname == 'style') {
            parserCss(parent.children[0].text);
        }
        stack.pop();
    }
});

5.计算样式

function computedCss(element) {
    let attrs = element.attributes; // 获取元素属性
    element.computedStyle = {}; // 计算样式
    Object.entries(attrs).forEach(([key, value]) => {
        cssRules.forEach(rule => {
            let selector = rule.selectors[0];
            if ((selector == '#'+value && key == 'id') || (selector == '.'+value && key == 'class')) {
                rule.declarations.forEach(({ property, value }) => {
                    element.computedStyle[property] = value;
                })
            }
        })
    });
}

6.布局绘制

function layout(element) {
    // 计算位置 -> 绘制
    if (Object.keys(element.computedStyle).length != 0) {
        let { background, width, height, top, left } = element.computedStyle
        let code = `
            let canvas = document.getElementById('canvas');
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight ;
            let context = canvas.getContext("2d")
            context.fillStyle = "${background}";
            context.fillRect(${top}, ${left}, ${parseInt(width)}, ${parseInt(height)});
            `
        fs.writeFileSync('./code.js', code);
    }
}

总结:DOM如何生成的

  • 当服务端返回的类型是text/html时,浏览器会将收到的数据通过HTMLParser进行解析 (边下载边解析)
  • 在解析前会执行预解析操作,会预先加载JSCSS等文件
  • 字节流 -> 分词器 -> Tokens -> 根据token生成节点 -> 插入到 DOM树中
  • 遇到js:在解析过程中遇到script标签,HTMLParser会停止解析,(下载)执行对应的脚本。
  • js执行前,需要等待当前脚本之上的所有CSS加载解析完毕(js是依赖css的加载)

浏览器渲染原理-模拟请求->渲染流程

  • CSS样式文件尽量放在页面头部,CSS加载不会阻塞DOM tree解析,浏览器会用解析出的DOM TREE和 CSSOM 进行渲染,不会出现闪烁问题。如果CSS放在底部,浏览是边解析边渲染,渲染出的结果不包含样式,后续会发生重绘操作。
  • JS文件放在HTML底部,防止JS的加载、解析、执行堵塞页面后续的正常渲染
转载自:https://juejin.cn/post/7242186721784791100
评论
请登录