前端知识总结
# 前端知识总结
# 1.前言
见解有限,如有描述不当之处,请帮忙指出,如有错误,会及时修正。
为什么要梳理这篇文章?
最近恰好被问到这方面的问题,尝试整理后发现,这道题的覆盖面可以非常广,很适合作为一道承载知识体系的题目。
关于这道题目的吐槽暂且不提(这是一道被提到无数次的题,得到不少人的赞同,也被很多人反感),本文的目的是如何借助这道题梳理自己的前端知识体系!
窃认为,每一个前端人员,如果要往更高阶发展,必然会将自己的知识体系梳理一遍,没有牢固的知识体系,无法往更高处走!
**展现形式:**本文并不是将所有的知识点列一遍,而是偏向于分析+梳理
内容:在本文中只会梳理一些比较重要的前端向知识点,其它的可能会被省略
目标:本文的目标是梳理一个较为完整的前端向知识体系
本文是个人阶段性梳理知识体系的成果,然后加以修缮后发布成文章,因此并不确保适用于所有人员,但是,个人认为本文还是有一定参考价值的
另外,如有不同见解,可以一起讨论
———-超长文预警,需要花费大量时间。———-
本文适合有一定经验的前端人员,新手请规避。
本文内容超多,建议先了解主干,然后分成多批次阅读。
本文是前端向,以前端领域的知识为重点
# 2.大纲
- 对知识体系进行一次预评级
- 为什么说知识体系如此重要?
- 梳理主干流程
- 从浏览器接收url到开启网络请求线程
- 多进程的浏览器
- 多线程的浏览器内核
- 解析URL
- 网络请求都是单独的线程
- 更多
- 开启网络线程到发出一个完整的http请求
- DNS查询得到IP
- tcp/ip请求
- 五层因特网协议栈
- 从服务器接收到请求到对应后台接收到请求
- 负载均衡
- 后台的处理
- 后台和前台的http交互
- http报文结构
- cookie以及优化
- gzip压缩
- 长连接与短连接
- http 2.0
- https
- 单独拎出来的缓存问题,http的缓存
- 强缓存与弱缓存
- 缓存头部简述
- 头部的区别
- 解析页面流程
- 流程简述
- HTML解析,构建DOM
- 生成CSS规则
- 构建渲染树
- 渲染
- 简单层与复合层
- Chrome中的调试
- 资源外链的下载
- loaded和domcontentloaded
- CSS的可视化格式模型
- 包含块(Containing Block)
- 控制框(Controlling Box)
- BFC(Block Formatting Context)
- IFC(Inline Formatting Context)
- 其它
- JS引擎解析过程
- JS的解释阶段
- JS的预处理阶段
- JS的执行阶段
- 回收机制
- 其它
- 总结
# 3.对知识体系进行一次预评级
看到这道题目,不借助搜索引擎,自己的心里是否有一个答案?
这里,以目前的经验(了解过一些处于不同阶段的相关前端人员的情况),大概有以下几种情况:(以下都是以点见面,实际上不同阶段人员一般都会有其它的隐藏知识点的)
# level1:
完全没什么概念的,支支吾吾的回答,一般就是这种水平(大致形象点描述):
- 浏览器发起请求,服务端返回数据,然后前端解析成网页,执行脚本。。。
这类人员一般都是:
- 萌新(刚接触前端的,包括0-6个月都有可能有这种回答)
- 沉淀人员(就是那种可能已经接触了前端几年,但是仍然处于初级阶段的那种。。。)
当然了,后者一般还会偶尔提下**http
、后台
、浏览器渲染
,js引擎
**等等关键字,但基本都是一详细的问就不知道了。。。
# level2:
已经有初步概念,但是可能没有完整梳理过,导致无法形成一个完整的体系,或者是很多细节都不会展开,大概是这样子的:(可能符合若干条)
- 知道浏览器输入url后会有http请求这个概念
- 有后台这个概念,大致知道前后端的交互,知道前后端只要靠http报文通信
- 知道浏览器接收到数据后会进行解析,有一定概念,但是具体流程不熟悉(如render树构建流程,layout、paint,复合层与简单层,常用优化方案等不是很熟悉)
- 对于js引擎的解析流程有一定概念,但是细节不熟悉(如具体的形参,函数,变量提升,执行上下文以及VO、AO、作用域链,回收机制等概念不是很熟悉)
- 如可能知道一些http规范初步概念,但是不熟悉(如http报文结构,常用头部,缓存机制,http2.0,https等特性,跨域与web安全等不是很熟悉)
到这里,看到这上面一大堆的概念后,心里应该也会有点底了。。。
实际上,大部分的前端人员可能都处于level2,但是,跳出这个阶段并不容易,一般需要积累,不断学习,才能水到渠成
这类人员一般都是:
- 工作1-3年左右的普通人员(占大多数,而且大多数人员工作3年左右并没有实质上的提升)
- 工作3年以上的老人(这部分人大多都业务十分娴熟,一个当好几个用,但是,基础比较薄弱,可能没有尝试写过框架、组件、脚手架等)
大部分的初中级都陷在这个阶段,如果要突破,不断学习,积累,自然能水到渠成,打通任督二脉
# level3:
基本能到这一步的,不是高阶就是接近高阶,因为很多概念并不是靠背就能理解的,而要理解这么多,需形成体系,一般都需要积累,非一日之功。
一般包括什么样的回答呢?(这里就以自己的简略回答进行举例),一般这个阶段的人员都会符合若干条(不一定全部,当然可能还有些是这里遗漏的):
- 首先略去那些键盘输入、和操作系统交互、以及屏幕显示原理、网卡等硬件交互之类的(前端向中,很多硬件原理暂时略去。。。)
- 对浏览器模型有整体概念,知道浏览器是多进程的,浏览器内核是多线程的,清楚进程与线程之间得区别,以及输入url后会开一个新的网络线程
- 对从开启网络线程到发出一个完整的http请求中间的过程有所了解(如dns查询,tcp/ip链接,五层因特网协议栈等等,以及一些优化方案,如
dns-prefetch
) - 对从服务器接收到请求到对应后台接收到请求有一定了解(如负载均衡,安全拦截以及后台代码处理等)
- 对后台和前台的http交互熟悉(包括http报文结构,场景头部,cookie,跨域,web安全,http缓存,http2.0,https等)
- 对浏览器接收到http数据包后的解析流程熟悉(包括解析html,词法分析然后解析成dom树、解析css生成css规则树、合并成render树,然后layout、painting渲染、里面可能还包括复合图层的合成、GPU绘制、外链处理、加载顺序等)
- 对JS引擎解析过程熟悉(包括JS的解释,预处理,执行上下文,VO,作用域链,this,回收机制等)
可以看到,上述包括了一大堆的概念,仅仅是偏前端向,而且没有详细展开,就已经如此之多的概念了,所以,个人认为如果没有自己的见解,没有形成自己的知识体系,仅仅是看看,背背是没用的,过一段时间就会忘光了。
再说下一般这个阶段的都可能是什么样的人吧。(不一定准确,这里主要是靠少部分现实以及大部分推测得出)
- 工作2年以上的前端(基本上如果按正常进度的话,至少接触前端两年左右才会开始走向高阶,当然,现在很多都是上学时就开始学了的,还有部分是天赋异禀,不好预估。。。)
- 或者是已经十分熟悉其它某门语言,再转前端的人(基本上是很快就可以将前端水准提升上去)
一般符合这个条件的都会有各种隐藏属性(如看过各大框架、组件的源码,写过自己的组件、框架、脚手架,做过大型项目,整理过若干精品博文等)
# level4:
由于本人层次尚未达到,所以大致说下自己的见解吧。
一般这个层次,很多大佬都并不仅仅是某个技术栈了,而是成为了技术专家,技术leader之类的角色。所以仅仅是回答某个技术问题已经无法看出水准了, 可能更多的要看架构,整体把控,大型工程构建能力等等
不过,对于某些执着于技术的大佬,大概会有一些回答吧:(猜的)
- 从键盘谈起到系统交互,从浏览器到CPU,从调度机制到系统内核,从数据请求到二进制、汇编,从GPU绘图到LCD显示,然后再分析系统底层的进程、内存等等
总之,从软件到硬件,到材料,到分子,原子,量子,薛定谔的猫,人类起源,宇宙大爆炸,平行宇宙?感觉都毫无违和感。
# 4.为什么说知识体系如此重要?
为什么说知识体系如此重要呢?这里举几个例子
假设有被问到这样一道题目(随意想到的一个):
- 如何理解
getComputedStyle
在尚未梳理知识体系前,大概会这样回答:
- 普通版本:
getComputedStyle
会获取当前元素所有最终使用的CSS属性值(最终计算后的结果),通过window.getComputedStyle
等价于document.defaultView.getComputedStyle
调用 - 详细版本:
window.getComputedStyle(elem, null).getPropertyValue("height")
可能的值为100px
,而且,就算是css上写的是inherit
,getComputedStyle
也会把它最终计算出来的。不过注意,如果元素的背景色透明,那么getComputedStyle
获取出来的就是透明的这个背景(因为透明本身也是有效的),而不会是父节点的背景。所以它不一定是最终显示的颜色。
就这个API来说,上述的回答已经比较全面了。
但是,其实它是可以继续延伸的。
譬如现在会这样回答:
getComputedStyle
会获取当前元素所有最终使用的CSS属性值,window.
和document.defaultView.
等价…getComputedStyle
会引起回流,因为它需要获取祖先节点的一些信息进行计算(譬如宽高等),所以用的时候慎用,回流会引起性能问题。然后合适的话会将话题引导回流,重绘,浏览器渲染原理等等。当然也可以列举一些其它会引发回流的操作,如offsetXXX
,scrollXXX
,clientXXX
,currentStyle
等等
再举一个例子:
visibility: hidden
和display: none
的区别
可以如下回答:
- 普通回答,一个隐藏,但占据位置,一个隐藏,不占据位置
- 进一步,
display
由于隐藏后不占据位置,所以造成了dom树的改变,会引发回流,代价较大 - 再进一步,当一个页面某个元素经常需要切换
display
时如何优化,一般会用复合层优化,或者要求低一点用absolute
让其脱离普通文档流也行。然后可以将话题引到普通文档流,absolute
文档流,复合图层的区别, - 再进一步可以描述下浏览器渲染原理以及复合图层和普通图层的绘制区别(复合图层单独分配资源,独立绘制,性能提升,但是不能过多,还有隐式合成等等)
上面这些大概就是知识系统化后的回答,会更全面,容易由浅入深,而且一有机会就可以往更底层挖
# 前端向知识的重点
此部分的内容是站在个人视角分析的,并不是说就一定是正确答案
首先明确,计算机方面的知识是可以无穷无尽的挖的,而本文的重点是梳理前端向的重点知识
对于前端向(这里可能没有提到node.js
之类的,更多的是指客户端前端),这里将知识点按重要程度划分成以下几大类:
- 核心知识,必须掌握的,也是最基础的,譬如浏览器模型,渲染原理,JS解析过程,JS运行机制等,作为骨架来承载知识体系
- 重点知识,往往每一块都是一个知识点,而且这些知识点都很重要,譬如http相关,web安全相关,跨域处理等
- 拓展知识,这一块可能更多的是了解,稍微实践过,但是认识上可能没有上面那么深刻,譬如五层因特网协议栈,hybrid模式,移动原生开发,后台相关等等(当然,在不同领域,可能有某些知识就上升到重点知识层次了,譬如hybrid开发时,懂原生开发是很重要的)
为什么要按上面这种方式划分?
这大概与个人的技术成长有关。
记得最开始学前端知识时,是一点一点的积累,一个知识点一个知识点的攻克。
就这样,虽然在很长一段时间内积累了不少的知识,但是,总是无法将它串联到一起。每次梳理时都是很分散的,无法保持思路连贯性。
直到后来,在将浏览器渲染原理、JS运行机制、JS引擎解析流程梳理一遍后,感觉就跟打通了任督二脉一样,有了一个整体的架构,以前的知识点都连贯起来了。
梳理出了一个知识体系,以后就算再学新的知识,也会尽量往这个体系上靠拢,环环相扣,更容易理解,也更不容易遗忘
# 5.梳理主干流程
回到这道题上,如何回答呢?先梳理一个骨架
知识体系中,最重要的是骨架,脉络。有了骨架后,才方便填充细节。所以,先梳理下主干流程:
1. 从浏览器接收url到开启网络请求线程(这一部分可以展开浏览器的机制以及进程与线程之间的关系)
2. 开启网络线程到发出一个完整的http请求(这一部分涉及到dns查询,tcp/ip请求,五层因特网协议栈等知识)
3. 从服务器接收到请求到对应后台接收到请求(这一部分可能涉及到负载均衡,安全拦截以及后台内部的处理等等)
4. 后台和前台的http交互(这一部分包括http头部、响应码、报文结构、cookie等知识,可以提下静态资源的cookie优化,以及编码解码,如gzip压缩等)
5. 单独拎出来的缓存问题,http的缓存(这部分包括http缓存头部,etag,catch-control等)
6. 浏览器接收到http数据包后的解析流程(解析html-词法分析然后解析成dom树、解析css生成css规则树、合并成render树,然后layout、painting渲染、复合图层的合成、GPU绘制、外链资源的处理、loaded和domcontentloaded等)
7. CSS的可视化格式模型(元素的渲染规则,如包含块,控制框,BFC,IFC等概念)
8. JS引擎解析过程(JS的解释阶段,预处理阶段,执行阶段生成执行上下文,VO,作用域链、回收机制等等)
9. 其它(可以拓展不同的知识模块,如跨域,web安全,hybrid模式等等内容)
梳理出主干骨架,然后就需要往骨架上填充细节内容
# 6.从浏览器接收url到开启网络请求线程
这一部分展开的内容是:浏览器进程/线程模型,JS的运行机制
# 多进程的浏览器
浏览器是多进程的,有一个主控进程,以及每一个tab页面都会新开一个进程(某些情况下多个tab会合并进程)
进程可能包括主控进程,插件进程,GPU,tab页(浏览器内核)等等
- Browser进程:浏览器的主进程(负责协调、主控),只有一个
- 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
- GPU进程:最多一个,用于3D绘制
- 浏览器渲染进程(内核):默认每个Tab页面一个进程,互不影响,控制页面渲染,脚本执行,事件处理等(有时候会优化,如多个空白tab会合并成一个进程)
如下图:
# 7.多线程的浏览器内核
每一个tab页面可以看作是浏览器内核进程,然后这个进程是多线程的,它有几大类子线程
- GUI线程
- JS引擎线程
- 事件触发线程
- 定时器线程
- 网络请求线程
可以看到,里面的JS引擎是内核进程中的一个线程,这也是为什么常说JS引擎是单线程的
# 解析URL
输入URL后,会进行解析(URL的本质就是统一资源定位符)
URL一般包括几大部分:
protocol
,协议头,譬如有http,ftp等host
,主机域名或IP地址port
,端口号path
,目录路径query
,即查询参数fragment
,即#
后的hash值,一般用来定位到某个位置
# 网络请求都是单独的线程
每次网络请求时都需要开辟单独的线程进行,譬如如果URL解析到http协议,就会新建一个网络线程去处理资源下载
因此浏览器会根据解析出得协议,开辟一个网络线程,前往请求资源(这里,暂时理解为是浏览器内核开辟的,如有错误,后续修复)
# 8.解析页面流程
前面有提到http交互,那么接下来就是浏览器获取到html,然后解析,渲染
这部分很多都参考了网上资源,特别是图片,参考了来源中的文章
# 流程简述
浏览器内核拿到内容后,渲染步骤大致可以分为以下几步:
1. 解析HTML,构建DOM树
2. 解析CSS,生成CSS规则树
3. 合并DOM树和CSS规则,生成render树
4. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
5. 绘制render树(paint),绘制页面像素信息
6. 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上
如下图:
# HTML解析,构建DOM
整个渲染步骤中,HTML解析是第一步。
简单的理解,这一步的流程是这样的:浏览器解析HTML,构建DOM树。
但实际上,在分析整体构建时,却不能一笔带过,得稍微展开。
解析HTML到构建出DOM当然过程可以简述如下:
Bytes → characters → tokens → nodes → DOM
譬如假设有这样一个HTML页面:(以下部分的内容出自参考来源,修改了下格式)
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
浏览器的处理如下:
列举其中的一些重点过程:
1. Conversion转换:浏览器将获得的HTML内容(Bytes)基于他的编码转换为单个字符
2. Tokenizing分词:浏览器按照HTML规范标准将这些字符转换为不同的标记token。每个token都有自己独特的含义以及规则集
3. Lexing词法分析:分词的结果是得到一堆的token,此时把他们转换为对象,这些对象分别定义他们的属性和规则
4. DOM构建:因为HTML标记定义的就是不同标签之间的关系,这个关系就像是一个树形结构一样
例如:body对象的父节点就是HTML对象,然后段略p对象的父节点就是body对象
最后的DOM树如下:
# 生成CSS规则
同理,CSS规则树的生成也是类似。简述为:
Bytes → characters → tokens → nodes → CSSOM
譬如style.css
内容如下:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
那么最终的CSSOM树就是:
# 构建渲染树
当DOM树和CSSOM都有了后,就要开始构建渲染树了
一般来说,渲染树和DOM树相对应的,但不是严格意义上的一一对应
因为有一些不可见的DOM元素不会插入到渲染树中,如head这种不可见的标签或者display: none
等
整体来说可以看图:
# 渲染
有了render树,接下来就是开始渲染,基本流程如下:
图中重要的四个步骤就是:
1. 计算CSS样式
2. 构建渲染树
3. 布局,主要定位坐标和大小,是否换行,各种position overflow z-index属性
4. 绘制,将图像绘制出来
然后,图中的线与箭头代表通过js动态修改了DOM或CSS,导致了重新布局(Layout)或渲染(Repaint)
这里Layout和Repaint的概念是有区别的:
- Layout,也称为Reflow,即回流。一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树
- Repaint,即重绘。意味着元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就可以了
回流的成本开销要高于重绘,而且一个节点的回流往往回导致子节点以及同级节点的回流, 所以优化方案中一般都包括,尽量避免回流。
什么会引起回流?
1.页面渲染初始化
2.DOM结构改变,比如删除了某个节点
3.render树变化,比如减少了padding
4.窗口resize
5.最复杂的一种:获取某些属性,引发回流,
很多浏览器会对回流做优化,会等到数量足够时做一次批处理回流,
但是除了render树的直接变化,当获取一些属性时,浏览器为了获得正确的值也会触发回流,这样使得浏览器优化无效,包括
(1)offset(Top/Left/Width/Height)
(2) scroll(Top/Left/Width/Height)
(3) cilent(Top/Left/Width/Height)
(4) width,height
(5) 调用了getComputedStyle()或者IE的currentStyle
回流一定伴随着重绘,重绘却可以单独出现
所以一般会有一些优化方案,如:
- 减少逐项更改样式,最好一次性更改style,或者将样式定义为class并一次性更新
- 避免循环操作dom,创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window.document
- 避免多次读取offset等属性。无法避免则将它们缓存到变量
- 将复杂的元素绝对定位或固定定位,使得它脱离文档流,否则回流代价会很高
注意:改变字体大小会引发回流
再来看一个示例:
var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
// 添加node,再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));
# 9简单层与复合层
上述中的渲染中止步于绘制,但实际上绘制这一步也没有这么简单,它可以结合复合层和简单层的概念来讲。
这里不展开,进简单介绍下:
- 可以认为默认只有一个复合图层,所有的DOM节点都是在这个复合图层下的
- 如果开启了硬件加速功能,可以将某个节点变成复合图层
- 复合图层之间的绘制互不干扰,由GPU直接控制
- 而简单图层中,就算是absolute等布局,变化时不影响整体的回流,但是由于在同一个图层中,仍然是会影响绘制的,因此做动画时性能仍然很低。而复合层是独立的,所以一般做动画推荐使用硬件加速
更多参考:
# Chrome中的调试
Chrome的开发者工具中,Performance中可以看到详细的渲染过程:
# 资源外链的下载
上面介绍了html解析,渲染流程。但实际上,在解析html时,会遇到一些资源连接,此时就需要进行单独处理了
简单起见,这里将遇到的静态资源分为一下几大类(未列举所有):
- CSS样式资源
- JS脚本资源
- img图片类资源
遇到外链时的处理
当遇到上述的外链时,会单独开启一个下载线程去下载资源(http1.1中是每一个资源的下载都要开启一个http请求,对应一个tcp/ip链接)
遇到CSS样式资源
CSS资源的处理有几个特点:
- CSS下载时异步,不会阻塞浏览器构建DOM树
- 但是会阻塞渲染,也就是在构建render时,会等到css下载解析完毕后才进行(这点与浏览器优化有关,防止css规则不断改变,避免了重复的构建)
- 有例外,
media query
声明的CSS是不会阻塞渲染的
遇到JS脚本资源
JS脚本资源的处理有几个特点:
- 阻塞浏览器的解析,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析HTML
- 浏览器的优化,一般现代浏览器有优化,在脚本阻塞时,也会继续下载其它资源(当然有并发上限),但是虽然脚本可以并行下载,解析过程仍然是阻塞的,也就是说必须这个脚本执行完毕后才会接下来的解析,并行下载只是一种优化而已
- defer与async,普通的脚本是会阻塞浏览器解析的,但是可以加上defer或async属性,这样脚本就变成异步了,可以等到解析完毕后再执行
注意,defer和async是有区别的: defer是延迟执行,而async是异步执行。
简单的说(不展开):
async
是异步执行,异步下载完毕后就会执行,不确保执行顺序,一定在onload
前,但不确定在DOMContentLoaded
事件的前或后defer
是延迟执行,在浏览器看起来的效果像是将脚本放在了body
后面一样(虽然按规范应该是在DOMContentLoaded
事件前,但实际上不同浏览器的优化效果不一样,也有可能在它后面)
遇到img图片类资源
遇到图片等资源时,直接就是异步下载,不会阻塞解析,下载完毕后直接用图片替换原有src的地方
# loaded和domcontentloaded
简单的对比:
- DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片(譬如如果有async加载的脚本就不一定完成)
- load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了
# 10.CSS的可视化格式模型
这一部分内容很多参考《精通CSS-高级Web标准解决方案》以及参考来源
前面提到了整体的渲染概念,但实际上文档树中的元素是按什么渲染规则渲染的,是可以进一步展开的,此部分内容即: CSS的可视化格式模型
先了解:
- CSS中规定每一个元素都有自己的盒子模型(相当于规定了这个元素如何显示)
- 然后可视化格式模型则是把这些盒子按照规则摆放到页面上,也就是如何布局
- 换句话说,盒子模型规定了怎么在页面里摆放盒子,盒子的相互作用等等
说到底: CSS的可视化格式模型就是规定了浏览器在页面中如何处理文档树
关键字:
包含块(Containing Block)
控制框(Controlling Box)
BFC(Block Formatting Context)
IFC(Inline Formatting Context)
定位体系
浮动
...
另外,CSS有三种定位机制:普通流
,浮动
,绝对定位
,如无特别提及,下文中都是针对普通流中的
# 包含块(Containing Block)
一个元素的box的定位和尺寸,会与某一矩形框有关,这个框就称之为包含块。
元素会为它的子孙元素创建包含块,但是,并不是说元素的包含块就是它的父元素,元素的包含块与它的祖先元素的样式等有关系
譬如:
- 根元素是最顶端的元素,它没有父节点,它的包含块就是初始包含块
- static和relative的包含块由它最近的块级、单元格或者行内块祖先元素的内容框(content)创建
- fixed的包含块是当前可视窗口
- absolute的包含块由它最近的position 属性为
absolute
、relative
或者fixed
的祖先元素创建- 如果其祖先元素是行内元素,则包含块取决于其祖先元素的
direction
特性 - 如果祖先元素不是行内元素,那么包含块的区域应该是祖先元素的内边距边界
- 如果其祖先元素是行内元素,则包含块取决于其祖先元素的
# 控制框(Controlling Box)
块级元素和块框以及行内元素和行框的相关概念
块框:
- 块级元素会生成一个块框(
Block Box
),块框会占据一整行,用来包含子box和生成的内容 - 块框同时也是一个块包含框(
Containing Box
),里面要么只包含块框,要么只包含行内框(不能混杂),如果块框内部有块级元素也有行内元素,那么行内元素会被匿名块框包围
关于匿名块框的生成,示例:
<div>
Some text
<p>More text</p>
</div>
div
生成了一个块框,包含了另一个块框p
以及文本内容Some text
,此时Some text
文本会被强制加到一个匿名的块框里面,被div
生成的块框包含(其实这个就是IFC
中提到的行框,包含这些行内框的这一行匿名块形成的框,行框和行内框不同)
换句话说:
如果一个块框在其中包含另外一个块框,那么我们强迫它只能包含块框,因此其它文本内容生成出来的都是匿名块框(而不是匿名行内框)
行内框:
- 一个行内元素生成一个行内框
- 行内元素能排在一行,允许左右有其它元素
关于匿名行内框的生成,示例:
<P>Some <em>emphasized</em> text</P>
P
元素生成一个块框,其中有几个行内框(如em
),以及文本Some
,text
,此时会专门为这些文本生成匿名行内框
display属性的影响
display
的几个属性也可以影响不同框的生成:
block
,元素生成一个块框inline
,元素产生一个或多个的行内框inline-block
,元素产生一个行内级块框,行内块框的内部会被当作块块来格式化,而此元素本身会被当作行内级框来格式化(这也是为什么会产生BFC
)none
,不生成框,不再格式化结构中,当然了,另一个visibility: hidden
则会产生一个不可见的框
总结:
- 如果一个框里,有一个块级元素,那么这个框里的内容都会被当作块框来进行格式化,因为只要出现了块级元素,就会将里面的内容分块几块,每一块独占一行(出现行内可以用匿名块框解决)
- 如果一个框里,没有任何块级元素,那么这个框里的内容会被当成行内框来格式化,因为里面的内容是按照顺序成行的排列
# BFC(Block Formatting Context)
FC(格式上下文)?
FC即格式上下文,它定义框内部的元素渲染规则,比较抽象,譬如
FC像是一个大箱子,里面装有很多元素
箱子可以隔开里面的元素和外面的元素(所以外部并不会影响FC内部的渲染)
内部的规则可以是:如何定位,宽高计算,margin折叠等等
不同类型的框参与的FC类型不同,譬如块级框对应BFC,行内框对应IFC
注意,并不是说所有的框都会产生FC,而是符合特定条件才会产生,只有产生了对应的FC后才会应用对应渲染规则
BFC规则:
在块格式化上下文中
每一个元素左外边与包含块的左边相接触(对于从右到左的格式化,右外边接触右边)
即使存在浮动也是如此(所以浮动元素正常会直接贴近它的包含块的左边,与普通元素重合)
除非这个元素也创建了一个新的BFC
总结几点BFC特点:
- 内部
box
在垂直方向,一个接一个的放置 - box的垂直方向由
margin
决定,属于同一个BFC的两个box间的margin会重叠 - BFC区域不会与
float box
重叠(可用于排版) - BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此
- 计算BFC的高度时,浮动元素也参与计算(不会浮动坍塌)
如何触发BFC?
- 根元素
float
属性不为none
position
为absolute
或fixed
display
为inline-block
,flex
,inline-flex
,table
,table-cell
,table-caption
overflow
不为visible
这里提下,display: table
,它本身不产生BFC,但是它会产生匿名框(包含display: table-cell
的框),而这个匿名框产生BFC
更多请自行网上搜索
# IFC(Inline Formatting Context)
IFC即行内框产生的格式上下文
IFC规则
在行内格式化上下文中
框一个接一个地水平排列,起点是包含块的顶部。
水平方向上的 margin,border 和 padding 在框之间得到保留
框在垂直方向上可以以不同的方式对齐:它们的顶部或底部对齐,或根据其中文字的基线对齐
行框
包含那些框的长方形区域,会形成一行,叫做行框
行框的宽度由它的包含块和其中的浮动元素决定,高度的确定由行高度计算规则决定
行框的规则:
如果几个行内框在水平方向无法放入一个行框内,它们可以分配在两个或多个垂直堆叠的行框中(即行内框的分割)
行框在堆叠时没有垂直方向上的分割且永不重叠
行框的高度总是足够容纳所包含的所有框。不过,它可能高于它包含的最高的框(例如,框对齐会引起基线对齐)
行框的左边接触到其包含块的左边,右边接触到其包含块的右边。
结合补充下IFC规则:
浮动元素可能会处于包含块边缘和行框边缘之间
尽管在相同的行内格式化上下文中的行框通常拥有相同的宽度(包含块的宽度),它们可能会因浮动元素缩短了可用宽度,而在宽度上发生变化
同一行内格式化上下文中的行框通常高度不一样(如,一行包含了一个高的图形,而其它行只包含文本)
当一行中行内框宽度的总和小于包含它们的行框的宽,它们在水平方向上的对齐,取决于 `text-align` 特性
空的行内框应该被忽略
即不包含文本,保留空白符,margin/padding/border非0的行内元素,
以及其他常规流中的内容(比如,图片,inline blocks 和 inline tables),
并且不是以换行结束的行框,
必须被当作零高度行框对待
总结:
- 行内元素总是会应用IFC渲染规则
- 行内元素会应用IFC规则渲染,譬如
text-align
可以用来居中等 - 块框内部,对于文本这类的匿名元素,会产生匿名行框包围,而行框内部就应用IFC渲染规则
- 行内框内部,对于那些行内元素,一样应用IFC渲染规则
- 另外,
inline-block
,会在元素外层产生IFC(所以这个元素是可以通过text-align
水平居中的),当然,它内部则按照BFC规则渲染
相比BFC规则来说,IFC可能更加抽象(因为没有那么条理清晰的规则和触发条件)
但总的来说,它就是行内元素自身如何显示以及在框内如何摆放的渲染规则,这样描述应该更容易理解
# 其它
当然还有有一些其它内容:
- 譬如常规流,浮动,绝对定位等区别
- 譬如浮动元素不包含在常规流中
- 譬如相对定位,绝对定位,
Fixed
定位等区别 - 譬如
z-index
的分层显示机制等
# 11.总结
上述这么多内容,目的是:梳理出自己的知识体系
本文由于是前端向,所以知识梳理时有重点,很多其它的知识点都简述或略去了,重点介绍的模块总结:
- 浏览器的进程/线程模型、JS运行机制(这一块的详细介绍链接到了另一篇文章)
- http规范(包括报文结构,头部,优化,http2.0,https等)
- http缓存(单独列出来,因为它很重要)
- 页面解析流程(HTML解析,构建DOM,生成CSS规则,构建渲染树,渲染流程,复合层的合成,外链的处理等)
- JS引擎解析过程(包括解释阶段,预处理阶段,执行阶段,包括执行上下文、VO、作用域链、this、回收机制等)
- 跨域相关,web安全单独链接到了具体文章,其它如CSS盒模型,viewport等仅是提及概念
关于本文的价值?
本文是个人阶段性梳理知识体系的成果,然后加以修缮后发布成文章,因此并不确保适用于所有人员
但是,个人认为本文还是有一定参考价值的
# 12.写在最后的话
还是那句话:知识要形成体系
梳理出知识体系后,有了一个骨架,知识点不易遗忘,而且学习新知识时也会更加迅速,更重要的是容易举一反三,可以由一个普通的问题,深挖拓展到底层原理
前端知识是无穷无尽的,本文也仅仅是简单梳理出一个承载知识体系的骨架而已,更多的内容仍然需要不断学习,积累