前端面试基础知识整理【Day-3】

目录

前言

浏览器渲染篇

1.关键路径渲染

2.回流和重绘(绘制)的区别

[3.script 标签的 defer 和 async](#3.script 标签的 defer 和 async)

代码篇

1.手写一个EventBus(订阅-发布模式)

回顾篇

1.为什么做动画时,改变left比改变transform属性更消耗性能


前言

往期回顾:

前端面试基础知识整理【Day-1】-CSDN博客

前端面试基础知识整理【Day-2】-CSDN博客

浏览器渲染篇

1.关键路径渲染

关键路径渲染指的是浏览器把代码变成屏幕上像素经过的六个阶段,任何一个阶段卡住,页面就会白屏或者卡顿,下面是六个阶段:

  1. 构建DOM树:解析HTML,生成DOM节点树
  2. 构建CSSOM树,解释CSS,生成CSS规则树
  3. 生成渲染树:(DOM + CSSOM)才是渲染树,display:none的节点不会出现在渲染树中,但是visibility:hidden的节点会出现在渲染树中(因为它占据空间)
  4. 回流:计算每个节点在屏幕上的位置和大小(即几何信息
  5. 重绘(绘制):填充像素(颜色、背景、边框等等)
  6. 合成:浏览器将不同的层合并,最终显示在屏幕上

所以,根据上面的六个阶段,如果想要优化浏览器页面加载速度可以从以下几个方面入手:

  1. 减少关键资源请求数(少写几个CSS、JS文件)
  2. 减少关键资源体积(例如将png格式的图片改为webp格式的图片)
  3. 异步加载JS文件,只在需要的时候才动态import文件

2.回流和重绘(绘制)的区别

回流必定引起重绘,但是重绘不一定引起回流

这句话很好理解:"回流计算的是节点的几何信息(大小、位置),重绘计算的是节点的(颜色、背景色、阴影等),可以说,重绘是对回流计算的节点进行美化的一个过程"

因此,当某个节点大小改变时,那么颜色、背景色这些属性对应的也要进行重绘来填充

而某个节点的颜色改变时,只需要重绘填充颜色即可,至于节点的大小、位置并没有改变,所以回流不会发生

下面是对回流重绘的一个解释:

回流

定义 :当元素的几何属性(宽、高、位置、显示隐藏)发生变化时,浏览器需要重新计算元素的几何信息

性能:消耗资源非常多,一个元素的大小变化在整个DOM树节点上的影响是全局性的

触发条件

  • 改变窗口大小
  • 改变字体大小
  • 添加/删除DOM节点
  • 改变width、height、margin、padding
  • 读取offsetWidth、offsetHeight、scrollTop等属性时

重绘

定义:当元素的外观属性发生变化,但不影响布局时

性能:资源消耗较少

触发条件

  • color
  • background-color
  • visibility
  • outline

3.script 标签的 defer 和 async

在HTML中,**<script>**标签默认会阻塞DOM的解析,当浏览器执行到<script>标签时,会从服务器下载并执行JS,执行完JS之后再解析HTML

为了不阻塞页面显示,<script> 引入了两个属性:"defer "和"async"

两个属性改变的是**<script>** 标签的下载时机执行时机执行顺序

特性 <script> <script async> <script defer>
HTML解析 立即停止,等待JS代码下载和执行 不停止,JS并行下载 不停止,JS并行下载
JS执行时机 下载完立即执行 下载完立即执行 等到HTML全部解析完 (DOMContentLoaded前)执行
执行顺序 (多个<script>标签) 按代码书写顺序 谁先下完执行谁 严格按代码书写顺序
适用场景 关键且依赖顺序的脚本 独立的脚本 (埋点、广告) 依赖DOM或其它脚本的方案

一句话概括:

<script async>:下载完立即中断HTML去执行,谁下载快谁先被执行,但如果a.js依赖于b.js,但是b.js大一点下得慢,a.js先执行就会报错

<script defer>:下载完成并等到HTML解析完成之后再执行,执行顺序严格按照代码顺序,并且可以保证DOM被渲染好了(不用写window.onload)

现在的工程项目中,通常把<script>放在<head>里并加入<defer>,这是最安全、性能最好的选择

在以前的HTML老代码中,可能会将JS代码写在<body>标签的最后,或者在JS里手动添加"window.onload"这很啰嗦,但在现代项目中,直接使用defer关键字即可

一个老项目代码:

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <script>
        window.onload = function() {
            const box = document.getElementById('box');
            box.innerText = "DOM加载完成";
        }
    </script>
</head>
<body>
    <div id="box">准备改变内容</div>
    <script src="vue.js"></script>
    <script src="plugin.js"></script>
    <script src="app.js"></script>
</body>
</html>

经过现代化工程修改后的代码:

javascript 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>现代化工程代码</title>
    <script defer src="vue.js"></script>
    <script defer src="plugin.js"></script>
    <script defer src="app.js"></script>
</head>
<body>
    <div id="box">准备改变内容</div>
</body>
</html>

代码篇

1.手写一个EventBus(订阅-发布模式)

javascript 复制代码
class EventBus {
  constructor() {
    this.events = {};
  }

  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  }

  emit(eventName, ...args) {
    if (this.events[eventName]) {
      const callbacks = [...this.events[eventName]];
      callbacks.forEach(callback => {
        callback(...args);
      })
    }
  }

  off(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(cb => {
        return cb !== callback && cb.originalCallback != callback;
      })
    }
  }

  once(eventName, callback) {
    const onceWrapper = (...args) => {
      this.off(eventName, onceWrapper);
      callback(...args);
    }
    onceWrapper.originalCallback = callback;
    this.on(eventName, onceWrapper);
  }

}


const bus = new EventBus();

const fn1 = (name, age) => console.log(`fn1: ${name}, ${age}`);
const fn2 = (name, age) => console.log(`fn2: ${name}, ${age}`);

console.log("1.on和emit方法展示")
bus.on('sayHi', fn1);
bus.on('sayHi', fn2);
bus.emit('sayHi', 'Alice', 30);

console.log("2.off方法展示")
bus.off('sayHi', fn1);
bus.emit('sayHi', 'Bob', 25);

console.log("3.once方法展示")
const onceOn = (name, age) => console.log(`onceOn: ${name}, ${age}`);
bus.once('greet', onceOn);
bus.emit('greet', 'Charlie', 28);
bus.emit('greet', 'David', 35);

console.log("4.once还没被执行就被off掉")
const onceOff = (name, age) => console.log(`onceOff: ${name}, ${age}`);
bus.once('welcome', onceOff);
bus.off('welcome', onceOff);
bus.emit('welcome', 'Eve', 22);

运行结果:

回顾篇

1.为什么做动画时,改变left比改变transform属性更消耗性能

本问题用"回流/重绘"原理解释:

  1. 首先,改变left属性必然会引起回流,此时性能消耗巨大,此外,还会引起重绘,所以性能消耗更大了
  2. 其次,改变transform属性不会引起节点的几何属性变化,也不会引起节点的外观属性变化,故不会引起回流或者重绘
  3. 最后,使用transform,浏览器会使用GPU加速渲染,大大提升效率
相关推荐
用户98236107902772 小时前
Vite 项目优化分包填坑之依赖多版本冲突问题深度解析与解决方案
前端
陆枫Larry2 小时前
深入浅出:CSS 中的“隐形结界”——BFC 详解
前端·css
wuhen_n2 小时前
JavaScript 深拷贝的完全解决方案
前端·javascript
大时光2 小时前
gsap 配置解读 --3
前端
兰亭古墨2 小时前
钉钉工作台自建组件定时器被禁?用 Swiper 模拟 setInterval 的优雅方案
前端·钉钉
phltxy2 小时前
Vue核心进阶:v-model深度解析+ref+nextTick实战
前端·javascript·vue.js
三小河2 小时前
React 样式——styled-components
前端·javascript·后端
Hi_MrXiao2 小时前
电脑上安装使用多个版本的谷歌浏览器
前端·chrome
广州华水科技2 小时前
单北斗GNSS变形监测一体机在大坝安全监测中的应用探索
前端