一、从输入URL到页面加载显示完成都发生了什么?
这个问题的根本:知识点广,区分度高。
回答思路:渲染过程是重点,其他自己擅长的点可以适当展开。
如何解答:
1、UI thread(UI线程)先判断输入的是搜索还是URL地址,如果是搜索则启用浏览器设置的默认搜索引擎进行搜索,如果是URL则开始URL的相应解析请求。
URL的组成结构可以细讲一下,如下图拆解图。
2、UI线程执行完之后会通知Network thread(网络线程),开始发起网络请求。
网络请求步骤:
(1)、DNS查找IP
(2)、协议是https,需要建立TLS连接
(3)、如果收到301状态码,重新执行Network thread(网络线程)发起新请求,跟重定向新地址创建连接
(4)、设置UA等信息,发送GET请求
(5)、服务器上的应用处理请求,组织好Response信息返回给浏览器,对服务端熟悉的可以展开
(6)、浏览器读取Response信息,分析数据类型
(7)、安全检查
(8)、通知UI线程数据准备就绪
UI线程和网络线程都是属于Browser Process(浏览器进程)。
3、Renderer process(渲染进程),重点回答
在渲染进程里,我们最主要也最熟悉的线程是Main thread主线程,主要围绕主线程来回答。
Raster Thread绘制线程,Compositor Thread复合线程。
(1)解析文本构建DOM
(2)边解析DOM边加载子资源,如图片,CSS等资源
(3)JS阻塞解析,如果JS不对DOM做修改的操作,可以使用async/defer属性进行非阻塞加载
(4)解析CSS,计算computed styles
(5)构建布局树,位置和大小
(6)创建绘制记录,确定绘制顺序,绘制前的准备工作
(7)将页面拆分图层,构建图层树,提高绘制的效率
(8)复合线程像素画图层,创建一条复合帧,即将所有绘制好的图层合并
以上就是主要以渲染层面回答这个问题的知识点。如果还要继续扩展可以讲http头信息、缓存机制、状态码等。
二、什么是首屏加载?怎么优化?
这个问题的核心:
(1)Web增量加载的特点决定了首屏性能不会完美
(2)过长的白屏影响用户体验和留存
(3)首屏(above the fold)=> 初次印象
如何回答首屏:
首屏的对于用户3个关键时刻:
(1)白屏时页面发生了什么事情,什么时候可以出现内容,First Contentful Paint(FCP)
(2)页面开始出现内容知道网站可访问,开始等待页面具体有意义的内容出现完成,Largest Contentful Paint(LCP)
(3)内容出现后会想页面能不能用,什么时候可以进行交互,Time to Interactive(TTI)
以上三个关键节点的量化指标:
先回答以上首屏的节点和相应指标,后回答如何进行首屏优化:
1、资源体积太大:资源压缩,传输压缩,代码拆分,Tree shaking,HTTP2,缓存
2、首页内容太多:路由/组件/内容 lazy-loading,预渲染/SSR,Inline CSS
3、加载顺序不合适:prefetch,preload
三、JS是怎样管理内存?什么情况会造成内存泄漏?
问题核心:内存泄漏严重影响性能,高级语言不等于不需要管理内存
如何回答:
1、JS相关的内存机制
——变量创建时自动分配内存,不使用时“自动”释放内存,俗称GC。
——所有的GC都是近似实现,只能通过判断变量是否还能再次访问到。
——局部变量,函数执行完,没有闭包引用,就会被标记回收。
——全局变量,直至浏览器卸载页面时释放。
——GC两种实现方式:
(1)引用计数,无法解决循环引用的问题
(2)标记清除,不能被访问到的被标记,然后进行清除,这是目前大多数浏览器的实现方式。
标记清除也有缺陷,如下代码,b属性永远不会被访问,但是因为a一直被访问所以b不会被清除
const obj = { a:new Array(1000),b:new Array(2000)} setInterval(()=>console.log(obj.a),1000)
2、如何避免内存泄漏
(1)避免意外的全局变量产生,如方法里面声明变量不使用var,let,const,造成意外的全局变量产生
(2)避免反复运行引发大量闭包,如下代码
var store; function outer(){ var largeData = new Array(10000000); var prevStore = store; function inner(){ if(prevStore) return largeData } return function(){} } setInterval(function(){ store = outer() },10)
(3)避免脱离DOM元素,如下代码
function createElement(){ const div=document.createElement('div'); div.id='detached'; return div } const detachedDiv = createElement(); document.body.appendChild(detachedDiv); function deleteElement(){ document.body.removeChild(document.getElementById('detached')) } deleteElement()
上述代码虽然detached这个DOM元素以及被删除掉了,但是detachedDiv这个变量引用了这个DOM元素,这个变量没有被回收那么这个DOM元素依然会存在内存中。