Interview -- HTML&CSS
在此系列中开始梳理前端的面试体系内容。
目录
- HTML 中 DOCTYPE 的作用是什么?
- 如何理解 HTML 语义化?
- 块状元素和内联元素有哪些?
- 盒模型的宽度计算
- BFC 的理解和应用
- float 布局的问题
- flex 布局实现一个三点色子
- absolute 和 relative 定位问题
- 居中定位问题
- rem 是什么?
- property 和 attribute 有什么区别
- 请描述一下浏览器渲染过程
- 请描述一下回流和重绘
- 事件分级 DOM0 和 DOM2 的区别
- addEventListener 的第三个参数有哪些
- 事件委托
- 获取浏览器内核
- 大屏自适应
HTML
HTML 考察较少,内容不多。
HTML 中 DOCTYPE 的作用是什么?
<!DOCTYPE html>
DOCTYPE 声明位于 HTML 文档中的第一行,处于 <html>
标签之前。告知浏览器的解析器用什么文档类型定义来解析这个文档。
如果不写, 浏览器会以老旧的"怪异模式"去渲染页面, 不同的浏览器下显示样式效果会不一致。
如何理解 HTML 语义化?
- 让人更易读懂,增加代码的可读性;
- 让搜索引擎更易读懂(SEO)。
块状元素和内联元素有哪些?
- 块状元素:
display: block/table
,有 div、h1、h2、table、ul、ol、p 等。 - 内联元素:
display: inline/inline-block
,有 span、img、input、button 等。
CSS 布局
盒模型的宽度计算
offsetWidth = (内容宽度 + 内边距 + 边距),无外边距。
实际上是看 box-sizing
,默认为 content-box
,可改为 border-box
。
BFC 的理解和应用
全称是 Block format context, 块级格式化上下文。独立渲染一块区域,内部元素的渲染不会影响边界以外的元素。
常见形成条件:
- float 不是 none;
- position 是 absolute 或 fixed;
- overflow 不是 visible 或 clip 的块级元素。(设置 overflow: hidden; 该方法形成 BFC 最常用)
- display 是 flex、inline-block 等。
常见应用是清除浮动。
.clearFix:after {
content: '';
display: table;
clear: both;
}
提示
原理: 后添加一个空值,且 display 为 table 类型触发 BFC,而后 clear 清除浮动影响。
float 布局的问题
圣杯布局和双飞翼布局的目的:
- 三栏布局,中间一栏的内容最先加载和渲染;
- 俩侧内容固定,中间内容岁宽度自适应;
- 一般用于 PC 网页。
圣杯布局实现的技术要点:
- 使用 float 布局;
- 俩侧使用 margin 负值,以便和中间内容横向重叠;
- 防止中间内容被俩侧覆盖,一个用 padding,一个用 margin。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>圣杯布局</title>
<style>
body {
min-width: 650px;
}
#header {
text-align: center;
background-color: green;
}
.container {
padding-left: 200px;
padding-right: 150px;
}
#center {
background-color: blue;
width: 100%;
}
.container .column {
float: left;
}
#left {
position: relative;
right: 200px;
background-color: pink;
width: 200px;
margin-left: -100%;
}
#right {
background-color: yellow;
width: 150px;
margin-right: -150px;
}
#footer {
clear: both;
background-color: green;
text-align: center;
}
</style>
</head>
<body>
<div id="header">--- 圣杯布局 ---</div>
<div class="container">
<div id="center" class="column">中间内容</div>
<div id="left" class="column">圣杯左侧</div>
<div id="right" class="column">圣杯右侧</div>
</div>
<div id="footer">--- 尾部 ---</div>
</body>
</html>
提示
圣杯布局中,right 只用到了 margin-right: -150px
将自身宽度给抵消了。因此依据浮动的原理,不占据位置直接上浮。
margin 属性的百分比值是相对于父元素的宽度(width)来计算的。
左侧布局说明:
width: 200px; margin-left: -100%;
使得左元素覆盖到中间元素的左侧开头;position: relative; right: 200px;
使元素向左移动自身宽度距离(亦可left: -200xp;
)。
<!DOCTYPE html>
<html>
<head>
<title>双飞翼</title>
<style type="text/css">
body {
min-width: 650px;
}
#header {
text-align: center;
background-color: green;
}
.col {
float: left;
}
#main {
width: 100%;
background: blue;
background: #aaccdd;
}
#inner {
margin: 0 200px 0 100px;
}
#left {
width: 100px;
background: red;
margin-left: -100%;
}
#right {
width: 200px;
background: green;
margin-left: -200px;
}
#footer {
clear: both;
background-color: green;
text-align: center;
}
</style>
</head>
<body>
<div id="header">--- 圣杯布局 ---</div>
<div id="main" class="col">
<div id="inner">main</div>
</div>
<div id="left" class="col">left</div>
<div id="right" class="col">right</div>
<div id="footer">--- 尾部 ---</div>
</body>
</html>
提示
双飞翼布局中,需要多用一个 div 盒子包裹主体内容,使其具有延展性。同时也用到了margin
负值将自身宽度给抵消了。
两者的区别,双飞翼将俩侧撑开是中间主体盒子用的 margin 撑出两侧,因此右盒子是 margin-left 负值;
而圣杯是共同的外部盒子用 padding 撑出俩侧,因此右盒子是用 margin-right 负值将自身宽度给去除。
flex 布局实现一个三点色子
flex 常见语法:
- flex-direction
- justify-content
- align-item
- flex-wrap
- align-self
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>色子三点</title>
<style>
body {
min-width: 650px;
}
.box {
width: 200px;
height: 200px;
border: 2px solid #ccc;
border-radius: 10px;
padding: 20px;
display: flex;
justify-content: space-between;
}
.item {
display: block;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #666;
}
.item:nth-child(2) {
align-self: center;
}
.item:nth-child(3) {
align-self: flex-end;
}
</style>
</head>
<body>
<div class="box">
<span class="item"></span>
<span class="item"></span>
<span class="item"></span>
</div>
</body>
</html>
absolute 和 relative 定位问题
- relative 依据自身定位
- absolute 依据最近一层的定位元素定位,定位元素即 absolute、relative、fixed,若都没有则依据 body 进行定位。
居中定位问题
水平居中:
- inline 元素:
text-align:center
- block 元素:
margin: auto
- absolute 元素: left: 50% + margin-left 负自身一半宽度
垂直居中:
- inline 元素:line-height 的值等于 heigh 值(实际上只设置 line-height 行高即可);
- absolute 元素: top:50% + margin-top 负自身一半高度;
- absolute 元素:
transform(-50%, -50%)
; - absolute 元素:
top,left,bottom,right = 0
+margin: auto
更多可查看在另外一篇的总结 《多种方式实现居中》
rem 是什么?
- px:绝对长度单位;
- em:现对长度单位,现对于父元素;
- rem:相对长度单位,相对于根元素 html,响应式布局。
响应式布局的常见方案:media-query,根据不同的屏幕宽度设置根元素 font-size,而后用 rem,基于根元素的相对单位。
网页的视口宽度:
- 屏幕高度:
window.screen.height
- 网页视口高度:
window.innerHeight
- body 高度:
document.body.clientHeight
property 和 attribute 有什么区别
在 HTML 中,"property"(属性)和"attribute"(特性)也是两个相关但不完全相同的概念。它们在 HTML 中的含义和用法略有不同。
属性(Property): 属性是 DOM 元素对象的成员,可以通过 JavaScript 访问和操作。在 HTML 中,属性是元素在 DOM 中对应的 JavaScript 对象的属性。属性通常反映了元素的当前状态或值,可以读取和修改。
例如,考虑以下 HTML 元素:
<input type="text" id="myInput" value="Hello" />
在 JavaScript 中,我们可以通过 id
属性和 value
属性来访问和修改该元素的属性:
const inputElement = document.getElementById('myInput')
console.log(inputElement.value) // 输出 "Hello"
inputElement.value = 'New Value' // 修改 value 属性的值
在这个例子中,value
是元素的属性,可以通过 JavaScript 对象的属性访问和修改。
特性(Attribute): 特性是 HTML 元素在 HTML 标记中定义的附加信息,反映了元素的初始状态或设置。特性在 HTML 中以字符串形式表示,并包含在元素的标记中。特性可以影响元素的行为、样式、标识等。
例如,在以下 HTML 元素中:
<input type="text" id="myInput" value="Hello" />
type
、id
和 value
都是元素的特性。这些特性提供了关于元素的附加信息,如输入框的类型、唯一标识符和初始值。
需要注意的是,特性和属性之间并不总是一一对应。某些特性在 DOM 对象中没有相应的属性,而某些属性是通过特定的 DOM 接口提供的,而不是直接映射到 HTML 的特性。
在一些情况下,特性和属性的值是同步的,当特性值发生变化时,属性值也会相应更新。但是,这种同步并非始终如此,特别是在使用 JavaScript 动态修改属性或特性时,它们之间的同步可能会有所不同。
总结: 在 HTML 中,Property 属性是 DOM 元素对象的属性,可以通过 JavaScript 访问和操作,而特性是元素在 HTML 标记中定义的附加信息,反映了元素的初始状态或设置。属性是 JavaScript 对象的一部分,特性是 HTML 标记的一部分。属性通常用于表示元素的当前状态,而特性用于提供元素的初始设置或附加信息。
请描述一下浏览器渲染过程
简单概述:
- 解析 HTML 生成 DOM 树
- 解析 CSS 生成 CSSOM 树
- 将 DOM 和 CSSOM 合并成渲染树
- 根据渲染树进行布局
- 将各个节点绘制到屏幕上
详细流程:
第一步是渲染主线程过程:
解析 HTML 文档, 解析遇到 HTML 元素会生成 DOM 节点, 遇到 CSS 样式会生成 CSSOM 节点;
CSS 不会阻塞 HTML 的解析, 这是因为 解析 CSS 的工作是在预解析线程中进行的;
JS 会阻塞 HTML 的解析, 因为 JS 可能会修改 DOM 结构;
渲染的下一步是计算样式, 依次对树中的每个节点计算出它的最终样式, 称为"样式计算"(Computed style)。这一步会得到一颗带有样式的 DOM 树;
然后是布局,布局完成后会得到布局树 layout;
然后是分层 layer, 主线程会对整个布局树进行分层,将需要显示的节点分层,比如背景图片这些不需要立即显示的节点会被分层到单独的层中,以提高渲染效率;
最后是绘制 paint, 主线程会遍历布局树,将每个节点绘制出来。完成绘制后,主线程会将每个图层的绘制信息交给合成线程,剩下的工作由合成线程完成。
第二步是合成线程的工作:
- 分块 tiling, 合成线程对每个图层进行分块 tiling,将图层分成很多个小块,每个小块称为一个 tile。这个过程是交由多线程同时进行的;
- 分块完成后,进入光栅化阶段 raster,合成线程将 tile 块信息交由 GPU 进程, 以极高的速度完成光栅化。光栅化的结果就是一块一块的位图。
- 光栅化完成后,是 draw 阶段,合成线程会将每个 tile 的位图信息交给浏览器进程,浏览器进程会将这些位图合并成一张完整的页面。
动画之所以推荐用 transform 来实现,是因为 transform 不会引起重排和重绘。形变发生在合成线程, 与渲染主线程无关, 这就是 transform 效率高的本质原因。
请描述一下回流和重绘
回流 reflow
reflow 的本质是重新计算 layout 树,当元素的几何属性发生变化时,浏览器需要重新计算元素的几何属性,重新计算布局树,会引发 layout。然后再将其绘制出来。
这个过程发生在 渲染主线程, 因此效率不高。
此外,改动属性造成的 reflow 是异步完成的,所以 JS 获取布局属性时,可能无法获取到最新的布局信息。
重绘 repaint
repaint 的本质就是重新根据分层信息,计算了绘制命令。 此步骤还是在渲染主线程中(最后一步)。
因此,回流一定会引起重绘。即样式变动会导致重绘。
为什么 transform 效率高
transform 不会引起重排和重绘。它影响的是渲染流程的最后一个 draw 阶段, 这个阶段在合成线程中, 这个过程是多线程的, 效率极高。
如何减少回流和重绘
- 避免频繁的操作 DOM 元素, 比如使用 documentFragment 一次性添加多个子元素;
- 避免频繁的读取 DOM 元素, 比如使用变量缓存 DOM 元素, 因为 js 读取 DOM 元素的位置和大小, 也会引起回来;
- 尽量使用 css 属性简写, 如使用 boder 代替 border-width, border-style, border-color;
- 批量修改元素样式, 比如使用 class;
- 避免用 table 布局(table 元素, 一旦触发回流就会导致 table 里所有的其它元素回流)。
事件分级 DOM0 和 DOM2 的区别
DOM0 和 DOM2 是关于 JavaScript 事件处理的两种不同模型。
DOM0 事件处理
特点: 所有浏览器都支持; 事件只能注册一次, 后面的会覆盖前面的;
- 在 DOM0 模型中,事件处理程序直接附加到 DOM 元素的属性上,例如 onclick、onmouseover 等。
- 通过直接在元素上设置属性,可以将事件处理函数指定为一个全局或局部的 JavaScript 函数。
DOM2 事件
DOM2 引入了更灵活和强大的事件处理模型,通过 addEventListener(event, function, useCapture) 方法来添加事件监听器。
addEventListener 方法允许同一元素绑定多个事件处理函数,并且不会相互覆盖。
区别
- DOM0 之间会覆盖;
- DOM2 之间不会覆盖;
- DOM0 和 DOM2 直接可以共存, 不会相互覆盖。
addEventListener 的第三个参数有哪些?
el.addEventListener(type, function, useCapture)
- el 事件对象;
- type 事件类型, 如 click, mouseover;
- listener: 事件触发处理函数;
- 第三个参数 useCapture 是一个布尔值, 表示事件是否在捕获阶段触发处理函数。
第三个参数还可以是一个对象:
el.addEventListener(type, function, {
capture: true, // 事件是否在捕获阶段触发处理函数
once: true, // 事件是否只触发一次
passive: true // 表示是否将事件处理程序设置为被动模式。被动模式的事件处理程序永远不会调用 event.preventDefault(),因为浏览器会在执行事件处理程序之前假设它不会调用阻止默认行为的方法,从而优化页面的滚动性能。
})
事件委托
事件委托就是把原本需要绑定在子元素的响应事件委托给父元素, 具体实现过程主要是: 利用事件冒泡过程, 并且利用事件对象 event.target 获取触发事件的元素, 然后根据这个元素来执行相应的操作。
e.target 和 e.currentTarget 的区别:
- e.target 指向触发事件的元素;
- e.currentTarget 指向绑定事件的元素;
<div id="outer">
<div id="inner">Click me!</div>
</div>
document.getElementById('outer').addEventListener('click', function (e) {
console.log(e.target.id) // 输出:inner
console.log(e.currentTarget.id) // 输出:outer
})
在上述例子中, 我们点击子元素, target 指向了子元素, currentTarget 指向了父元素。
获取浏览器内核
console.log('浏览器内核: ', navigator.appCodeName)
大屏自适应
大屏自适应的方式有多种,但一般可采用 rem + media 媒体查询的方式实现:
rem 根据根元素的字体大小为相对单位, 默认是 16px 为 1rem:
html { font-size: 16px; /* 或者使用其他适合你设计的字体大小 */ }
响应式设计和媒体查询, 针对大屏幕的情况,可以使用媒体查询来调整根元素的字体大小,以适应更大的屏幕尺寸。
@media screen and (min-width: 1200px) { html { font-size: 20px; /* 在大屏幕上增大字体大小 */ } }