【面试系列一】如何回答如何理解重排和重绘

最近在面试的时候经常会问:如何理解重排和重绘? 我发现很多候选人都没有答道关键点上,感觉是在哪里看到过相关的文章,听起来零零散散,毫无逻辑。 错误示范 一般的面试过程就是这样的: 面试官:如何理解重排和重绘? 候选人:重排就是当页面的结构发生变化了,就会重排,比如改变变字体的大小,增删 DOM 元素这样的。重绘就是页面结构没有变化,只是外观变了,比如改了一下字体颜色、背景颜色这样的。就只会发生重绘。 当然他说的也没错,我也不能直接说他错,就继续引导 面试官:那重排和重绘有什么关系吗? 候选人:重排一定会导致重绘,重绘不一定会导致重排。 面试官:为什么呢? 候选人:因为重排结构发生变化了嘛,肯定会导致重绘。 我这时候表情就是这样: 如果你觉得上面的回答很真实,那下面的你确定得好好看看。 接下来一般我不会直接跳过,我会再问一下浏览器关键渲染路径引导一下。 如果不知道的话,我会再引导一下(这个时候其实基本已经放弃了)。 问一下你知道当浏览器加载到一个 HTML 会发生什么事情吗? 如果还是不知道的话,这下一题了。 如果知道关键渲染路径的,基本引导一下还是可以搞明白,如果不清楚的,肯定是理解不了重排和重绘的。 考点 这道题我一般考察两个点: 浏览器的关键渲染路径。如果答不到这上面,一般这个题就凉了。 性能优化,如果减少重绘和回流,当然这个点肯定也是要基于对 关键渲染路径 的理解(这点不是关键点)。 复习 复习的目的是为了知道考点是啥,简单的给大家复习一下,更详细的内容希望按我介绍的知识点(可以看我文末推荐的文章进行深入学习),毕竟复习不是上课。 我们可以能知道,写了 HTML、CSS、JavaScript 就可以将页面渲染到屏幕上,但是浏览器是如何把我们的代码渲染到屏幕上的像素点的呢?这就需要了解到这么一个概念 CRP: 关键渲染路径(Critical Rendering Path)是浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。优化关键渲染路径可提高渲染性能。 大致步骤是这样:在解析 HTML 时会创建 DOM,HTML 可以请求 JavaScript,而 JavaScript 反过来,又可以更改 DOM。HTML 包含或请求样式,依次来构建 CSSOM。浏览器引擎将两者结合起来以创建 Render Tree (渲染树),Layout(布局)确定页面上所有内容的大小和位置,确定布局后,将像素 Paint (绘制)到屏幕上。 优化关键渲染路径可以缩短首次渲染的时间。了解和优化关键渲染路径对于确保重排和重绘可以每秒 60 帧的速度进行,以确保高效的用户交互并避免讨厌是很重要的。 接下来研究一下详细的过程: 步骤 1. 生成DOM DOM构建是增量的。浏览器从远程下载 Byte => 根据相应的编码 (如 utf8) 转化为字符串 => 通过 AST 解析为 Token => 生成 Node => 生成 DOM。...

March 15, 2022 · 2 min · 302 words · 桃翁

使用 React 和 TypeScript 编写干净代码的10个必知模式

React 是一个 JavaScript 库,它是当今最流行和行业领先的前端开发库。 JavaScript 是一种松散的类型化语言,因此,它捕获了运行时。这样做的结果就是 JavaScript 错误被捕获得非常晚,这可能导致严重的 bug。 当然 React 作为一个 JavaScript 库,也继承了这个问题。 干净代码(Clean code)是一种一致的编程风格,它使代码更容易编写、读取和维护。任何人都可以编写计算机可以理解的代码,但是优秀的开发人员可以编写人类可以理解的干净的代码。 干净的代码是一种以读者为中心的开发风格,它提高了我们的软件质量和可维护性。 编写干净代码需要编写具有清晰和简单的设计模式的代码,这使得人们可以轻松地阅读、测试和维护代码。因此,干净的代码可以降低软件开发的成本。这是因为编写干净的代码所涉及的原则,消除了技术债务。 在本文中,我们将介绍一些在使用 React 和 TypeScript 时使用的有用模式。 💡 为了让您的团队更容易地保持代码健康并优先处理技术债务工作,请尝试使用 Stepsize 的 VS Code 和 JetBrains 扩展。它们帮助工程师创建技术问题,将它们添加到迭代 中,并持续解决技术债务——而不离开编辑器。 现在让我们来了解一下在使用 React 和 Typescript 时应用的 10 个有用模式: 1. 使用默认导入来导入 React 考虑下面的代码: import * as React from "react"; 虽然上面的代码可以工作,但是如果我们不使用 React 的所有内容,那么导入它们是令人困惑的,也不是一个好的做法。一个更好的模式是使用如下所示的默认导出: import React, {useContext, useState} from "react"; 使用这种方法,我们可以从 React 模块中解构我们需要的东西,而不是导入所有的内容。 注意: 要使用这个选项,我们需要配置 tsconfig.json 文件,如下所示: { "compilerOptions": { "esModuleInterop": true" } } 在上面的代码中,通过将 esModuleInterop 设置为 true,我们启用了 allowSyntheticDefaultImports ,这对于 TypeScript 支持我们的语法非常重要。...

March 9, 2022 · 4 min · 835 words · 桃翁

img 和 picture 的区别和使用场景

img img 是 HTML4 时就有的标签, 至今仍然是在网页中嵌入图片的最常用的方式。 与 <span>, <em> 等标签一样属于行内标签 (准确地说属于 Phrasing Content)。下面是一个示例: <img src="favicon72.png" alt="MDN logo" srcset="favicon144.png 2x"> img 其实也可以控制在高清屏幕采用哪个图片,适合用在移动端 picture <picture> <source srcset="/media/cc0-images/surfer-240-200.jpg" media="(min-width: 800px)"> <img src="/media/cc0-images/painted-hand-298-332.jpg" alt="" /> </picture> 要决定加载哪个URL,user agent 检查每个 <source> 的 srcset、media 和 type 属性,来选择最匹配页面当前布局、显示设备特征等的兼容图像。 picture 就可以方便的控制在某种媒体类型,加载哪个图片。感觉比较适合做响应式用。 相比 img 标签,picture 提供了更丰富的响应式资源选择方式; picture 是 HTML5 中定义新标签, 其中可以定义若干个 <source>,浏览器会匹配 <source> 的 type, media, srcset 等属性, 来找到最适合当前布局、视口宽度、设备像素密度 的一个去下载。 为了向下兼容不识别 <picture> 和 <source> 的浏览器,<picture> 中还可以写一个 <img> 作为 fallback。...

July 1, 2021 · 2 min · 234 words · 桃翁

如何应用 SOLID 原则在 React 中整理代码之开闭原则

SOLID 是一套原则。它们主要是关心代码质量和可维护性的软件专业人员的指导方针。 React 不是面向对象,但这些原则背后的主要思想可能是有帮助的。在本文中,我将尝试演示如何应用这些原则来编写更好的代码。 在前一篇文章中,我们讨论了单一责任原则。今天,我们将讨论 SOLID 的第二个原则: 开闭原则。 本系列其他文章 如何应用 SOLID 原则在 React 中整理代码之单一原则 什么是开闭原则? Robert c. Martin 认为这个原则是面向对象设计最重要的原则。但他不是第一个定义这个概念的人。Bertrand Meyer 于1988年在他的《面向对象软件构造》一书中写到了这一点。他解释了开放/封闭原则: 软件实体(类、模块、功能等)应该对扩展开放,但对修改关闭。 这个原则告诉您以这样一种方式来编写代码,即您能够在不更改现有代码的情况下添加其他功能。 让我们看看我们在哪里可以应用这个原则。 让我们从一个例子开始 假设我们有一个 User 组件,其中我们传递用户的详细信息,这个类的主要目的是显示该特定用户的详细信息。 这是一个很简单的开始。但是我们的生活并不是那么简单。几天后,我们的经理告诉我们系统中有三种类型的用户: SuperAdmin、 Admin 等等。 它们每个都有不同的信息和功能。 一个糟糕的解决方案 第一个也是显而易见的解决方案:在组件中包含一个条件,并根据不同的用户类型呈现不同的信息。 import React from 'react'; export const User = ({user}) => { return <> <div> Name: {user.name}</div> <div> Email: {user.email}</div> { user.type === 'SUPER_ADMIN' && <div> Details about super admin</div> } { user.type === 'ADMIN' && <div> Details about admin</div> } </> } 你知道这里出了什么问题吗?...

May 24, 2021 · 2 min · 219 words · 桃翁

理解 JavaScript 中的执行上下文和执行栈

译者序 最近在研究 JavaScript 基础性的东西,但是看到对于执行上下文的解释我发现有两种,一种是执行上下文包含:scope(作用域)、variable object(变量对象)、this value(this 值),另外一个种是包含:lexical environment(词法环境)、variable environment(变量环境)、this value(this 值)。 后面我查阅了不少博客以及 ES3 和 ES5 的规范才了解到,第一种是 ES3 的规范,经典书籍《JavaScript高级程序设计》第三版就是这样解释的,也是网上广为流传的一种,另一种是 ES5 的规范。 然后我接着又去翻了 ES2018 中的,发现又有变化了,已经增加了更多的内容了,考虑到这部分内容颇为复杂,准备后面再进行总结分享,查资料的时候看到这篇讲执行上下文(ES5 )的还不错,所以就翻译出来先分享给大家。 以后看到变量对象、活动对象知道是 ES3 里面的内容,而如果是词法环境、变量环境这种词就是 ES5 以后的内容。 以下是正文: 什么是执行上下文? 简而言之,执行上下文是计算和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。 执行上下文的类型 JavaScript 中有三种执行上下文类型。 全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。 Eval 函数执行上下文 — 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。 执行栈 执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO(后进先出)的数据结构,被用来存储代码运行时创建的所有执行上下文。 当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。 引擎会执行处于栈顶的执行上下文的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。 让我们通过下面的代码示例来理解: let a = 'Hello World!...

April 2, 2020 · 3 min · 448 words · 桃翁