HTML 面试相关
1. HTML 文件中的 DOCTYPE 是什么作用?
HTML 超文本标记语言: 是一个标记语言, 就有对应的语法标准 DOCTYPE 即 Document Type,网页文件的文档类型标准。 主要作用是告诉浏览器的解析器要使用哪种 HTML 规范 或 XHTML 规范 来解析页面。 DOCTYPE 需要放置在 HTML 文件的 <html>
标签之前,如:
html
<!DOCTYPE html>
<html>
...
</html>
(目前主流)
2. 前缀为 data-
开头的元素属性是什么?
这是一种为 HTML 元素添加额外数据信息的方式,被称为 自定义属性。
我们可以直接在元素标签上声明这样的数据属性:
html
<div id="mydiv" data-message="Hello,world" data-num="123"></div>
也可以使用 JavaScript 来操作元素的数据属性:
js
let mydiv = document.getElementById("mydiv");
// 读取
console.log(mydiv.dataset.message);
// 写入
mydiv.dataset.foo = "bar!!!";
注意:在各种现代前端框架出现后,这种原生的自定义属性已经变得不太常用了, 以前的使用频率非常高。
3. HTML5 对比 HTML4 有哪些不同之处?
考察点: 是否了解 html5 新增的一些新特性
记忆角度: 更标准, 新增标签, 新增 type 表单属性, 新增全域属性, 新增 API...
不同点 | 备注说明 |
---|---|
只有一种 DOCTYPE ⽂件类型声明(统一标准) | <!DOCTYPE html> |
增加了一些新的标签元素(功能, 语义化) | section, video, progress, nav, meter, time, aside, canvas, command, datalist, details, embed, figcaption, figure, footer, header, hgroup... |
input 支持了几个新的类型值 | date, email, url 等等 |
新增了一些标签属性 | charset(⽤于 meta 标签);async(⽤于 script 标签) |
新增的全域属性 | contenteditable(可编辑的 div), draggable... hidden... |
新增 API | 本地存储, 地理定位, Canvas 绘图, 拖拽 API, 即时通信 WebSocket... |
获取地理定位: navigator.geolocation.getCurrentPosition(successCallback, errorCallback) (为了安全, 需要在 https 网站使用)
4. meta 标签有哪些常用用法?
<meta>
标签的具体功能一般由 name/http-equiv
和 content
两部分属性来定义。
- 如果设置 name 属性,则它描述的是网页文档的信息(例如:作者、⽇期和时间、⽹⻚描述、 关键词)
- 如果设置 http-equiv 属性,则它描述的相当于是 HTTP 响应头信息(例如:网页内容信息, 网页缓存等)
一些常用的功能及写法:
- 设置网页关键词 (SEO)
html
<meta name="keywords" content="电商,好货,便宜" />
- 设置网页视口(viewport)控制视⼝的⼤⼩、缩放和⽐例等 (移动端开发)
html
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
/>
- 设置 http 响应头:Content-Type 网页内容类型 (字符集)
html
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<!-- 设置字符集可简写为 -->
<meta charset="utf-8" />
5. img 标签的 srcset 的作用是什么?
考察点: 处理响应式图片的方式 (css 媒体查询换的是背景图片, 而不是 img 标签的 src)
开发者和设计师们竞相寻求 处理响应式图片 的方法。这的确是一个棘手的问题
其实通过使用 img 标签的 srcset 属性,可定义一组额外的图片集合,让浏览器根据不同的屏幕状况选取合适的图片来显示。
也就是图片的响应式处理能力。
如果你的响应式需求比较简单,只需要针对屏幕的不同 dpr (device pixel ratio,设备像素比)来决定图片的显示的话,
dpr 设备像素比, 越高, 能够显示的越清晰 (dpr: 2, dpr: 3)
那么就只要这么写:
html
<img srcset="320.png 1x, 640.png 2x, 960.png 3x" />
对于可变宽度的图像,我们使用srcset
搭配w
描述符以及sizes
属性 。
w
描述符告诉浏览器列表中的每个图象的宽度。sizes
属性需要至少包含两个值,是由逗号分隔的列表。
根据最新规范,如果srcset
中任何图像使用了w
描述符,那么必须要设置sizes
属性。
sizes
属性有两个值:
第一个是媒体查询条件;
第二个是图片对应的尺寸值,
在特定媒体条件下,此值决定了图片的宽度。
需要注意是,源图尺寸值不能使用百分比,如果要用 100%,
vw
是唯一可用的 CSS 单位。
html
<img
alt="img元素srcset属性浅析"
srcset="320.png 320w, 480.png 480w, 640.png 640w"
sizes="
(max-width: 320px) 100vw,
(max-width: 360px) 320px,
(max-width: 480px) 360px,
(max-width: 640px) 480px,
640px"
src="640.png"
/>
为 img 定义以上属性后,浏览器的工作流程如下:
- 检查设备的实际宽度
- 检查 img 标签的 sizes 属性中定义的媒体查询条件列表,并计算哪个条件最先匹配到
- 得到图片此时的响应式宽度
- 加载 srcset 中最接近, 最适合媒体查询匹配到的宽度的图片
注意: 测试时, 清除缓存测试, 因为一旦加载了高清图, 就不会也没有必要, 回过去再用小图替换了
且我们无法确定究竟显示哪张图像,因为每个浏览器根据我们提供的信息挑选适当图像的算法是有差异的。
(译者注:srcset 和 size 列表是对浏览器的一个建议(hint),而非指令。由浏览器根据其能力、网络等因素来决定。)
6. 响应式图片处理优化: Picture 标签
考察点: 响应式图片处理
picture
元素就像是图像和其源的容器。浏览器仍然需要img
元素,用来表明需要加载的图片
在 <picture>
下可放置零个或多个<source>
标签、以及一个<img>
标签,为不同的屏幕设备和场景显示不同的图片。
如果 source 匹配到了, 就会优先用匹配到的, 如果没有匹配到会往下继续找
使用picture
元素选择图像,不会有歧义。
浏览器的工作流程如下:
浏览器会先根据当前的情况,去匹配和使用
<source>
提供的图片如果未匹配到合适的
<source>
,就使用<img>
标签提供的图片
html
<picture>
<source srcset="640.png" media="(min-width: 640px)" />
<source srcset="480.png" media="(min-width: 480px)" />
<img src="320.png" alt="" />
</picture>
7. 在 script 标签上使用 defer 和 async 的区别是什么?
明确: defer 和 async 的使用, 可以用于提升网页性能
script 标签存在两个属性,defer 和 async,因此 script 标签 的使用分为三种情况:
执行顺序:
- 多个带 async 属性的标签,不能保证加载的顺序,完全依赖于网络传输结果,谁先到执行谁;
- 多个带 defer 属性的标签,按照在 HTML 中出现的顺序执行;
是否阻塞解析 html
async:可能阻塞,也可能不阻塞
- async 阻塞的场景:当浏览器遇到带有 async 属性的 script 时,请求该脚本的网络请求是异步的,不会阻塞浏览器解析 HTML,一旦网络请求回来之后,如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再进行解析。
- async 不阻塞的场景:如果在 JS 脚本请求回来之前,HTML 已经解析完毕了,立即执行 JS 代码,不存在阻塞问题
defer: 不阻塞
<script src="example.js"></script>
没有 defer 或 async 属性,浏览器会立即加载并执行相应的脚本。
不等待后续加载的文档元素,读到就开始加载和执行,此举会阻塞后续文档的加载
<script async src="example.js"></script>
有了 async 属性,表示后续文档的加载和渲染与 js 脚本的加载和执行是并行进行的,即异步执行;
<script defer src="example.js"></script>
有了 defer 属性,加载后续文档的过程和 js 脚本的加载是并行进行的(异步),此时的 js 脚本仅加载不执行, js 脚本的执行需要等到文档所有元素解析完成之后,DOMContentLoaded 事件触发执行之前。
下图是使用了 defer、async、和未使用时的运行情况对比:
【上图的图例说明】
绿线:HTML 的解析时间
蓝线:JS 脚本的加载时间
红色:JS 脚本的执行时间
从图中我们可以明确一下几点:
1.defer 和 async 在网络加载过程是一致的,都是异步执行的;(放在页面顶部, 也不会阻塞页面的加载, 与页面加载同时进行)
2.两者的区别, 脚本加载完成之后, async 是立刻执行, defer 会等一等 (等前面的 defer 脚本执行, 等 dom 的加载)
所以, js 脚本加上 async 或 defer, 放在头部可以减少网页的下载加载时间, 如果不考虑兼容性, 可以用于优化页面加载的性能
8. 前端本地数据存储的方式和区别
- Cookies
- localStorage
- sessionStorage
- Web SQL
- IndexedDB
以上几种前端存储的区别是什么?
方式名称 | 标准说明 | 功能说明 |
---|---|---|
Cookies | HTML5 前加入 | 1.会为每个请求自动携带所有的 Cookies 数据,比较方便,但是也是缺点,浪费流量; 2.每个 domain(站点)限制存储 20 个 cookie; 3.容量只有 4K 4.浏览器 API 比较原始,需要自行封装操作。 (js-cookie) |
localStorage | HTML5 加入 | 1.兼容 IE8+,操作方便; 2.永久存储,除非手动删除; 3.容量为 5M |
sessionStorage | HTML5 加入 | 1.功能基本与 localStorage 相似,但当前页面关闭后即被自动清理; 2.与 Cookies、localStorage 不同点是不能在所有同源窗口间共享,属于会话级别的存储 |
Web SQL | 非标准功能 | 1.2010 年已被废弃,但一些主流浏览器中都有相关的实现; 2.类似于 SQLite 数据库,是一种真正意义上的关系型数据库,⽤ SQL 进⾏操作; |
IndexedDB | HTML5 加入 | 1.是一种 NoSQL 数据库,⽤键值对进⾏储存,可进⾏快速读取操作; 2.适合复杂 Web 存储场景,⽤ JS 操作⽅便 (前端大量存数据的场景较少, 如果有, 可以用) 3.存储空间容量, 大于等于 250MB,甚至没有上限 |
9. HTML、XML、XHTML 之间有什么区别?
语言 | 中文名 | 说明 |
---|---|---|
HTML4 | 超文本标记语言 | 主要用于做界面呈现。HTML 是先有实现,后面才慢慢制定标准的,导致 HTML ⾮常混乱和松散,语法非常的不严谨。 |
XML | 可扩展标记语言 | 主要⽤于存储数据和结构。语法严谨,可扩展性强。由于 JSON 也有类似作⽤但更轻量⾼效, XML 的市场变得越来越⼩。 |
XHTML | 可扩展超文本标记语言 | 属于加强版 HTML,为解决 HTML 的混乱问题而生,在语法方面变得和 XML 一样严格。另外,XHTML 的出现也催生了 HTML 5,让 HTML 向规范化严谨化过渡。 |
HTML5 | 超文本标记语言 | 在 HTML 的基础上进行拓展,用于页面呈现 (目前标准) |
10. 谈谈你对 HTML 语义化的理解?
考察核心点: 语义化的好处 (利于 SEO, 可阅读性更好)
语义化的好处:
对开发者的好处 | 对机器/程序的好处 |
---|---|
使⽤了语义化标签的程序,可读性明显增强,开发者可以比容易和清晰地看出⽹⻚的结构;这也更利于整个开发团队的协作开发和后续维护工作 | 带有语义的网页代码在⽂字类应用上的表现⼒丰富,利于搜索引擎爬⾍程序来爬取和提取出有效的信息;语义化标签还⽀持读屏软件,根据⽂章可以⾃动⽣成⽬录等,方便特殊人群无障碍的使用这些网页程序。 |
11. 浏览器是如何渲染页面的?
当浏览器的 网络线程
收到 HTML 文档后,会产生一个渲染任务,并将其传递到 渲染主线程的消息队列, 在事件循环
的机制下,渲染主线程取出消息队列中的渲染任务
,开启渲染流程
整个渲染流程分为多个阶段:
渲染主线程阶段 :解析 HTML(parse HTML) =>> 样式计算(recalculate style) =>> 布局(layout) =>> 分层(layer) =>> 绘制(paint)
合成线程阶段 :分块(tiling) =>> 光栅化(raster) =>> 画(draw)
GPU 进程
主要步骤: 每个阶段都有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入,这样整个渲染流程就形成啦一套组织严密的流水线
一. 解析 HTML(parse HTML):解析 HTML 文件得到 DOM
树和 CSSOM
树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中。
解析过程中遇到 CSS 解析 CSS,遇到 JS 执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。
如果主线程解析到
link
位置,此时外部的 CSS 文件还没有下载解析好,主线程不会等待,继续解析后续的 HTML。**这是因为下载和解析 CSS 的工作是在预解析线程中进行的。**这就是 CSS 不会阻塞 HTML 解析的根本原因。如果主线程解析到
script
位置,会停止解析 HTML,转而等待 JS 文件下载好,并将全局代码解析执行完成后,才能继续解析 HTML。**这是因为 JS 代码的执行过程可能会修改当前的 DOM 树,**所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因。
二、样式计算(recalculate style):会得到一棵带有样式的 DOM
树
主线程会遍历得到的 DOM 树,依次为树中每个节点计算它最终的样式,称之为 Computed Style, 在这一过程中,很多预设值会变成绝对值,比如red
会变成rgb(255,0,0)
;相对单位会变成绝对单位,比如em
会变成px
这一步完成后,会得到一棵带有样式的 DOM 树
三、布局(layout):布局会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息,相对包含块的位置,
大部分时候 DOM 树和布局树并非一一对应
比如
display:none
的节点没有几何信息,因此不会生成布局树,又比如使用了为伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但他们拥有几何信息,会生成到布局树中,还有匿名行盒,匿名块盒等会让他们不一一对应
四、 分层(layer): 主线程会使用复杂的策略对整个 布局树 进行分层,
分层的好处是,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率
滚动条、堆叠上下文、transform、opacity 等或多或少都会影响分层结果,也可以通过 will-change
属性最大程度的影响分层结果
五、 绘制(paint):渲染主线程只做到这一步 ,渲染主线程会为每个层单独产生绘制指令,用于描述这一层的内容如何画出来
完成绘制后,主线程将每个图层的绘制信息交给合成线程,剩余工作将由合成线程完成
六、分块(tiling):合成线程首先对每个图层进行分块,将其划分为更多的小区域,他会从线程池中拿出多个线程来完成分块工作
七、光栅化(raster): 合成线程会将信息交给 GPU 进程, 开启多个线程加速光栅化过程,以及高的速度完成光栅化
GPU 进程会开启多个线程来完成光栅化,并且优先处理 优先处理靠近视口的块
光栅化的结果就是一块一块的位图
八、 画(draw):合成线程拿到每个层,每个块的位图后,生成一个个 指引(quad)信息,
- 指引会标识出每个位图应该画到屏幕的那个位置,以及会考虑旋转,缩放等变形,
- 合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像
页面在首次加载时必然会经历 reflow 和 repaint。
reflow 过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。
所以我们应该尽可能少的减少 reflow (重新布局) 。 例如: transform 变换, 只会触发重绘, 不会触发重排 (效率非常高)
Transform 快的原因:因为 transform(变形) 既不会影响布局也不会影响绘制指令, 它影响的只是渲染流程的最后一个「draw」阶段,并且发生在合成线程,与渲染主线程无关
12. 什么是 reflow(回流)?
Reflow 的本质就是重新计算 layout 树
当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout
当为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 js 代码全部完成后再进行统一计算,所以,改动属性造成的 reflow 是异步完成的
也正因为如此,当 js 获取布局属性时,就可能造成无法获取到最新的布局信息,浏览器在反复权衡下,最终决定获取属性立即 reflow 所以改变布局树后又立即拿改变后的数据的话,就会立即 reflow,就会变为同步操作
13. 什么是 repaint(重绘)?
Repaint 的本质是重新根据分层信息计算了绘制指令
- 当改动了可见样式后,就需要重新计算,会引发 repaint
- 由于元素的布局信息也属于可见样式,所以 reflow(回流) 一定会引起 repaint(重绘),但是 repaint(重绘)不一定影响 reflow(回流)
- transform 变换, 只会触发重绘, 不会触发重排 (效率非常高)
14. 浏览器 reflow(回流)与 repaint(重排)的区别是什么?
页面首次渲染, 必然会进行一次 reflow
和 一次 repaint
- reflow: 回流 (布局相关的,读取某些属性值),改变 layout 树
- repaint: 重绘(绘制, 颜色, 字体, .... 跟结构无关的)
reflow(回流):
- 回流是由于节点的 ⼏何属性 发⽣改变,需要重新计算每个节点的几何信息,会发生回流
- getComputedStyle 方法和读取一些需要即时计算的属性也会发生回流
比如:
使用 JS 改变 DOM 元素时会触发 reflow,即添加(appendChild)、删除(removeChild)DOM 元素或者改变 DOM 元素的可见性(display:none)
CSS 中 width/height/border/margin/padding 的修改,如 width=778px;
内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
字体大小改变或更换字体 (font);
浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
读取元素的某些属性,offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
这些属性有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流,除此还包括 getComputedStyle 方法,原理是一样的
repaint(重绘)
- 一般在改变 DOM 元素的视觉效果时触发(例如:改变元素背景⾊),即不涉及任何排版布局的问题时触发,它主要针对的是某一 dom 元素的重新绘制
比如:
- color 的修改,如 color=#ddd;
- text-align 的修改,如 text-align:center;
- a:hover 也会造成重绘;
- :hover 引起的颜色等不导致页面回流的 style 变动等等。
15. 为什么 transform 的效率高?
因为 transform(变形)发生在合成线程,与渲染主线程无关,不会触发 reflow(回流) 和 repaint(重绘)
16. 如何避免重排或重绘?
主要有三大方式来避免:
- 集中修改样式
- 使用文档碎片(DocumentFragment)
- 将元素提升为合成层
集中修改样式
通常以改 class 的⽅式,实现样式的集中修改。
js
el.setAttribute("className", isDark ? "dark" : "light");
使用文档碎片
通过 document.createDocumentFragment
可创建⼀个游离于 DOM 树外的节点,在该节点上做批量操作后再将它插⼊ DOM 树中,只会引发⼀次重排。
js
// 创建碎片节点
const fragment = document.createDocumentFragment();
// 多次操作碎片节点
for (let i = 0; i < 10; i++) {
const node = document.createElement("p");
node.innerHTML = i;
fragment.appendChild(node);
}
// 一次性添加到 DOM 树中
document.body.appendChild(fragment);
将元素提升为合成层
将元素提升为合成层的最好⽅式是使⽤ CSS 的 will-change
属性:
css
#target {
will-change: transform;
}
提升为合成层有下列几个优点:
合成层的位图会由 GPU 合成,⽐由 CPU 处理更快
当需要重绘时只重绘本身,不影响其他层
transform
和opacity
不会触发重排和重绘
17. 给页面中的所有 div
创建一个边框
js
document.styleSheets[0].addRule("div", "border:2px solid #f40");
document.styleSheets
:获取页面使用的 css 样式表