libin1991 / libin_Blog

爬虫-博客大全
https://libin.netlify.com/
124 stars 17 forks source link

【笔试】2018面试笔试 #458

Open libin1991 opened 6 years ago

libin1991 commented 6 years ago

防止网页被嵌入框架的代码

任何页面都不可嵌套

1
2
3
4
// 判断当前的window对象是否对顶层top对象还可以使用window.top !== window.self
if (window !== top)
// 如果不是,将top对象的网址自动导向被嵌入网页的网址
top.location.href = window.location.href;

本地域名可嵌套,其他域名不可

1
2
3
4
5
6
7
8
try{
  top.location.hostname;
  if (top.location.hostname !== window.location.hostname) {
    top.location.href = window.location.href;
  }
}catch(e){
  top.location.href = window.location.href;
}

PS:上面两种对于动态生产iframe标签和禁用js不会用效果。
别人可能这样禁用你的js
<noscript><iframe src=fillseo.html></iframe></noscript>

js如何判断是否在iframe中

1
2
3
4
5
6
7
8
9
10
11
12
//方式一
if (self.frameElement && self.frameElement.tagName == "IFRAME") {
alert('在iframe中');
}
//方式二
if (window.frames.length != parent.frames.length) {
alert('在iframe中');
}
//方式三
if (self != top) {
alert('在iframe中');
}

比较可靠的方式

为了彻底防止别人用IFRAME框架嵌套调用自己的网页,如下方法是最可靠的.
这里赋值为空页面,也可赋值为你的页面的URL地址.

1
2
3
if(top != self){
location.href = "about:blank";
}

在meta中设置

<meta http-equiv="X-FRAME-OPTIONS" content="DENY">

在http的header做手脚

1
2
header(‘X-Frame-Options:Deny');
header("X-XSS-Protection: 0");

在Apache、IIS、Nginc主机中设置

X-Frame-Options "SAMEORIGIN";

BFC

BFC的定义

块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视化CSS渲染的一部分,是布局过程中生成块级盒子的区域(可以理解为独立的布局作用域),也是浮动元素与其他元素的交互限定区域。
具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。

BFC的触发

body 根元素或包含根元素的元素
浮动元素:float 除 none 以外的值
绝对定位元素:position (absolute、fixed)
display 为 inline-block、table-cells、flex
overflow 除了 visible 以外的值 (hidden、auto、scroll)

BFC的运用

同一个 BFC 下外边距会发生折叠
BFC 可以包含浮动的元素(清除浮动)(父容器设置为overflow: hidden;即可清除浮动)
BFC 可以阻止元素被浮动元素覆盖(为原本被覆盖的元素设置overflow: hidden;是该元素触发BFC解决问题)
10 分钟理解 BFC 原理

Banner如何平滑的从最后一张过渡到第一张

1.如有1,2,3,4张banner需要轮播,对应图片顺序为:4,1,2,3,4,1
2.动画结束后改变left定位到前面的1,这个过程页面看不出变化。
图片轮播,第一张图片和最后一张图片怎么过渡?

Event Loop(浏览器环境)

定义:为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理(user agent)必须使用事件循环(event loops)。即事件循环,是JavaScript引擎处理异步任务的方式。为了让单线程的JavaScript通畅的跑起来,所有的异步操作都要被合适的处理,这个处理逻辑就叫做Event Loop。
HTML标准-Event loops

堆、栈、队列

堆(heap)

堆(heap)是指程序运行时申请的动态内存,在JS运行时用来存放对象。

栈(stack)

栈(stack)遵循的原则是“先进后出”,JS种的基本数据类型与指向对象的地址存放在栈内存中,此外还有一块栈内存用来执行JS主线程–执行栈(execution context stack),这里只考虑执行栈。

队列(queue)

队列(queue)遵循的原则是“先进先出”,JS中除了主线程之外还存在两个“任务队列”(微任务队列microTask和宏任务队列macroTask)。

js中的两个队列

在JavaScript中,任务被分为Task(又称为MacroTask,宏任务)和MicroTask(微任务)两种。
MicroTask: process.nextTick(node独有), Promises, Object.observe(废弃), MutationObserver
MacroTask: script(整体代码), setTimeout, setInterval, setImmediate(node独有), I/O, UI rendering
javascript执行:总的执行顺序为同步代码—>microTask—>macroTask,在执行microTask、macroTask是产生新的异步操作,如此一来就形成了循环。
具体来说,浏览器会不断从task队列中按顺序取task执行,每执行完一个task都会检查microtask队列是否为空(执行完一个task的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去task队列中取下一个task执行,以此类推。

Philip Roberts的演讲《Help, I’m stuck in an event-loop》的event-loop 图

Philip Roberts的演讲《Help, I'm stuck in an event-loop》

解释

当主线程运行的时候,JS会产生堆和栈(执行栈)
主线程中调用的webaip所产生的异步操作(dom事件、ajax回调、定时器等)只要产生结果,就把这个回调塞进“任务队列”中等待执行。
当主线程中的同步任务执行完毕,系统就会依次读取“任务队列”中的任务,将任务放进执行栈中执行。
执行任务时可能还会产生新的异步操作,会产生新的循环,整个过程是循环不断的。

JavaScript 运行机制–Event Loop详解
一篇文章教会你Event loop——浏览器和Node

Event Loop(Node环境)

Node使用了libuv库来实现Event loop。

Event Loop顺序

nodejs的event loop分为6个阶段,它们会按照顺序反复运行,分别如下:

  1. timers:执行setTimeout() 和 setInterval()中到期的callback。
  2. I/O callbacks:上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行
  3. idle, prepare:队列的移动,仅内部使用
  4. poll:最为重要的阶段,执行I/O callback,在适当的条件下会阻塞在这个阶段
  5. check:执行setImmediate的callback
  6. close callbacks:执行close事件的callback,例如socket.on(“close”,func)
    Node-Event Loop
    不同于浏览器的是,在每个阶段完成后,而不是MacroTask任务完成后,microTask队列就会被执行。这就导致了同样的代码在不同的上下文环境下会出现不同的结果。
    另外需要注意的是,如果在timers阶段执行时创建了setImmediate则会在此轮循环的check阶段执行,如果在timers阶段创建了setTimeout,由于timers已取出完毕,则会进入下轮循环,check阶段创建timers任务同理。
    一篇文章教会你Event loop——浏览器和Node

RegExp相关知识点

RegExp 构造函数创建了一个正则表达式对象,用于将文本与一个模式匹配[2018-03-27]。

正则flags的说明

g–全局匹配;找到所有匹配,而不是在第一个匹配后停止
i–忽略大小写
m–多行; 将开始和结束字符(^和$)视为在多行上工作(也就是,分别匹配每一行的开始和结束(由 \n 或 \r 分割),而不只是只匹配整个输入字符串的最开始和最末尾处
u–Unicode; 将模式视为Unicode序列点的序列
y–粘性匹配; 仅匹配目标字符串中此正则表达式的lastIndex属性指示的索引(并且不尝试从任何后续的索引匹配)

正则表达式中特殊字符的含义

字符类别(Character Classes)

.

点号,小数点,匹配任意单个字符(但不包括行结束符\n \r等)。
例如,/.y/ 匹配 “yes make my day” 中的 “my” 和 “ay”,但是不匹配 “yes”。

\d

匹配任意阿拉伯数字。等价于[0-9]。
例如,/\d/ 或 /[0-9]/ 匹配 “B2 is the suite number.” 中的 ‘2’。

\D

匹配任意一个不是阿拉伯数字的字符。等价于[^0-9]。
例如,/\D/ 或 /[^0-9]/ 匹配 “B2 is the suite number.” 中的 ‘B’。
PS:由于没有全局匹配,只能匹配到一个’B’就结束匹配。

\w

匹配任意来自基本拉丁字母表中的字母数字字符,还包括下划线。等价于 [A-Za-z0-9_]。
例如,/\w/ 匹配 “apple” 中的 ‘a’,”$5.28” 中的 ‘5’ 和 “3D” 中的 ‘3’。

\W

匹配任意不是基本拉丁字母表中单词(字母数字下划线)字符的字符。等价于 [^A-Za-z0-9]。
例如,/\W/ 或 /[^A-Za-z0-9
]/ 匹配 “50%” 中的 ‘%’。

\s

匹配一个空白符,包括空格、制表符、换页符、换行符和其他 Unicode 空格。
例如 /\s\w*/ 匹配 “foo bar” 中的 ‘ bar’。

\S

匹配一个非空白符。
例如,/\S\w*/ 匹配 “foo bar” 中的 ‘foo’。
PS:经常使用[\s\S]来匹配所有字符。

其他

\t 匹配一个水平制表符(tab)
\r 匹配一个回车符(carriage return)
\n 匹配一个换行符(linefeed)
\v 匹配一个垂直制表符(vertical tab)
\f 匹配一个换页符(form-feed)
[\b] 匹配一个退格符(backspace)(不要与 \b 混淆)
\0 匹配一个 NUL 字符。不要在此后面跟小数点。
\xhh 匹配编码为 hh (两个十六进制数字)的字符。
\uhhhh 匹配 Unicode 值为 hhhh (四个十六进制数字)的字符。
\ 发生转义
例如, 是一个特殊字符,表示匹配某个字符 0 或多次,如 /a/ 意味着 0 或多个 “a”。 为了匹配字面意义上的 ,在它前面加上一个反斜杠,例如,/a\/匹配 ‘a*’。

字符集合(Character Sets)

[xyz]

匹配集合中的任意一个字符。你可以使用连字符’-‘指定一个范围。
例如,[abcd] 等价于 [a-d],匹配”brisket”中的’b’和”chop”中的’c’。

[^xyz]

一个反义或补充字符集,也叫反义字符组。
例如,[^abc] 等价于 [^a-c]。 第一个匹配的是 “bacon” 中的’o’ 和 “chop” 中的 ‘h’。

边界(Boundaries)

^

匹配输入开始。如果多行(multiline)标志被设为 true,该字符也会匹配一个断行(line break)符后的开始处。
例如,/^A/ 不匹配 “an A” 中的 “A”,但匹配 “An A” 中的 “A”。

$

匹配输入结尾。如果多行(multiline)标志被设为 true,该字符也会匹配一个断行(line break)符的前的结尾处。
例如,/t$/ 不匹配 “eater” 中的 “t”,但匹配 “eat” 中的 “t”。

\b

如果符合要求就一直往后匹配,一直到无法匹配为止,这就是贪婪模式。所谓的惰性模式就是一旦匹配到合适的就结束,不在继续匹配下去了。

分组(Grouping)与反向引用(back references)

先行断言(lookahead)

先行肯定断言

x(?=y)
只有当 x 后面紧跟着 y 时,才匹配 x。
/Jack(?=Sprat|Frost)/ 只有在 ‘Jack’ 后面紧跟着 ‘Sprat’ 或 ‘Frost’ 时,才会匹配它。然而,’Sprat’ 或 ‘Frost’ 都不是匹配结果的一部分。

先行否定断言

x(?!y)
只有当 x 后面不是紧跟着 y 时,才匹配 x。
只有当 x 后面不是紧跟着 y 时,才匹配 x。例如,/\d+(?!.)/ 只有当一个数字后面没有紧跟着一个小数点时,才会匹配该数字。/\d+(?!.)/.exec(“3.141”) 匹配 141 而不是 3.141。

后行断言(lookbehind)

这是ES2018新加的标准

后行肯定断言

(?<=y)x
只有当 x 前面紧跟 y 时,才匹配 x。
例如:/(?<=\$)\d+/.exec(‘$1000’) // 1000

后行否定断言

(?<!y)x
只有当 x 前面紧不是跟 y 时,才匹配 x。
例如:/(?<!\$)\d+/.exec(‘$99¥1000’) // 99

libin1991 commented 6 years ago

(点击上方公众号,可快速关注)

本文来自(作者蔡剑涛)的投稿

https://mp.weixin.qq.com/s/-LcNZWFFty2lWuND6uuNNA

1. **前言**

前端圈有个“梗”:在面试时,问个css的position属性能刷掉一半人,其中不乏工作四五年的同学。在公司一直有参与前端的基础面试,深感这个“梗”不是个玩笑。

然而,我觉得实际比例可能会更高,甚至很多面试官自己也未必真正掌握。因为大部分前端同学,可能不知道初始包含块的概念,或知道但对这个概念理解有误。

造成这种现象的原因主要有两方面,一方面是在介绍这个知识点时,网上有谬误的文章太多,国内外亦如此(MDN也名列其中),导致很多同学被误导(我一开始也是),而且这种错误被代代相传;另一方面可能是我们平时不太注重概念的定义、自身对待知识的态度还不够严谨、缺乏验证精神和系统总结的习惯。

一次偶然的机会,我发现了这种谬误,并找到了W3C组织对初始化包含块的官方定义,也为了让刚入前端圈的同学少走一些弯路,在此我想借本文分享给大家(详述请见5.5. 包含块章节),也系统分享一下,本人在前端布局基础方面积累的浅薄经验。(因为是系统概述,所以篇幅会比较长,希望各位读者有心理准备)

2. 什么是前端布局基础?

前端布局方案主要有三种:

这些方案都能够解决布局问题,而且每个方案都有各自的理论基础,那么哪一个方案的基础理论可以称得上是前端布局基础?要回答这个问题,我们还得深入去了解这三种方案的特性。

传统布局方案,需要使用者熟练掌握元素的分类及布局特性、浮动原理和定位原理等众多基础知识,方能在解决各类前端布局问题时游刃有余,这不仅学习成本大,而且实现的复杂度也高,实现的CSS代码也不够精简、优雅。但由于其基础知识来源于CSS2,所以浏览器兼容性最好,对于用户是友好的。

flex布局方案,正是为了解决传统布局方案的种种不便,而提出的一种新型改进方案,它不再需要借助浮动和定位等布局手段,而是通过父元素(flex box)单方面配置相关的CSS属性来决定子元素的布局规则,且在大多情况下无需子元素(flex item)参与,就能完成子元素间的布局问题,不仅学习成本低(公司之前有几个后端工程师亦能快速上手),且大大简化了布局的实现复杂度,CSS代码也更加精炼。美中不足的是IE10才开始支持,且需要使用-ms-前缀(IE11无需)。

虽然现今的手机多使用的是现代浏览器,对flex支持度较好,然而并不是每一款手机都如此:笔者曾在一个移动端项目采用过flex布局方案,然而公司的测试同学在“华为荣耀5”的自带浏览器,检测到无法支持flex布局,我们能够跟测试的同学说,是这款华为手机的浏览器有问题吗?显然不能。于是故笔者在项目早期就及时放弃了flex布局方案,改用传统布局方案实现,避免了后面大规模的改动。

grid布局方案,是由微软提出,相对于传统布局方案和flex布局方案,它是一种二维布局方案,在IE10开始支持,但需要使用-ms-后缀(IE11+不再需要)。

总的来说,这三类方案都能基本解决日常的前端布局问题,且从易用性、灵活性和强大性来说,flex布局和grid布局更是未来的趋势。但是从当前各版本浏览器在用户市场上的使用情况和各方案的浏览器兼容性来看,传统布局方案对用户最友好,具有一定的不可替代性,所以我觉得,传统布局方案是最应该先掌握好的,尤其是对于在to B企业工作的前端同学来说。

所以本文将详细介绍的“前端布局基础”,指的是围绕着“传统布局方案”的众多CSS知识,其主要内容来源于CSS2规范。

3. 为什么要学好前端布局基础?

页面写多了的前端同学,我想应该都会有这样一个深刻的感受:在编写页面时,经常会遇到不同场景的布局问题,我们不仅需要针对特定的场景选定可实现的布局实现方案,而且需要考虑未来可能发生的变化。

而要做好这一点,就需要扎实的前端基础作为依托。

所以在我看来,学好前端布局基础,其目的是为了在面对不同场景的布局问题时,能够提出一种合理的布局方案:既能解决问题,又能最大程度地拥抱变化。

4. 量化布局方案的合理性

前面提到过的“解决问题”、“拥抱变化”,仅仅是合理布局方案的两大核心目标,如果想要让目标更好地落地,我们仍需要一些量化合理性的原则,来提升对目标的方向感,以让目标变得更加可执行。

说到量化“解决问题”这个目标,对于即写即呈现的前端代码来说,我们可以很直观地判断一种方案是否可行,所以不需要太多的量化手段,我们主要是要量化“拥抱变化”这个目标。

要想量化“拥抱变化”这个目标,我们首先得清楚“变化”有哪些。笔者根据过往的开发经验,将变化分为两大类:一是布局需求的变化,二是运行环境的变化。

而针这这两类变化,我提出如下量化原则:

一、对于布局需求的变化,可以做到:

二、对于运行环境的变化,可以做到:

如果一个方案能够体现以上几点原则,我认为可以称得上是一个合理的方案。最后,我将布局实现方案的合理性归纳为:方案在满足正确性的前提下,其实现逻辑规范、实现职责分明且拥有良好的浏览器兼容性。

下面我们正式开始介绍与“传统布局方案”相关的布局基础知识。

5. 布局基础要点

5.1. CSS标准盒模型(或W3C盒模型)

一个web页面是由众多html元素拼凑而成的,而每一个html元素,都被解析为一个矩形盒,而CSS盒模型就是这种矩形盒的解构模型。CSS盒模型,它由内到外、被四条边界Content edge、Padding edge、Border edge和Margin edge划分为四个区域:Content area、Padding area、Border area和Margin area,在形状上,Content area(又称content-box)是实心矩形,其余是空心环形(空心部分是Content area),如下图所示:

CSS盒模型-区域划分图

此外,每个区域都有其特定的作用:Content area,是当前元素用来容纳所有子孙元素;Padding area,是当前元素用来隔离自身和子孙元素;Border area是当前元素用来显示自身的轮廓;Margin area,是当前元素用来隔离自身和相邻元素。理解每个区域的作用和职责至关重要,有助于我们写出优雅、清晰的布局代码。

CSS盒模型-区域作用图

而每个区域的尺寸,又分别由特定的CSS属性来控制,如下图所示:

CSS盒模型-属性控制图

这些CSS尺寸属性(width、height、padding、border和margin),相当于一个个hook,我们可以通过设置这些“hook”来达到调整元素尺寸的目的。

5.2. box-sizing(CSS3属性)

5.2.1. box-sizing的作用

box-sizing,顾名思义,其作用与设置CSS box的尺寸大小有关,而CSS box又可细分为:

简单来说,box-sizing的作用就是告诉浏览器:CSS属性width和height是用于设置哪一种box的尺寸,在W3C标准中,box-sizing的值仅有content-box和border-box(firefox则额外支持padding-box)。所以,

当box-sizing的值为content-box(默认值)时,有:

width = content-width;

height = content-height;

当box-sizing的值为border-box时,有:

width = content-width + padding-left + padding-right + border-left-width + border-right-width;

height = content-height + padding-top + padding-bottom + border-top-height + border-bottom-height;

关于box-sizing的作用,还有另一种表述:告诉浏览器,是使用W3C盒模型,还是使用IE盒模型。

5.2.2. box-sizing的浏览器兼容性

box-sizing是CSS3属性,在IE8+(包含IE8)开始支持,然而在IE8,box-sizing的值为border-box时,不能与min-width, max-width, min-height或max-height的一起使用,因为IE8对min-和max-的解析,仍是作用于content-box,不受box-sizing属性控制。

5.2.3. box-sizing的**产生原因**

仅仅掌握box-sizing的基础使用,是无法真正理解box-sizing的作用,所以要想把box-sizing用好,我们还得从CSS盒模型的发展史来深入理解box-sizing的产生原因。

在CSS的发展历程中,有两个版本,一个是IE盒模型,另外一个是W3C盒模型。IE盒模型,在IE5-(包含IE5)和navigator4上均有使用;而W3C盒模型,在IE6+(包含IE6)标准模式开始得到支持。两种版本的盒模型,其实在模型结构上是一致的,只是with和height属性的计算规则不一样,其区别,等价于“box-sizing的两个属性值border-box和content-box的区别“,如下图所示:

IE盒模型和W3C盒模型的区别

在了解了CSS盒模型的发展历程,以及后来新增的box-sizing的开始支持时间,我们不难发现:

对于IE盒模型,我们看到了W3C组织先去后留的反复态度,我不禁提出以下两点疑惑:

问题一: 为什么W3C组织在制定盒模型标准时,一开始会放弃IE盒模型,而重新建立以content-box为计算规则的W3C盒模型?W3C盒模型比IE盒模型好在哪里?

问题二:为什么在CSS3中,又重新提供了对IE盒模型的支持(box-sizing设置为border-box),又是基于哪方面的考虑?

关于第一个问题,本人并没有找到相关的官方说明,但我比较认可的一种说法是:

在日常生活中,我们在放东西时,会关心东西放到多大的盒子里面,这里的“多大”,往往指的是盒子的容量,而不是整个盒子的尺寸。而HTML元素也被看成是一个盒子、一个容器,相应地,我们也会更关注其内容区域的尺寸,也更希望对内容区域有更强的控制力。所以,从存储的角度来看,W3C盒模型更符合这种认知,借助width和height,我们可以通过声明的方式,直接设置conent-box的尺寸。而如果采用IE盒模型,我们只能先设置整个盒子的尺寸(border-box),最后由浏览器自动计算出content-box的尺寸,显得对content-box尺寸的控制力较弱。

关于第二个问题,我认为有以下几个原因:

  1. 有助于复用基于IE盒模型开发的CSS代码;

  2. IE盒模型的“遗老遗少”可以延续计算习惯;

  3. 部分html元素,在解析时依然采用IE盒模型的计算规则(这样的元素有select、button),使用IE盒模型有助于保持一致性;

  4. 从元素布局的角度来看,IE盒模型的width和height的语义更符合人类的直观认知(盒子的尺寸、轮廓应该以border为界);

  5. 在弹性布局和响应式布局场景,IE盒模型比W3C盒模型表现更佳(更容易实现、浏览器兼容性更好),如设置某个元素的宽度始终占当前行总宽度的固定百分比(小于100%),并且该元素拥有固定像素的padding;

举个例子:设置一个元素,其宽度分别为当前行的40%,且该元素的padding固定为10px。

IE盒模型的实现方案:

方案一: 使用一个div即可实现,直接设置width为40%,padding为10px;

W3C盒模型的实现方案:

方案一:使用两个div模拟实现,外层div的width设置为40%,内层div的padding为10px, width为auto;

方案二:使用一个div即可实现,但是需要借用CSS3的calc函数,动态计算其内容区域的宽度,即width为calc(40% - 20px), padding为10px;

显然,IE盒模型的实现方案更加简洁,而且浏览器兼容性更好。

对上述两个问题的解答,其实也是对IE盒模型和W3C盒模型的一个比较。我们可以从比较中,明晰两种盒模型各自的优缺点。同时,经过大量的实践经验证明和充分讨论,IE盒模型总体上是优于W3C盒模型,这也是IE盒模型能够“王者归来”,被W3C组织重新启用的真正原因。

于是乎,为了重新在新规范中支持IE盒模型,也为了向后兼容W3C盒模型,W3C组织在CSS3中添加了box-sizing属性,用于切换这两种盒模型。

5.2.4. 对box-sizing的评价

在我看来,在CSS3中添加box-sizing其实是一种比较trick的弥补方式。虽然这种设计能重新提供对IE盒模型的支持,但是在某种程度上,造成了CSS属性width和height具有二义性,使其职责变得不单一。然而这似乎又是最可取的修正方案了,因为在网上已经存在了大量基于W3C盒模型开发的网页,后续的修正方案不得不考虑向后兼容。我们只能在不合理设计的基础上,再次用不优雅的设计来解决新的问题。

如果能够穿越时空,回到W3C组织在讨论“如何设计标准盒模型”时,我认为更合适的设计方案是添加新的属性单独用于设置content-box的尺寸,而保留IE盒模型width和height原来的语义。这样就不会有后来的box-sizing属性。

我猜想W3C组织也想过这种方案,但是当时可能认为:

  1. 直接设置元素border-box尺寸的意义不大,且border-box的尺寸设置也能够通过设置content-box的尺寸来实现;(其实同时两种支持content-box和border-box尺寸的设置也无妨,完全可以当做是语法糖)

  2. 设置content-box尺寸又属于高频操作,若新增的属性命名为content-width或content-height则显得名称太长;(命名为cwidth和cheight也行)

基于这两点,最终提出了用width和height来设置content-box尺寸的解决方案,也就是如今我们看到的W3C盒模型。

纵观CSS盒模型的发展史和box-sizing的创建原因,感触比较深的就是:不合理的设计并不是总会被修正,因为既有实现的广泛应用,会使得其被继续遵循。而后续的新增设计,也是建立在先前不合理设计的基础上。这是否也验证了黑格尔的哲学名言:存在即合理?

关于对box-sizing的评价和思考,可能显得有一些马后炮,一些猜想也可能只是笔者的凭空臆想,并非W3C组织原意。在这里只是为了分享我对重构的一些思考,也是为了与和我有同样疑惑的同学做个交流。

5.2.5. box-sizing的最佳实践

在这里主要回答三个问题:

问题一:box-sizing的值,取content-box好,还是取border-box值好?

如果最低需要兼容IE6、7,那么box-sizing不可使用,只能使用W3C盒模型;

如果最低只需兼容IE8,那么使用content-box在功能上完全没有问题,只是在一些弹性布局和响应式布局实现上,会稍微麻烦一点;而border-box虽然在这些方面表现更好,但是不能和IE8的min-width、min-height、max-width和max-height四个属性一同使用,使用的话就要稍微注意一下;

如果最低只需兼容IE9,那么本人觉得,全局配置取content-box更为合适,局部配置二者均可。原因如下:

  1. CSS3提供了calc函数(IE9+),使得W3C盒模型有了强有力的助攻,在弹性布局和响应式布局的表现,与IE盒模型无异;

  2. 默认优于配置原则:我个人认为,“默认优于配置”,特别是在reset.css这种架构级、平台级的配置文件,要尽量避免对未来可能引入的模块有侵入性。譬如,我们在一个项目中时常需要引入第三方组件,如果这个组件没有强声明box-sizing,那么其默认使用的就是W3C标准盒模型,如果在全局的reset.css中设置box-sizing的值为border-box以选用IE盒模型,那么就会影响到这一类默认基于W3C盒模型的第三方组件的样式。这里也给我们提了一个醒,在封装组件时,记得强声明box-sizing,哪怕你使用默认的content-box。

总之,大部分场景二者可以互换,只是使用理念不一样。小部分场景border-box更具优势,但随着calc函数的支持,这种优势已经不再,相反content-box是默认值的优势愈加明显。

我个人建议是:全局使用默认W3C盒模型(你的CSS代码最低能够兼容IE6/7,在IE8也可以和min-和max-一起使用),局部场景二者均可(仅把IE盒模型当作是一种布局技巧来使用)。你喜欢全局使用IE盒模型也是可以的,只要确认项目只需要兼容到IE8,即便有可能影响到引入的第三方组件,也是有办法处理的。

问题二:如果想要全局使用IE盒模型,那么在reset.css中,该怎样设置box-sizing?

这里提供一个参考:

html {

-webkit-box-sizing: border-box;

-moz-box-sizing: border-box;

box-sizing: border-box;

}

, :before, *:after {

-webkit-box-sizing: inherit;

-moz-box-sizing: inherit;

box-sizing: inherit;

}

这样设置的好处有:

  1. 子元素的盒模型类型,默认由父元素决定,方便组件统一设置;

  2. 支持低版本的浏览器:Safari (< 5.1), Chrome (< 10), and Firefox (< 29);

问题三:Bootstrap3开始,全局使用IE盒模型(box-sizing取border-box),又是基于怎样的考虑?怎么协调好与基于标准盒模型开发的第三方组件的关系?

众所周知,BS2还考虑对IE7的兼容,而BS3彻底放弃了对IE7的兼容,并将box-sizing设置为border-box。关于这一点,可见“Bootstrap 3 released”官方发布的change list,摘录如下:

从以上直白的表述中:Better box model by default(默认使用更好的盒模型),我们可以看出BS作者是IE盒模型的拥趸。作者也把理由罗列了出来,其核心内容也是如前面所提到的,IE盒模型在响应式布局上的良好表现。补充的一点是,如果不全局设置border-box,而每个组件及其子组件单独设置,维护起来将是个梦魇(作者在官方编号为12351的issure中有提到)。

而关于BS如何处理好与基于标准盒模型开发的第三方组件的关系,亦可参见编号为12351的issue:"Move away from * {box-sizing: border-box } to play nice with 3rd party scripts"

作者在issue中,霸气又委婉地回应:

  1. BS并不考虑对第三方组件和框架的支持。作者委婉地说,BS是一个大项目,活跃维护者也主要是四个人,顾不来所有人的需求啊~(但感觉作者是在说,BS是个又大又全的框架,你丫还搞第三方组件干嘛呀)

2.IE盒模型,用了大家都说好,为什么第三方组件不转过来支持IE盒模型啊(果然是铁粉)

本章节从box-sizing的作用、浏览器兼容性、产生原因、评价和最佳实践这5个切入点,来讲解box-sizing属性,以期加深各位同学对这个属性的理解和掌握。特别要强调的一点是,如果刚接手某个项目,在编写CSS代码前,先看看项目是否有全局配置box-sizing,并根据具体的取值来选用相应的尺寸计算规则。

5.3. 元素的分类及其布局特性

5.3.1. 元素的分类

从元素的布局特性来分,主要可以分为三类元素:block-level(块级)元素、inline-level(行内级)元素和inline-block-level(行内块级)元素,我们可以对其下个定义:

5.3.1.1. 块级元素

display属性取block、table、flex、grid和list-item等值的独占一行显示的元素。

5.3.1.2. 行内级元素

display属性取inline值的可在同一行内排列显示的元素。

5.3.1.3. 行内块级元素

display属性取inline-block、inline-table、inline-flex和inline-grid等值的兼具块级元素和行内级元素布局特性的元素。

友情提示:

1)关于各类元素display的取值,实际已全部罗列,但为了保证定义能拥抱变化(未来可能引入新的display属性值),在罗列时使用了等字;

2)w3c官方文档,把display属性值为inline、inline-block、inline-table的元素,统称为inline-level元素,我不太喜欢也不太认可这种泛泛的分类,本文重新定义了一个“inline-block-level元素”的概念,来对“inline-level元素”进行了细分,并将inline-blocks、inline-tables单独分类为inline-block-level元素,原文档如下:“The following values of the 'display' property make an element inline-level: 'inline', 'inline-table', and 'inline-block'.”

5.3.2. 元素的布局特性

5.3.2.1. 块级元素(block-level)的布局特性

对于块级元素,有如下几个布局特性:

  1. 独占一行(width默认为100%,height为0);

  2. 可以设置任何尺寸相关的属性(width、padding、margin和border);

5.3.2.2. 行内级元素(inline-level)的布局特性

在讲行内级元素的布局特性之前,我们先了解一下行内级元素的分类,其可再细分两类元素:

1)可置换行内元素

在MDN中,其对“可置换行内元素”的定义如下:

按字面翻译,“可置换行内元素”,是展示内容不在CSS作用域内的元素。这句话是不是不好理解?我们可以换另外一种方式理解:“可置换行内元素”,是这样一类元素,其展示的内容是通过元素的src、value等属性或CSS content属性从外部引用得到的,可被替换的。随着内容来源或内容数量的变化,可置换元素本身也会有水平和垂直方向上尺寸的变化。典型的可替换元素有 <img><object><video><embed>,表单类的可替换元素有<textarea><input> ,某些元素只在一些特殊情况下表现为可替换元素,例如 <audio><canvas>

特别地,通过 CSS content 属性来插入的对象又被称作 匿名可置换元素。

2)不可置换行内元素

“不可置换行内元素”其实就是我们常见的一类行内元素,这一类行内元素有等。“不可置换行内元素”是相对于“可置换行内元素”的,其展示的内容是在CSS作用域范围内的,是不可替换的。

言归正传,行内级元素有如下几个布局特性:

  1. 在一行内可以与多个同类型的元素按从左到右的顺序排列;

  2. 不可置换行内元素不能设置width、height和垂直方向上的margin,而可置换行内元素则可以;

  3. 在水平和垂直方向上的对齐方式,行内级元素分别受父元素的text-align属性和自身vertical-align属性的控制(父元素是table-cell元素时,也受父元素的vertical-align属性控制),在水平方向上默认左对齐,在垂直方向上默认在行框的baseline基线上显示(“行框”的概念,会在后面深入讲解);

友情提示:

1)有时候我们不必太纠结于哪些行内元素是可置换行内元素,因为有些浏览器(如chrome)的默认样式(user agent stylesheet),会将这一类元素重置为inline-block元素,我们可以统一把可置换行内元素理解为inline-block元素,因为其布局特性与inline-block-level元素相同。

2)当inline-level元素水平排列时,两两之间可能会出现大约6px的空白,这是由元素间的空白字符(换行符、空格或制表符)产生,如下图所示:

清除方法有很多,本人习惯用浮动的方式来处理,其它方法可自行google。

5.3.2.3. 行内块级元素(inline-block-level)的布局特性

行内块级元素兼具block-level元素和inline-level元素的布局特性,主要体现为:

  1. 排列方式与行内级元素同,不独占一行,在一行内按从左到右的顺序排列;

  2. 水平和垂直方向上的对齐方式与行内级元素同;

  3. 和块级元素一样,可以设置任何尺寸属性(但width默认为0);

注:我们不难发现,其实可置换行内元素,其布局特性与inline-block-level元素相同。

5.4. 格式化上下文(Formatting Context)

格式化上下文,它指的是具有某种CSS格式化规则(布局规则)的上下文环境,在这个上下文环境内的所有子元素,都将根据其特定的CSS格式化规则来进行排列。

我们可以给某个作为容器的元素指定特定的格式化上下文,也就是说我们可以定义一个具有特定布局规则的渲染区域。常见的格式化上下文有BFC(CSS2.1 规范)、IFC(CSS2.1 规范)、 FFC(CSS3规范新增)和GFC(CSS3规范新增),具体介绍如下:

5.4.1. BFC

5.4.1.1. 定义

BFC, 全称是block formatting context,它是一个独立封闭的渲染区域,在这个区域内的所有元素,从区域的顶部起,一个接一个地根据自身的布局特性进行排列:在这个区域内的块级元素 ,按从上到下的顺序显示,相邻的块级元素可以使用margin隔离,但在垂直方向上相邻的块级元素会发生margin合并;在这个区域内的inline-level或inline-level-block元素,则按从左到右的顺序显示(W3C组织说BFC内部的元素都是一个接一个地垂直显示,我觉得不是很严格,因为BFC内部也可以容纳inline-level和inline-level-block元素,所以这里我的解释和W3C还是稍微有一些不一样)。具有BFC格式化环境的元素,我们称之为BFC元素,可以说,BFC定义了BFC元素content区域的渲染规则。

看到这段描述,是不是觉得BFC的渲染规则,不就是文档流的默认布局规则吗?确实很像,但不完全等同。BFC元素内部的渲染规则和普通块级元素内部的渲染规则,还是有一些不同的,我们将在5.4.1.3. 特性一节详述。

5.4.1.2. **创建方式**

创建BFC元素的方式有如下几种(摘自MDN BFC):

  • 根元素或其它包含它的元素

  • 浮动元素 (元素的 float 不是 none)

  • 绝对定位元素 (元素的 positionabsolutefixed)

  • 内联块 (元素具有 display``: inline-block)

  • 表格单元格 (元素具有 display``: table-cell,HTML表格单元格默认属性)

  • 表格标题 (元素具有 display``: table-caption, HTML表格标题默认属性)

  • overflow 值不为 visible 的块元素

  • display``: flow-root

  • contain为以下值的元素: layout, content, 或 strict

  • 弹性项 (display``: flexinline-flex元素的子元素)

  • 网格项 (display``: gridinline-grid 元素的子元素)

  • 多列容器 (元素的 column-countcolumn-width 不为 auto, 包括 column-count: 1的元素)

  • column-span``: all应当总是会创建一个新的格式化上下文,即便具有 column-span: all 的元素并不被包裹在一个多列容器中。

5.4.1.3. 特性

BFC元素具有如下特性:

  1. 对应一个独立、封闭的渲染区域,子元素的CSS样式不会影响BFC元素外部;

举个例子,我们分别用连续的两个块级元素,一个是普通块级元素,另一个是BFC元素(均使用绿色背景),分别包裹一个margin-top为20px的子元素(黄色背景),对比其布局效果:

说明:

  • 普通块级元素,其子元素的margin-top,不会隔开自身与父元素(普通块级元素),但是会作用到父元素外部(将父元素和叔伯元素或祖父元素隔开);

  • BFC元素,作为一个独立、封闭的渲染区域,其子元素的margin-top,则会隔开自身与父元素(BFC元素),而不会影响到父元素外部;

  1. 浮动子元素参与BFC父元素的高度计算,也就是BFC元素能够识别浮动元素(将元素声明为BFC元素,也是clearfix解决父元素塌陷问题的一种常用方法);

举个例子:

说明:

  • BFC元素,能够识别浮动子元素,浮动子元素参与BFC元素的高度计算,不会出现“高度塌陷”问题;

  • 普通块级元素,不能够识别浮动子元素,会出现“高度塌陷”问题;

  1. 占据文档流的BFC元素(可使用overflow: auto创建),能够识别浮动的兄弟元素;

举个例子:

说明:

  • 普通块级元素,不能够识别浮动的兄弟元素,会被浮动的兄弟元素覆盖部分内容;

  • 占据文档流的BFC元素(可使用overflow: auto创建),能够识别浮动的兄弟元素,不会被浮动的兄弟元素覆盖,与之同行显示;

  1. 占据文档流的BFC元素(可使用overflow: auto创建),width为auto时,会占满当前行的剩余宽度;

举个例子:

说明:

  • 文档流中的BFC元素, width为auto时,会占满当前行的剩余宽度;

5.4.2. IFC

5.4.2.1. 定义

IFC, 全称是inline formatting context,其内部的元素,在水平方向上,一个接一个地显示;在垂直方向上,每个元素可以设置不同的对齐方式;IFC内部的元素,被一行行的矩形框所包含,这些虚拟的矩形框,我们称为行框(line box)。IFC的作用区域,可以看成是包含其所有子元素的行框组成的矩形区域。

5.4.2.2. 创建方式

和BFC相比,它的创建方式是被动的、隐式的,是由所包含的子元素来创建:只有在一个区域内仅包含可水平排列的元素时才会生成,这些子元素可以是文本、inline-level元素或inline-block-level元素。

5.4.2.3. 特性

  1. IFC内部的元素,按从左到右、从上到下的顺序排布;

  2. IFC内部的每个元素,都可以通过设置vertical-align属性,来调整在垂直方向上的对齐;

  3. 包含这些内部元素的矩形区域,形成的每一行,被称为line box(行框,后面会详细介绍);

5.4.3. FFC和GFC

FFC(flex formatting context)和GFC(grid formatting context),分别是flex布局和grid布局的内容,这两个模块的内容非本文介绍的重点,所以感兴趣的同学可以自行google。

5.5. 包含块(Containing Block)

5.5.1. 定义

我们在设置元素尺寸属性(width、height、padding、margin和border)的百分比值或偏移属性(top、right、bottom和left)的值时,通常会有一个“相对参考系”,这个"相对参考系"一般是包裹着这个元素的块级祖先元素(一般是块级父元素)或离这个元素最近的非static(relative、absolute和fixed)定位的祖先元素。这些具有“相对参考系”作用的祖先元素,其容纳区域(cotent box或padding box),其实还有一个专门术语形容之,那就是包含块(在知识体系中有个包含块的概念,有助于加深对position定位原理的掌握)。

特别地,relative定位元素,其尺寸属性(width、height等)的“相对坐标系”仍是其包含块(块级祖先元素(一般是父元素)的content box),但是偏移属性(top、right、bottom和left)的“相对坐标系”则是其在文档流原来的位置。

5.5.2. ICB(initial containing block, 初始包含块)

5.5.2.1. 定义

如前面所说,任何一个元素都会有一个包含块作为设置尺寸属性和偏移属性的“相对参考系”,而对于顶层的根元素,没有任何元素包裹它,它的包含块是什么?它选取什么作为“相对参考系”?

其实根元素是有包含块的,它是一个不可见的矩形框,W3C组织称之为ICB(initial containing block, 初始包含块)。以下是W3C组织对ICB对定义:

The containing block in which the root element lives is a rectangle called the initial containing block.

5.5.2.2. ICB的尺寸和起始位置(左上角坐标)

在解释ICB的尺寸和起始位置时,在这里先简单补充一个背景知识:连续媒体(continuous media)和分页媒体(paged media)。如何理解这两个概念?在视觉阅读层面,它们是展示内容的两种呈现方式。

连续媒体,就是采用连续展示内容的方式,它保持了展示内容显示的连续性(一页显示所有内容),我们可以在连续媒体的viewport(可视窗口)查看当前呈现的内容。特别地,浏览器窗口就可以看成是连续媒体,当内容的尺寸超过viewport时,读者可以通过平滑滚动的方式来阅读内容。

分页媒体,就是采用切页展示内容的方式,它将要展示的内容切分为等尺寸的多页(分页显示所有内容),我们可以在分页媒体的page area(页面显示区域)查看当前呈现的内容。特别地,像幻灯片、电子书阅读器,就可以看成是分页媒体,当内容的尺寸超过page area时,读者可以通过切页的方式来阅读内容;

对于属于连续媒体(continuous media)的浏览器窗口来说,ICB的尺寸为viewport(浏览器视窗),其起始位置为画布原点(canvas origin,即首屏的左上角,浏览器渲染数据后生成的内容文档可以看成是一张画布)。

对于分页媒体来说,ICB的尺寸为page area(关于ICB在分页媒体的起始位置,没有找到相关资料,但这个对于本文来说也不是重点)。

直观来看,根元素的包含块ICB,就是“首屏”。

5.5.3. 不同定位元素分别对应的包含块

  • static和relative定位元素的包含块,为其块级祖先元素(通常是块级父元素)的content box;

  • absolute定位元素的包含块,为最近的非静态定位祖先元素的padding box,查无非静态定位祖先元素,那么它的包含块是ICB(即根元素的包含块);

  • fix定位元素的包含块,为当前viewport(视窗);

在这里要强调的一点,ICB(初始包含块)是专有名词,它特指根元素的包含块。不要将一个元素的初始包含块,错误理解为它的父元素。MDN的一位编辑者也犯了这种错误。具体如下:

经修正后:

也有一些权威CSS书籍说,当一个绝对定位元素找不到最近的非static祖先元素时,则相对于根元素定位,这种说法也是不严谨的。刚好看到一本,如下:

图1. 书的封面

图2. 原话截图

我们可以通过一个简单的例子推翻这种说法:将根元素html的高度设置为超过viewport高度,如5000px(假设viewport高度为500px),再将一个没有最近的非static祖先元素的绝对定位元素的bottom设置为0,尺寸为100px*100px即可。如果真如该书中所言,那么在首屏时,该绝对定位元素是被隐藏在滚动条下面的。而实际情况是:该绝对定位元素必然出现在首屏的底端,并且会随着页面滚动而滚动。验证如下:



相信这个谬误在前端圈流传已久,希望各位同学引起重视。



5.6. 基本原理

5.6.1. 文档流(正常流)

5.6.1.1. 定义

关于“文档流”,并没有找到较为官方的定义。笔者从google搜到一些认为比较靠谱的解释,罗列如下:

1)摘自:《CSS: understanding the document flow》

The document flow is the model by which elements are rendered by default in the CSS specifications. In this model, elements are rendered according by their default display rule. In other words, block-level elements are displayed on a new line and inline elements on the same line. Everything is stacked in an ordered way from top to bottom.

2)摘自:《What is "document flow"?》

Document flow is the arrangement of page elements as defined by positioning statements and the order of html statements; that is, how the different elements take up space and arrange themselves around each other.

在这里我想分享一下我自己对“文档流”下的定义:

文档流,是页面元素默认存放的“容器”。

**5.6.1.2. 特性**

文档流具有如下特性:

  1. 文档流按照页面元素书写的顺序,将页面元素按从左到右,从上至下的一般顺序进行排列,而页面元素则根据自身的布局属性(block-box or inline-box),决定是行内显示,还是换行显示;

  2. 文档流内的元素,相互尊重:有序排列,彼此识别;

5.6.1.3. 脱离文档流

元素脱离文档流,按我之前下的定义,其实就意味着:元素脱离了默认存放的容器,换到另外一个容器存放。一个元素脱离了文档流,这样会导致:其父元素无法识别其,其也不参与父元素高度的计算。若有一个父元素的所有子元素都脱离文档流,则会出现“高度塌陷”问题。常见的脱离文档流的方法有:

  • 将元素设置为浮动元素

  • 将元素设置为absolute、fixed元素

5.6.2. 浮动(float属性)

5.6.2.1. 浮动元素的分类

根据float属性的设置,元素可以分为浮动元素(值为left或right)和非浮动元素(值为none)。而按浮动方向划分,又可细分为:

  • 左浮动元素:float值为left的元素

  • 右浮动元素:float值为right的元素

5.6.2.2. 浮动原理

要想掌握浮动元素的浮动原理,只要理解浮动元素的浮动起始位置、浮动方向和浮动结束位置即可。

  • 浮动起始位置

浮动元素(包括左右)的浮动起始位置,为最后一行最左侧的空白位置,而不管空白位置是否能够容纳当前浮动元素;

  • 浮动方向

左浮动元素的浮动方向为从起始位置向左浮动;

右浮动元素的浮动方向为从起始位置向右浮动;

  • 浮动结束位置

    左浮动元素遇到第一个左浮动元素或包含块的最左侧padding时,结束浮动;

    右浮动元素遇到第一个右浮动元素或包含块的最右侧padding时,结束浮动;

以下demo可以帮助各位同学理解浮动元素的三要素:

/06:layout/float/1. 浮动元素三要素.html:

左浮动元素-1(width: 30%; height: 100px;)
左浮动元素-2(width: 30%; height: 200px;)
左浮动元素-3(width: 30%; height: 100px;)
左浮动元素-4(width: 30%; height: 100px;)

显示结果:

说明:

a. 有四个连续左浮动的元素,每个元素宽度为30%;

b. 当一行排满三个元素时,当前行只剩10%的宽度,不足以容纳第四个左浮动元素;

c. 第四个浮动元素,从起始位置(最后一行的最左侧空白)开始向左浮动,直到遇到第二个浮动元素的边界;

为了帮助大家理解好浮动原理,在这里我想额外定义几个术语:

  • 左浮动队:由若干个连续的左浮动元素组成

  • 右浮动队:由若干个连续的右浮动元素组成

  • 左浮动队头元素:左浮动队的第一个元素,也是最左侧的元素

  • 右浮动队头元素:右浮动队的第一个元素,也是最右侧的元素

特别地,

  1. 同一行内,最多有两条浮动队,一是左浮动队,二是右浮动队;

  2. 同一行内,一条浮动队可能占满一行;

  3. 连续浮动的若干元素,如果无法在同一行内显示,则会按行被切分为两条或更多条浮动队;

5.6.2.3. **浮动对元素display的影响**

当元素设置为浮动元素后,可能会引发display属性的值变化,具体规则如下:

浮动元素display属性变化对照表

5.6.3. 清除浮动(clear属性)

5.6.3.1. 三要素

清除浮动,其作用是改变“当前元素”与“前一个声明的浮动元素”之间的默认布局规则,这种改变主要体现为:让当前元素换行显示。这句话包含三个要素,分别为:

  • 使用者:当前元素(浮动元素或者非浮动元素的块级元素)

  • 作用对象(清除谁的浮动):前一个声明的浮动元素

  • 目的(作用):让当前元素换行显示

特别地,为什么使用者不包括非浮动的inline元素?因为非浮动的inline元素能够识别浮动元素,是否使用clear清除“前一个声明的浮动元素”的浮动,其布局结果是一样的。感兴趣的同学可以参考:06:layout/clear/4.非浮动inline元素清除左浮动.html,可以在调试器中观察注释非浮动inline元素的clear:left前后,其显示位置的变化。而非浮动的块级元素,因为无法识别前面声明的左浮动元素,故会和左浮动元素发生重叠(左浮动元素在上),所以非浮动的块级元素使用clear:left清除前一个左浮动元素,就能避免重叠的现象。

5.6.3.2. clear属性的取值及应用场景

前面简单介绍了clear属性的作用,是清除前面声明的浮动元素的浮动,然后让当前元素换行显示。但是要具体怎么使用,我们还得深入到clear的属性值和应用场景。

clear属性的取值有left、right和both。那么它们的应用场景分别是什么?

left值的应用场景是,前面声明的浮动元素是向左浮动(float: left);

right的应用场景是,前面声明的浮动元素是向右浮动(float: right);

both的应用场景是,前面声明的浮动元素的浮动方向不确定,可能是左,也可能是右(了解过clearfix实现原理的同学,就不难明白);

再次强调一下,当前元素如果要清除浮动,清除的是前面声明的浮动元素的浮动,其clear属性要取什么值,跟当前元素的是否是浮动元素或浮动方向没有任何关系,而取决于其前面声明的浮动元素的浮动方向。

举个例子,一个右浮动元素(float:right),前面有一个左浮动元素(float:left),如果这个右浮动元素使用clear: left时,这个元素会清除前一个元素的浮动,进而换行显示;如果使用clear:left时,这个元素在当前行的最右端显示。如下图所示(06:layout/clear/4.右浮动清除左浮动.html):

图1. 右浮动清除左浮动

图2.右浮动元素清除右浮动

在了解完clear属性的取值和应用场景,我们可以对其作用,可以总结为:

如果当前元素浮动元素或非浮动的块级元素,且前面声明的元素是左(右)浮动元素,那么当前元素可以使用clear: left(clear: right),清除前一个左(右)浮动元素的左(右)浮动,此时当前元素会换行显示;如果当前元素clear的浮动与前面一个浮动元素的浮动方向不同向,当前元素不会换行;

5.6.3.3. 清除浮动后的margin合并问题

1)两个浮动元素之间,其垂直方向上的margin不会发生合并,如下图所示:

浮动元素间会不发生垂直margin合并

2)非浮动的块级元素和浮动元素之间,其垂直方向上的margin会发生合并,如下图所示:

非浮动的块级元素与浮动元素间会发生margin合并

特别地,MDN的文档说非浮动的块级元素与浮动元素间不会发生margin合并,实际上会,上述结果已经证明,已在MDN上更正该错误。以下为MDN未修改前的原话:

MDN未修正前的原话

5.6.3.4. 清除浮动的特殊应用:解决父元素高度塌陷问题

众所周知,当一个父元素里面的所有元素都是浮动元素时,此时父元素无法识别这些浮动子元素,会进一步导致父元素发生高度塌陷问题。一种通用的解决方案就是在父元素内部的尾部append一个非浮动的、尺寸为0的块级元素(后面简称fix元素),然后使用clear:both,让这个fix元素换行显示,进而让父元素能够识别前一行的高度。这种朴素的方案其实就是clearfix的基本原理,clearfix只是更加优雅地用:after来实现fix元素。

特别说明:解决父元素高度塌陷问题,还可以通过将父元素声明为BFC元素来实现。

5.6.4. 定位(position属性)

5.6.4.1. 定位元素的分类

根据position属性的取值,static(默认值)、relative、absolute、fixed,元素可以分为静态定位元素(值为static)、相对定位元素(值为relative)、绝对定位元素(值为absoute)和固定定位元素(值为fixed)。

注:position的取值还有sticky,但IE11都不支持,此处不讲

5.6.4.2. 定位原理

static定位元素定位时的相对坐标系:无法设置top、right、bottom和left这四个偏移属性;

relative定位元素定位时的相对坐标系:元素在文档流原来的位置(区域);

absolute定位元素定位时的相对坐标系:离元素最近的一个非static(包含relative、absolute和fixed)定位祖先元素(包含块为其padding box),如果没有则为ICB(初始包含块),即根元素html的包含块;

fixed定位元素定位时的相对坐标系:当前的视窗(viewport);

5.6.5. line box(行框)

5.6.5.1. 定义

前面在介绍IFC时,我们提到过line box的定义:包含IFC内部的所有子元素的虚拟矩形区域,形成的每一行,称为line box。由于它是矩形的,中文常见将之翻译为行框。

5.6.5.2. 模型结构(七线谱)

line box的模型结构,形如七线谱,其中有六条重要的线:top线、text-top线、middle线、baseline线、text-bottom线和bottom线,如下图所示:

行框七线谱** **

其中top线到text-top线的区域bottom线到text-bottom的区域,又称为行半距(half-leading),两个行半距之和,为一个行距;text-top线到text-bottom线的区域,称之为内容区域(content-area)。如下图所示:

行框区域划分

5.6.5.3. 行框高度的计算

行框的高度,即一行的top线和bottom线间的垂直距离,这个垂直距离为:上下两个行半距的高度和一个内容区域的高度之和。影响行框高度计算的因素来自两方面,一是自身line-height属性的设置,二是内部inline-level子元素的margin box高度的取值和line-height、vertical-align两个属性的设置。关于其计算规则,具体罗列如下:

  1. 一个元素的行框高度,可由该元素的line-height属性设置;

  2. 一个元素的行框高度,受不可置换(span、a、label等)的子元素的内容高度(text-top到text-bottom的垂直距离)影响(内容高度又受font-size属性和浏览器的解析规则影响,但主要由font-size决定;相同的font-size,在不同的浏览器,计算出来的内容高度也不一样,最终导致的行框高度也不一样);

  3. 一个元素的行框高度,可由不可置换(span、a、label等)的子元素的line-height属性设置;

  4. 一个元素的行框高度,可由可置换行内元素(如img)或display属性为inline-block、inline-table的这一类inline-block-level子元素的margin box高度和vertical-align属性决定,当vertical-align为top或bottom时,行框的高度达到最小,刚好为子元素的margin box高度;

图1. img元素的margin box高度比行框高度小

图2. img元素的margin box高度与行框高度一致,行框高度达到最小

  1. 如果同时满足以上设置条件,那么行框的高度取最大值;

友情提示:在图1 img元素的margin box高度比行框高度小,我们会看到img元素到父元素的底端会有一段空白,为什么会有这种现象?张鑫旭老师在《CSS深入理解vertical-align和line-height的基友关系》一文中将之定义为“幽灵空白节点”,其实结合行框理论来解释,这段空白并不“幽灵”,也很好理解:它是行框的baseline线到bottom线的垂直距离,可置行内换元素如img和inline-block-level元素,在被浏览器解析时,会和“文本”一样,默认在baseline线上显示,而不是在行框的bottom线上。

举个例子: 行框高度的计算

html:

span(line-height: 40px) span(line-height: 38px)
span(line-height: 40px) span(line-height: 38px)

显示结果(chrome下):

图1. line box内部仅有不可置换元素

图2. line box内部还有可置换元素img

说明:

a. 元素每一行的line-height,既可以由当前元素的line-height属性设置(32px),也可以由该行子元素的line-height属性设置(分别是40px和38px),但取最大的line-height(40px),如图1所示;

b. 特别地,如果一行内还有可以设置height的可置换元素如img(height: 50px),且img的高度大于设置的最大line-height(40px)时,那么该行会被撑高,浏览器会重新计算line-height(最终结果为63px),如图2所示;

**5.6.5.4. **与line box行框有关的两个重要属性:line-height和vertical-align

相信很多前端同学有这样的感觉:line-height和vertical-align这两个属性总是形影不离,而且有着一种说不清的关系。

它们到底有什么联系吗?

其实这两个属性的关系可由行框和行框内的inline-level元素来体现。line-height属性决定inline-level元素所在行框的高度,它是inline-level元素在一行内垂直方向上的显示范围;vertical-align属性则决定inline-level元素在一行内的垂直对齐方式,即决定inline-level元素在一行内垂直方向上的最终位置。下面我们来深入介绍这两个属性:

1)line-height属性

1.1)line-height属性的作用

line-height属性一般用于块级元素设置其内部每一行的高度,即默认行高;line-height属性也可以用于不可置换元素(如span、a)设置所在行框的高度。也就说,每一行计算出来的最终行高,既受父元素line-height属性的影响,也受子元素line-height属性的影响。

1.2)line-height属性的取值

line-height的取值有和关键字normal(默认值)。其中:

  • 表示使用指定带单位的长度来设置line-height,这些长度单位可以是px、pt和em和rem;
  • 表示用font-size值的倍数来设置line-height;
  • 表示用font-size值的百分比来设置line-height;
  • 而关键字normal,其最终计算出来的尺寸,则取决于浏览器各自的解析机制和选用的font-family类型:浏览器会根据选用的font-family类型来计算出一个合适的值,W3C官方推荐使用值,并且推荐值的范围为1.0到1.2之间(但经过实测,浏览器在实现时,远比这个复杂,而且不同浏览器间也存在差异。唯一可以确定的一点是,最终的行高肯定会比font-size值要大)。

我们在将UI稿实现为页面代码时,常常强调要Pixel Perfect、高精准地还原设计稿。但

我们常常会遇到这样一个问题:当我们用一个块级元素包裹文本时,会发现块级元素的高度,实际比文本的font-size尺寸还要高,导致上下形成了一些空白,进一步造成块级元素内的文本与垂直方向上相邻元素的距离变大,如下图所示:

line-height值为normal

这种误差是由于line-height的默认值为normal,那有什么办法可以解决这个问题呢?较常用的方法是将块级元素的line-height设置为1或100%。设置后的结果如下图所示:

line-height值为1或100%

这样做也有一点不好,那就是:浏览器最终解析出来的内容高度,有可能是比font-size要大的,当行高为font-size时,文本内容就会溢出。我们将字体放大为100px,溢出效果就很明显,如下图所示:

line-height值为1或100%

1.2)line-height属性对元素高度的影响

我们可以通过了解line-height属性分别对块元素和不可置换的行内元素自身高度的影响、以及不可置换的子元素的line-height属性对父元素高度的影响,来深入理解line-height属性的作用。

为了帮助大家更好地理解line-height,我设计了如下三个小demo:

demo1: line-height属性对块级元素自身height的影响

html:

**// div为单行**

<div class="block">

    div(line-height: 32px)

</div>

**// div为多行**

<div class="block">

    div(line-height: 32px)

    <br>

    div(line-height: 32px)

</div>

显示结果(chrome下):

图1. div为单行时

图2. div为多行时

说明:

a. 当一个块元素不设置height,而且这个块元素仅有一行时,那么其height刚好等于line-height;

b. 当一个块元素不设置height,而且这个块元素有多行时,那么其height刚好等于每一行的line-height之和;

demo2: line-height属性对不可置换行内元素(如span)的height的影响

html:

span(line-height: 40px;font-size: 20px)

显示结果:

图1. 行内元素为单行时,height为28px(在chrome下)

图2. 行内元素单行时,height为20.5px(在firefox下

说明:

a. 不可置换行内元素为单行时,其height等于text-top线到text-bottom线的距离,所以line-height的取值不会影响到其height,其height由font-size和浏览器的默认解析机制决定(一般>font-size,大多少则取决于浏览器解析机制,如图1、2所示);

b. 不可置换元素为多行时,其height等于第一行的text-top线到最后一行的text-bottom线的距离,此时line-height的取值就会影响到其height,其height=line-height 行数 - (line-height - 每一行text-top线到text-bottom的距离),即height=line-height 行数 - 2 * half-heading;如下图所示:

图3. 行内元素为多行时(在chrome下)

demo3:不可置换的子元素(如span)的line-height,对父元素height的影响

html:

span(line-height: 40px) span(line-height: 38px)
span(line-height: 50px)

显示结果(chrome下):

说明:

a. 块级元素每一行的行高都可以不同;

b. 不可置换的行内子元素的line-height属性,可以决定所在行框的高度;

c. 如果一个父元素不设置height,那么其height为所有行的高度之和;

d. 不可置换的行内子元素的line-height属性,是通过影响行框的高度来影响父元素的高度的。

2)vertical-align属性

vertical-align的作用之一:就是用于设置inline-level元素自身在“行框”内的垂直对齐方式,其控制范围在一行内。较常用的值有top、middle、baseline(默认值)和bottom,不常用的有text-top、text-bottom、sub和super,几乎不用的有

行框七线谱

vertical-align属性的几个重要取值的作用如下:

  • 当vertical-align取top时,表示当前inline-level元素的上margin edge在行框内贴顶;

  • 当vertical-align取bottom时,表示当前inline-level元素的下margin edge在行框内贴底;

  • 当vertical-align取middle时,表示当前inline-level元素的垂直平分线和行框的middle线重合;

  • 当vertical-align取baseline时,表示当前inline-level元素的下margin edge紧贴在行框的baseline上;

vertical-align属性的另一个作用:就是table-cell元素用于控制其内部子元素在垂直方向上的对齐方式,而且这些子元素的类型不受限制,不仅可以是span,而且可以是div。

举个例子:

html:

div(top) div(middle) div(baseline) div(bottom)

显示结果:

说明:

a. table-cell元素通过设置自身的vertical-align属性,来设置其子元素在垂直方向上的对齐方式;

特别说明:我们常用说的使用table布局来实现子元素在父元素内部垂直居中,就是运用到了这个知识点。

5.6.6. margin

在传统的布局方案中,margin不仅用来隔离自身与相邻元素或父元素(一般不推荐用来隔离父元素),而且在元素水平和垂直方向上的居中定位,亦发挥了重要的作用。下面我们来深入介绍margin的相关布局特性。

5.6.6.1. auto的计算规则(在width和margin上使用)

1)水平方向上

谈到“如何设置文档流中的块级元素在父元素内部水平居中?”这个布局问题,相信很多同学马上会想到这个方案:给元素设置固定宽度,并使用margin: 0 auto(水平方向上的margin为auto)

.child {

width: 100px;

margin: 0 auto;

}

它的实现原理,包含如下四个基础知识点:

  • 块级元素的水平尺寸(outerWidth,margin box的宽度)的计算规则:

outerWidth = margin-left + border-left-width + padding-left + width + padding-right + border-right + margin-right,如下图所示:

  • 文档流中的块级元素,其在水平方向上的尺寸属性的初始值,仅width为auto,其余为0

  • 在水平方向上的尺寸属性,仅width、margin-left和margin-right可以设置auto值(自动计算)

  • 文档流中的块级元素,其在水平方向上的尺寸属性,当值为auto时,表示取所在行的剩余宽度,特别地,当margin-left和margin-right的值均为auto时,会平分所在行的剩余宽度

在理解了上述四个基础知识点,我们不难理解其原理:

当块级元素在水平方向上的尺寸属性,除了margin-left和margin-right值为auto,其余皆为定值,那么margin-left和margin-right会自动平分父元素的剩余宽度,进而达到在父元素内部水平居中的效果,如下图所示:

图1. 元素在父元素内部水平居中(左右margin各取一半)

图2. 水平居中元素的盒模型解构图

结合上述四个基础知识点,我们还可以得出如下结论:

文档流中的块级元素如果不设置任何水平尺寸属性,那么其默认的width为当前行的content width,此时width取auto和100%,最终的计算值一样

2)垂直方向上

或许我们都曾问过这样的一个问题:既然可以通过设置margin: 0 auto,让文档流中的块级元素在父元素内部水平居中,那么可否通过设置margin: auto 0,让其垂直居中?

答案是不能的,因为文档流中的块级元素,其垂直方向上的margin为auto时的计算规则和在水平方向上的计算规则不同:不取父元素剩余的高度,而为0。W3C标准原话如下:

“If “margin-top” or “margin-bottom” is “auto”, their used value is 0″

或许大家会问,为什么要这样设计呢?官方并没有给出说明,但是有网友给出了如下几个解释,罗列如下,供各位参考(可以在留言中分享你的看法,本人比较认同第一条):

It could be because of the typical vertical page flow, where page size increases height-wise. So, centering an element vertically in its container is not going to make it appear centered, relative to the page itself, unlike when it’s done horizontally (in most cases).

And maybe it’s because of this same reason, they decided to add an exception for absolute elements which can be centered vertically along the entire page’s height.

It could also be because of the margin collapse effect (a collapse of adjacent elements” margins) which is another exception for the vertical margins.

在W3C标准规约中,虽不能使用margin: auto 0,实现普通文档流中的块级元素在父元素内部垂直居中,但是可以使用margin: auto 0,实现绝对或固定定位元素在包含块内部垂直居中,因为绝对或固定定位元素垂直方向上的margin,其 auto仍会取包含块的剩余高度,W3C官方文档给出的计算公式如下:

'top' + 'margin-top' + 'border-top-width' + 'padding-top' + 'height' + 'padding-bottom' + 'border-bottom-width' + 'margin-bottom' + 'bottom' = height of containing block

等价的简化公式:

子元素outerHeight = 包含块height - 子元素top - 子元素bottom

提示:

  1. 子元素outerHeight,是指当前子元素margin box的高度;

  2. 包含块height,可以为当前子元素的相对定位参考系元素的padding box的高度、ICB的高度或viewport的高度;

要使用上述规则来实现子元素在父元素内部垂直居中,那么就需要保证:

  • 子元素的top值 + bottom值为0(原因:让子元素outerHeight 等于包含块height)

  • 子元素的top值取0(原因:让子元素的上margin edge紧贴包含块的顶部)

下面通过一个demo来详细介绍:

html:

垂直居中的子元素

显示结果:

图1. 子元素在父元素内部垂直居中(上下margin各取一半)

图2. 垂直居中元素的盒模型解构图

说明:

a. 绝对定位的子元素的top为0,其轮廓(包含margin)的上边界与其包含块内容区域的顶部紧贴;

b. 由已知求未知:包含块的height已知,子元素的top值和bottom值之和为0,即子元素的outerHeight可求,又因为子元素height已知,故垂直方向上的剩余高度可以确定,当子元素的margin-top和margin-bottom均为auto时,将平分剩余的高度;

5.6.6.2. **margin合并(margin collapsing)**

在垂直方向上,元素与自身或相邻的兄弟元素、父元素、子元素的margin,会发生合并(注意:在IE6/7子元素垂直方向上的margin会隔离父元素,而不是和父元素的margin发生合并,IE8+则与标准浏览器同),margin取较大的值,而在水平方向上则不会。各位读者可以从下面三个demo,来理解垂直方向上margin的合并:

1) 父元素与子元素(第一个子元素、最后一个子元素)

html:

第一个子元素(margin-top: 20px)
最后一个子元素(margin-bottom: 20px)

显示结果:

说明:

a. 父元素(黄色)的margin-top(40px)和第一个子元素的margin-top(20px)发生融合(取较大的40px);

b. 父元素(黄色)的margin-bottom(40px)和最后一个子元素的margin-bottom(20px)发生融合(取较大的40px);

2) 上下相邻的兄弟元素(同层元素)

html:

第一个元素(margin-bottom: 40px)
第二个元素(margin-top: 20px)

显示结果:

说明:

a. 第一个元素的margin-bottom(40px)和第二个元素的margin-top(20px)发生融合(取较大的40px);

3) 空块级元素

html:

第一行
<div class="empty-block"></div>
第二行

显示结果:

说明:

a. 两行之间的空白区域,为一个空块元素;

b. 空块的margin-top为40px, margin-bottom为20px;

c. 两行之间的距离为40px,可知空块元素的margin-top和margin-bottom发生了合并,取较大值;

这里我们举了三个会在垂直方向上发生margin合并的例子,但是细心的同学可能记得,我们在“5.6.3.3. 清除浮动后的margin合并问题”章节,举了一个在垂直方向上例子不会发生margin合并的例子:浮动元素间在垂直方向上不会发生margin合并。

5.6.6.3. 子元素的margin隔离父元素

细心的读者不难发现,在“2) 上下相邻的兄弟元素(同层元素)” 的demo可以看到

子元素(绿色)垂直方向上的margin并没有将自己与父元素(黄色)隔离开(IE6/7会,IE8+不会)。

那么什么情况,子元素的margin可以和父元素隔离开?

首先要强调的一点是, 子元素水平方向上的margin,始终能够隔离父元素;然而子元素在垂直方向上的margin隔离父元素的情况,本人记录的仅有以下四种(欢迎补充):

case 1: 父元素是BFC元素

html:

子元素(margin: 20px)

显示结果:

说明:

a. 父元素(黄色)是BFC元素,子元素(绿色)垂直方向上的margin能够隔离父元素;

case 2:父元素拥有border

html:

子元素(margin: 20px)

显示结果:

说明:

a. 父元素(黄色)拥有border,子元素(绿色)垂直方向上的margin能够隔离父元素;

case 3:父元素拥有padding

html:

子元素(margin: 20px)

显示结果:

说明:

a. 父元素(黄色)拥有padding,子元素(绿色)垂直方向上的margin能够隔离父元素;

case 4:子元素是可置换元素或display为inline-block、inline-table、table-caption的元素

html:

display: inline-block
display: inline-table

显示结果:

说明:

a. 可置换行内的和display属性为inline-block、inline-table的子元素,其垂直方向上的margin能够隔离自身与父元素;

在这里对margin合并和margin隔离作一个小结,本人把遇到过的所有在垂直方向上会发生与不会发生margin合并、能使用margin隔离与不能使用margin隔离的例子,都罗列了出来(然而这仅仅是在标准浏览器的例子,在IE6/7情况还会不一样,但因为现在基本无需再兼容低版本的IE,所以就不再列举)。目的不是让大家记住它,而是让大家避开它:在垂直方向上,兄弟元素间尽量不要设置相邻的margin,子元素也不要使用margin来隔离父元素,这样能尽量保证你的CSS代码,在各种版本的浏览器都有较好的兼容性(显示一致)。

7. 结尾语

本文从CSS盒模型及其发展史、元素的分类及其布局特性、格式化上下文(Formatting Context)、包含块、基本原理(文档流、浮动、清除浮动、定位、行框、margin)这五大模块,系统介绍了一下前端的布局基础,希望此次分享,能够让各位读者对前端基础布局有一个底层、体系的认识。因为内容涵盖过广,难免会有纰漏,还望见谅和指正。

此篇文章断断续续写了几个月,从年前写到年后,一方面是因为这个标题太大,含括的内容太多,需要慢慢梳理;一方面是文中要讲的东西,很多是出于本人的感悟和总结,为了保证观点的正确性、严谨性以及和行业的标准术语做好同步,需逐一验证;还有一方面也是近几个月来,本人需要处理的私事较多,分散了精力。

时隔半年,依然有不少朋友还在关注我的公众号,谢谢你们的支持。这迟来的一篇分享,希望能对各位有用,后面我也会努力分享更多对大家有帮助的文章。

最后我还想再分享一些心得体会:

  • 不要轻视简单的东西

我们在生活中总是容易忽略一些简单的东西,因为轻视简单,导致过了几年依然也没有掌握,前端的同学更应该注意这一点。

  • 尽信书不如无书

不要太相信权威,而是要学会验证、总结,并构建自己的知识体系。

  • 学技术要看官方文档

很多同学在初学时喜欢看一些快速入门的教程,我觉得这种学习习惯挺好的,但是建议不要遗漏官方文档的学习。因为初学者很难去鉴定一个非官方文档的质量,运气不好的话,还会被误导。而且官方文档最贴近原作者的想法,我们更容易体会到其设计思想。

本文就到此为止啦,本人水平和经验有限,如有纰漏,欢迎指正。如果觉得不错,也欢迎点zan、转发和留言,你们的支持,将会是我持续写作的动力,谢谢各位的阅读。

演示代码地址

https://github.com/momopig/simplicity/tree/master/06:layout

觉得本文对你有帮助?请分享给更多人

关注「前端大全」,提升前端技能

淘口令:复制以下红色内容,再打开手淘即可购买

范品社,使用¥极客T恤¥抢先预览(长按复制整段文案,打开手机淘宝即可进入活动内容)

libin1991 commented 6 years ago

说一下box-sizing的应用场景

  1. box-sizing的属性值分为两个,border-box和content-box,其中,
    border-box:width=content+padding+border
    content-box: width=content

应用场景
2. border-box属性在form上的使用
当我们在要做一个登陆页面的时候,这时候就需要表单和按钮这些元素
首先我们在div中设置两个表单,一个用来输入用户名,一个用来输入密码,同时还有一个登录按钮, 当我们想让这两个表单和一个登录按钮的长度相同时,我们试着把她们三个的width的值设置为100%
但她们的长度并不一致,表单和按钮的padding,border值不统一,这时我们给表单的属性中添加一个box-sizing:border-box,此时表单和按钮的长度保持一致
当不给表单添加box-sizing:border-box时,而是添加一个padding值会发现表单的长度都有所增加
当给按钮添加padding时,她的长度并不会改变,由此可以看出表单默认为content-box,按钮submit默认为border-box,button的默认值也为border-box

  1. border-box属性在盒子中的使用
    当我们设置一个宽度为500px的盒子,在里面放入四个盒子,分别为上(width:100%),中左(width:60%),中右(width:40%),下(width: 100%),此时在大盒子里面四个盒子排列的很整齐
    这是我们给上盒子设置一个padding或者border为5px,这时上盒子的长度就会超出大盒子的宽度,此时我们给上盒子添加box-sizing:border-box,就可以使他的宽度不超出

  2. 说到这里你应该就能看到,box-sizing:border-box这个属性值可以使dom元素的padding和border属性值作用于自身,而不对同级的兄弟元素造成影响

参考链接 box-sizing的使用场景

说一下你了解的弹性flex布局

  1. flex布局是什么?
    flex布局意为弹性布局,任何一个容器都可以指定为flex布局(display: flex),行内元素也可以使用flex布局(display: inline-flex),webkit内核的浏览器,必须加上(diaplay: -webkit-flex),设为flex布局后,子元素的float,clear,vertical-align属性将失效

  2. 基本概念
    采用flex布局的元素,成为flex容器,她的所有子元素自动成为容器成员,称为flex项目
    容器默认存在两根轴,水平的主轴(main axis)和垂直的交叉轴(cross axis),默认沿主轴排列,单个项目占据的主轴空间叫做main size

  3. 容器的属性
    这6个属性设置在容器上,flex-direction,flex-wrap, flex-flow, justify-content, align-items, align-content

    • flex-direction决定主轴的方向,即项目排列的方向
      flex-direction:row | row-reserve | column | column-reserve

    • flex-wrap决定项目在一条轴线上排不下时,如何换行
      flex-wrap: nowrap | wrap | wrap-reserve

    • flex-flow是flex-direction属性和flex-wrap属性的简写,默认为row nowrap

    • justify-content决定项目在主轴上的对齐方式
      justify-content: flex-start | flex-end | center | space-between | space-around

    • align-items决定项目在交叉轴上如何对齐
      align-items:flex-start | flex-end | center | baseline | stretch

    • ailgn-content决定多根轴线的对齐方式,如果项目只有一根轴线,该属性不起作用
      align-content: flex-start | flex-end | center | space-between | space-around | stretch

  4. 项目的属性 这6个属性设置在项目上,order,flex-grow,flex-shrink,flex-basis,flex,algin-self

    • order定义项目的排列顺序,数值越小,排列越靠前,默认为0
      order:<integer>

    • flex-grow定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
      flex-grow:<number>
      如果所有项目的flex-grow属性都为1,则他们将等分剩余空间(如果有的话)

    • flex-shrink定义项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
      flex-shrink: <number>
      如果一个项目的flex-shrink属性为0,其他项目为1,则空间不足时,前者不缩小

    • flex-basis定义项目在分配多余空间之前,项目占据的主轴空间,默认为auto,可以设置为width或height一样的值
      flex-basis:<length> | auto

    • flex属性是flex-grow,flex-shrink,flex-basis的简写,默认为0 1 auto
      默认存在两个快捷值auto(1,1,auto)和none(0,0,auto)

    • align-self允许单个项目和其他项目不一样的对齐方式,可覆盖align-items属性,默认值为auto
      align-self:auto | flex-start | flex-end | center | baseline | stretch

参考链接 flex布局教程:语法篇
参考链接 flex布局教程:实战篇

说一下一个未知宽高元素怎么上下左右垂直居中

  1. position+absolute
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
  1. flex
display: flex;
align-items: center;
justify-content: center;
  1. table-cell
display: table;(父)

display: table-cell;(子)
text-align: center;
vertical-align: middle;
  1. position+margin
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;

参考链接 未知宽高元素水平居中

说一下原型链,对象,构造函数之间的一些联系

  1. javascript函数的new关键字到底是干什么的
    不用创建临时对象,因为new会帮你做
    不用绑定原型,因为new会帮你做
    不用return临时对象,因为new会帮你做
    不要给原型想名字了,因为new指定名字为prototype

  2. 对象与原型链(__proto__和prototype)
    每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法
    对象__proto__属性的值就是他所对应的原型对象
    只有函数才有prototype属性,当你创建函数时,JS会为这个函数自动添加prototype属性,值是一个有constructor属性的对象,不是空对象,而一旦你把这个函数当作构造函数调用时,那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法
    对象的__proto__指向自己构造函数的prototype
    Object.prototype是原型链的顶端,Object本身是构造函数,继承了Function.prototype,Function也是对象,继承了Object.prototype
    Object.prototype.proto === null,说明原型链到Object.prototype终止
    null表示‘没有对象’,即此处不该有值
    Function本身就是函数,Function.__proto__是标准的内置对象Function.prototype,而Function.prototype.__proto__是标准的内置对象Object.prototype

  3. 构造函数和原型
    原型:每一个JS对象(除null外)在创建的时候就会与之关联另一个对象,这个对象就是我们说的原型,每个对象都会从原型继承属性
    proto:每一个JS对象(除null外)都具有的一个属性,叫__proto__,这个属性会指向该对象的原型
    constructor:每一个原型都有一个constructor属性指向关联的构造函数

参考链接 js的new到底是干什么的
参考链接 从__proto__和prototype来深入理解JS对象和原型链
参考链接 javascript深入之从原型到原型链
参考链接 从探究Function.proto===Function.prototype过程中的一些收获

DOM事件绑定的几种方式

  1. html中直接绑定
    html中绑定事件叫做内联绑定事件,不利于分离

  2. js中直接绑定
    js中直接绑定称为赋值绑定函数,缺点是只能绑定一次

  3. addEventListener
    target.addEventListener(type, listener[, useCapture])
    target表示要监听事件的目标对象,可以是一个文档上的元素DOM本身,Window或者XMLHttpRequest
    type表示事件类型的字符串
    listener为当指定的事件类型发生时被通知到的一个对象
    useCapture为设置事件的捕获或者冒泡
    true为捕获,false为冒泡(默认)
    addEventListener可以给同一个dom元素绑定多个函数,并且执行顺序按照绑定顺序执行,且执行顺序与useCapture无关
    给一个dom元素绑定同一个函数,最多只能绑定useCapture类型不同的两次
    addEventListener只支持到IE9,为兼容性考虑,在兼容IE8及一下浏览器可以用attachEvent函数,和addEventListener函数表现一样,但它绑定函数的this会指向全局

  4. 事件的解绑
    通过dom的on***属性设置的事件,可以用dom.onclick = null来解绑
    通过addEventListener绑定的事件可以使用removeEventListener来解绑,接受参数一样
    对于使用removeEventListener函数解绑事件,需要传入的listener,useCapture和addEventListener完全一致才可以解绑事件

  5. 事件冒泡
    事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点

  6. 事件捕获
    事件捕获的思想是不太具体的DOM节点应该更早接收到事件,而最具体的节点应该最后接收到事件,与事件冒泡顺序相反

  7. DOM事件流
    DOM事件流包括三个阶段,事件捕获阶段,处于目标阶段,事件冒泡阶段,首先发生的是事件捕获,为截获事件提供机会,然后是实际的目标接受事件,最后一个阶段是事件冒泡阶段,可以在这个阶段对事件作出响应

  8. stopPropagation()和stopImmediatePropagation()
    stopPropagation()既可以阻止事件冒泡,也可以阻止事件捕获,也可以阻止处于目标阶段
    stopImmediatePropagation()既可以阻止事件冒泡,也可以阻止事件捕获,还会阻止该元素其他事件的发生

参考链接 从一个事件绑定说起-DOM

有没有了解http2,websocket,https,说一下你的理解以及你了解的特性

  1. http2.0和http1.1的区别

    • 多路复用
      多路复用允许单一的http2连接同时发起多重的请求-响应信息
      http性能优化的关键并不在于高带宽,而是低延迟,TCP连接会随着时间进行自我调谐,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度,这种调谐则称之为TCP慢启动,由于这种原因,让原本就具有突发性和短时性的http连接变得十分低效
      http2通过让所有数据流共用同一个连接,可以更有效的使用TCP连接,让高带宽也能真正服务于http的性能提升
      小总结:多路复用技术,单连接多资源的方式,减少服务端的链接压力,内存占用更少,连接吞吐量更大,由于减少TCP慢启动时间,提高传输的速度

    因为所有的http2的请求都在一个TCP连接上,所以在http1中的自动化合并文件和Sprite合图等资源合并减少请求的优化手段对于http2来说是没有效果的

    • 二进制分帧
      http2在应用层和传输层之间增加一个二进制分帧层,http2会将所有传输的信息分割成更小的消息和帧,并对他们采用二进制格式的编码,其中http1的首部信息会被封装成Headers帧,而我们的request body则封装到Data帧里面

    • 首部压缩
      http请求和响应都是由状态行,请求/响应头部,消息主题三部分组成,一般而言,消息主题都会经过gzip压缩,或者本身传输的就是压缩后的二进制文件,但状态行和头部却没有经过任何压缩,直接以纯文本传输,浪费流量资源
      原理:头部压缩需要在支持http2的浏览器和服务端之间,维护一份相同的静态字典,包含常见的头部名称与值的组合,维护一份相同的动态字典,可以动态的添加内容,支持基于静态哈夫曼码表的哈夫曼编码

    • http2支持服务器推送
      服务端推送是一种在客户端请求之前发送数据的机制,当代网页使用了许多资源:html,样式表,脚本等,在http1.x中这些资源每一个都必须明确的请求,这可能是一个很慢的过程,因为服务器必须等待浏览器做的每一个请求,网络经常是空闲和未充分使用的
      为了改善延迟,http2引入了server push,它允许服务端推送资源给浏览器,在浏览器明确请求之前,一个服务器经常知道一个页面需要更多的附加资源,在他响应浏览器第一个请求时,可以开始推送这些资源,这允许服务端去完全充分利用一个可能空闲的网络,改善页面加载的时间
      有了http2的服务端推送,http1时代的内嵌资源的优化手段也变得没有意义了,使用服务端推送更高效,因为客户端可以缓存起来,甚至可以不同页面之间共享

    • 并行双向字节流的请求和响应
      在http2上,客户端和服务端可以把http消息分解成回不依赖的帧,然后乱序发送,最后再在另一端把她们重新组合起来,同一链接上可以有多个不同方向上的数据在传输,客户端可以一边乱序发送stream,也可以一边接收着服务端的响应,在服务端同理
      把http消息分解为独立的帧,交错发送,然后在另一端重新组装是http2最重要的一项增强,这个机制会在整个web技术栈中引发一系列的连锁反应,从而带来巨大的性能提升,因为

      1. 可以并行交错的发送请求,请求之间互不影响
      2. 可以并行交错的发送响应,响应之间互不干扰
      3. 只使用同一个连接即可并行的发送多个请求和响应
      4. 消除不必要的延迟,从而减少页面加载的时间
        也就是说‘域名分区’的优化手段对于http2来说是无用的,因为资源都是并行交错发送,且没有限制,不需要额外的多域名并行下载
    • http2的请求优先级
      每个http2流里面有个优先值,这个优先值确定着客户端和服务端处理不同的流采取不同的优先级策略,高优先级的流应该优先发送,但又不是绝对的准守,可能又会引入首队阻塞的问题,高优先级的请求慢导致阻塞其他文件的交付,分配处理资源和客户端与服务器间的带宽,不同优先级的混合是必须的

  2. https
    http协议传输的数据都是未加密的,也就是明文的,因此使用http协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL协议用于对http协议传输的数据进行加密,从而诞生了https,现在的https使用的都是TSL协议

    https在传输数据之前需要客户端和服务端之间进行一次握手,在握手的过程中将确立双方加密传输数据的密码信息,TSL/SSL协议不仅仅是一套加密传输的协议,TSL/SSL中使用了非对称加密,对称加密以及hash算法

    握手过程:

    • 浏览器将自己支持的一套加密规则发送给网站

    • 网站从中选出一组加密算法和hash算法,并将自己的身份信息以证书的形式发回给浏览器,证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息

    • 获得网站证书后浏览器要做以下工作

      1. 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致)如果证书受信任,则浏览器栏里会显示一个小锁头,否则会给出证书不受信的提示
      2. 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密
      3. 使用约定好的hash计算握手信息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站
    • 网站接收浏览器发来的数据之后要做以下工作

      1. 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手信息,并验证hash是否与浏览器发来的一致
      2. 使用密码加密一段握手信息,发送给浏览器
    • 浏览器解密并计算握手信息的hash,如果与服务端发来的hash一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密

    这里浏览器与网站互相发送加密的握手信息并验证,目的是为了保证双发都获得了一致的密码,并且可以正常的加密解密数据

    其中非对称加密算法用于在握手过程中加密生成的密码,对称加密算法用于对真正传输的数据进行加密,而hash算法用于验证数据的完整性

    由于浏览器生成的密码是整个数据加密的关键,因此在传输的时候使用了非对称加密算法对其进行加密,非对称加密算法会生成公钥和私钥,公钥只能用于加密数据,因此可以随意传输,而网站的私钥用于对数据进行解密,所以网站都会非常小心的保管自己的私钥,防止泄漏

    TSL握手的过程中如果有任何错误,都会使加密连接断开,从而阻止了隐私数据的传输,正是由于https非常的安全,攻击者无法从中找到下手的地方,于是更多的是采用了假证书的手法来欺骗客户端,从而获取明文信息

  3. webSocket概述
    http协议是一种无状态的协议,要实现有状态的会话必须借助一些外部机制如session和cookie,这或多或少或带来一些不便,尤其是服务端和客户端需要实时交换数据的时候
    webSocket允许服务器和客户端进行全双工通信,传统的http是单工通信的,它只允许客户端向服务端发出请求,服务端被动返回数据,而不能主动向客户端传递数据
    webSocket的请求头部

    Connection: Upgrade             //通信协议提升
    Upgrade: websocket              //传输协议升级为websocket
    Sec-WebSocket-Key: **********   //握手协议密钥,base64位编码的16字节的随机字符串
    

    webSocket的响应头部

    Connection: Upgrade                 //通信协议提升
    Upgrade: websocket                  //传输协议升级为websocket
    Sec-WebSocket-Accept: **********    //将客户上报的Sec-WebSocket-Key和一段GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)进行拼接,再将这个拼接的字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,最后再返回给客户端
    

    WebSocket,ajax轮询和long poll

参考链接 http2.0协议你应该准备的面试题
参考链接 面试时如何优雅的谈论http
参考链接 http2.0的奇妙日常 参考链接 浅谈WebSocket协议及其实现

  1. 说说对洗牌算法的理解和如何验证其正确性
    洗牌算法之前没了解过,刚面到的时候好蒙,闲话不多说,这里说下洗牌算法的js实现

    Fisher-Yates
    这是最经典的洗牌算法,其算法思想是从原数组中随机抽取一个新的元素到新数组中

    从还没处理的数组(假如还剩n个)中,产生一个[0,n]之间的随机数random

    从剩下的n个元素中把第random个元素取出到新数组中

    删除原数组第random个元素

    重复第2 3步直到所有的元素取完

    最终返回一个新的打乱的数组

    代码实现

    function shufle(arr){
        var result = [],
            random;
        while(arr.length > 0){
            random = Math.floor(Math.random() * arr.length);
            result.push(arr[random])
            arr.splice(random, 1)
        }
        return result;
    }
    

    这种算法的时间复杂度是O(n2)

参考链接 洗牌算法的js实现
参考链接 Fisher–Yates shuffle 洗牌算法

  1. 说一下你对事件委托和事件代理的理解?
    什么是事件委托?它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件,当我们需要对很多元素添加事件的时候,可以通过事件添加到他们的父节点二将时间委托给父节点来触发处理函数

    为什么要使用事件委托?
    一般来说,dom需要有事件处理程序,我们都会直接给它设置事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们这里有100个li,每个li都有相同的click事件,那么我们会用for循环的方法来遍历所有的li,然后给他们添加事件,那么这样会存在什么问题呢?
    在JavaScript中,添加到页面上的事件处理程序的数量将直接关联到页面整体的运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数就越多,就会延长整个页面交互就绪时间,这就是为什么性能优化的主要思想是减少dom操作的原因,如果使用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能
    每个函数都是一个对象,是对象就会占用内存,内存占用率就越大,自然性能就差了,比如上面的100个li,就要占用100个内存空间,如果是1000个,10000个呢,如果使用事件委托,那么我们就可以只对它的父级这一个对象(如果只有一个父级)进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,自然性能就会更好

    事件委托的原理?
    事件委托是利用事件的冒泡机制来实现的,何为事件冒泡呢?这里介绍下浏览器dom事件处理的过程,dom2.0模型将事件流程分为三个阶段:事件捕获阶段,事件目标阶段,事件冒泡阶段。
    事件捕获:当某个元素触发某个事件,顶层对象document就会发出一个事件流,随着dom树的节点向目标元素节点流去,直到到达事件真正发生的目标元素,在这个过程中,事件相应的监听函数是不会被触发的
    事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数,如果没有绑定监听函数,那就不执行
    事件冒泡:从目标元素开始,往顶层元素传播,途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发,如果想阻止事件冒泡,可以使用event.stopPropgation()或者event.cancelBubble=true来阻止事件的冒泡传播

    事件委托怎么实现:
    Event对象提供了一个属性叫target,可以返回事件的目标节点,我们称为事件源,也就是说,target就可以表示为当前事件操作的dom,但是不是真正操作的dom,当然,这个是有兼容性的,标准浏览器用event.target,IE浏览器用event.srcElement,此时知识获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名,这个返回的是一个大写的,一般转化为小写再进行比较
    如果你想将事件委托给父元素来处理,但每个子元素的事件内容又不相同时,这里我们可以给每个子元素添加一个唯一的key来作标识,然后在父元素中对其进行分别的处理

    const list = document.querySelector('#list)  
    const lists = list.querySelector('#list > li')
    for(let i=0; i<lists.length; i++){
        lists[i].dataset.key = 'list-' + i
    }
    list.addEventListener('click',function(e){
        const event = e || window.event
        const target = event.target || event.srcElement  
        if(target.nodeName.toLocaleLowerCase() === 'li'){
            switch(target.dataset.key){
                case 'list-1':
                    do something-1
                    break
                case 'list-2':
                    do something-2
                    break
                ...
                default:
                    do something-3
                    break
            }
        }
    })  
    

参考链接 JavaScrip事件代理和委托
参考链接 JS的事件委托和事件代理详解

  1. 说一下你对css重绘和重排的理解,两个有什么不同?
    这一题考的其实是网页性能的问题,我们先理解下对网页性能产生影响到因素,了解下网页是怎么生成的

    网页生成的过程,大致可以分为五步:

    • html代码转化为dom
    • css代码转化为cssom
    • 结合dom和cssom,生成一棵渲染树(包含每个节点的视觉信息)
    • 生成布局layout,即将所有的渲染树的节点进行平面合成
    • 将布局绘制paint在屏幕上

    在这五步里面,第一步和第三步都非常快耗时的是第四步和第五步
    其中生成布局flow和绘制paint这两步,合称为渲染render

    重绘和重排
    网页生成的时候,至少会渲染一次,用户访问的过程中,还会不断的重新渲染
    以下三种情况,会导致网页重新渲染

    • 修改dom
    • 修改样式表
    • 用户事件

    重新渲染,就需要重新生成布局和重新绘制,前者叫做重排reflow,后者交货重绘repaint
    需要注意的是,重绘不一定需要重排,比如改变某个网页元素的颜色,就只会触发重绘,不会触发重排,因为布局没有改变,但是,重排一定会导致重绘,比如改变一个网页元素的位置,就会同时触发重排和重绘,因为布局改变了

    对于性能的影响
    重绘和重排会不断触发,这是不可避免的,但是,他们是非常耗费资源的,是导致网页性能低下根本原因
    要提高网页性能,就是要降低重排和重绘的频率和成本,尽量少触发重新渲染

    • 一般来说,样式的写操作之后,如果有下面这些属性的读操作,都会引发浏览器立即重新渲染
      • offsetTop/offsetLeft/offsetWidth/offsetHeight
      • scrollTop/scrollLeft/scrollWidth/scrollHeight
      • clientTop/clientLeft/clientWidth/clientHeight
      • getComputeStyle()
    • 所以,从性能角度考虑,尽量不要把读操作和写操作,放在一个语句里,一般的规则是
      • 样式表越简单,重绘和重排就越快
      • 重排和重绘的dom层级越高,成本就越高
      • table元素的重绘和重排成本,要高于div元素

    提高性能的九个技巧

    • dom的多个读操作(或者多个写操作),应该放在一起,不要两个读操作之间,加入一个写操作
    • 如果某个样式是通过重排得到的,那么最好缓存结果,避免下一次用到的时候,浏览器又要重排
    • 不要一条条的改变样式,而是要通过改变class,或者csstext属性,一次性的改变样式
    • 尽量使用离线dom,而不是真正的网页dom,来改变元素样式,比如,使用cloneNode()方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点
    • 先将原素设为display:none(需要一次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要一次重排和重绘),这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染
    • position属性为absolute或fixed的元素,重排的开销会比较小,因为用考虑他对其他元素的影响
    • 只有必要的时候,才会将元素的display属性设为课件,因为不可见的元素不影响重排和重绘,另外visibility:hidden的元素只会对重绘有影响,不会影响重排
    • 使用虚拟dom的脚本库,如react
    • 使用window.requestAnimationFrame(),window.requestIdleCallback()这两个方法调节重新渲染

参考链接 网页性能管理详解

因为懒而不想写的部分

  1. ajax原生实现
  2. ajax readyState值理解
  3. Event loop事件循环理解
  4. js事件循环机制
  5. react+redux理解
  6. js执行顺序
  7. 强缓存和协商缓存
  8. http缓存机制
  9. http缓存小结

大概就这些吧,从年后初八面试到现在,磕磕碰碰花了挺多精力的,前段时间过了腾讯三面,让我等hr电话,欣喜若狂的以为能进鹅厂了,等了一个多礼拜,杳无音信,到处找人问,得到的结果是等四月中下旬的校招再通知我面试,瞬间一盆冷水浇下来,我基础比较差,算法功底也不强,自知够不上大厂的门,认了,自己本事不够,怨不得别人,这段时间在v2ex这个社区收获挺多的,写篇自己的面经给大家看看,各位都是大佬,有什么写错的地方,多多指教,不喜勿喷,谢谢