共计 12551 个字符,预计需要花费 32 分钟才能阅读完成。
浏览器的次要组成部分
- ⽤户界⾯ 包含地址栏、后退 / 后退按钮、书签菜单等。除了浏览器主窗⼝显示的您申请的⻚⾯外,其余显示的各个局部都属于⽤户界⾯。
- 浏览器引擎 在⽤户界⾯和出现引擎之间传送指令。
- 出现引擎 负责显示申请的内容。如果申请的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
- ⽹络 ⽤于⽹络调⽤,⽐如 HTTP 申请。其接⼝与平台⽆关,并为所有平台提供底层实现。
- ⽤户界⾯后端 ⽤于绘制根本的窗⼝⼩部件,⽐如组合框和窗⼝。其公开了与平台⽆关的通⽤接⼝,⽽在底层使⽤操作系统的⽤户界⾯⽅法。
- JavaScript 解释器。⽤于解析和执⾏ JavaScript 代码。
- 数据存储 这是长久层。浏览器须要在硬盘上保留各种数据,例如 Cookie。新的 HTML 标准 (HTML5) 定义了“⽹络数据库”,这是⼀个残缺(然而轻便)的浏览器内数据库。
值得注意的是,和⼤少数浏览器不同,Chrome 浏览器的每个标签⻚都别离对应⼀个出现引擎实例。每个标签⻚都是⼀个独⽴的过程。
—- 问题知识点分割线 —-
z-index 属性在什么状况下会生效
通常 z-index 的应用是在有两个重叠的标签,在肯定的状况下管制其中一个在另一个的上方或者下方呈现。z-index 值越大就越是在下层。z-index 元素的 position 属性须要是 relative,absolute 或是 fixed。
z-index 属性在下列状况下会生效:
- 父元素 position 为 relative 时,子元素的 z -index 生效。解决:父元素 position 改为 absolute 或 static;
- 元素没有设置 position 属性为非 static 属性。解决:设置该元素的 position 属性为 relative,absolute 或是 fixed 中的一种;
- 元素在设置 z -index 的同时还设置了 float 浮动。解决:float 去除,改为 display:inline-block;
—- 问题知识点分割线 —-
Ajax
它是一种异步通信的办法,通过间接由 js 脚本向服务器发动 http 通信,而后依据服务器返回的数据,更新网页的相应局部,而不必刷新整个页面的一种办法。
面试手写(原生):
//1:创立 Ajax 对象
var xhr = window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');// 兼容 IE6 及以下版本
//2:配置 Ajax 申请地址
xhr.open('get','index.xml',true);
//3:发送申请
xhr.send(null); // 谨严写法
//4: 监听申请,承受响应
xhr.onreadysatechange=function(){if(xhr.readySate==4&&xhr.status==200 || xhr.status==304)
console.log(xhr.responsetXML)
}
jQuery 写法
$.ajax({
type:'post',
url:'',
async:ture,//async 异步 sync 同步
data:data,// 针对 post 申请
dataType:'jsonp',
success:function (msg) { },
error:function (error) {}})
promise 封装实现:
// promise 封装实现:function getJSON(url) {
// 创立一个 promise 对象
let promise = new Promise(function(resolve, reject) {let xhr = new XMLHttpRequest();
// 新建一个 http 申请
xhr.open("GET", url, true);
// 设置状态的监听函数
xhr.onreadystatechange = function() {if (this.readyState !== 4) return;
// 当申请胜利或失败时,扭转 promise 的状态
if (this.status === 200) {resolve(this.response);
} else {reject(new Error(this.statusText));
}
};
// 设置谬误监听函数
xhr.onerror = function() {reject(new Error(this.statusText));
};
// 设置响应的数据类型
xhr.responseType = "json";
// 设置申请头信息
xhr.setRequestHeader("Accept", "application/json");
// 发送 http 申请
xhr.send(null);
});
return promise;
}
—- 问题知识点分割线 —-
0.1 + 0.2 === 0.3 嘛?为什么?
JavaScript 应用 Number 类型来示意数字(整数或浮点数),遵循 IEEE 754 规范,通过 64 位来示意一个数字(1 + 11 + 52)
- 1 符号位,0 示意负数,1 示意正数 s
- 11 指数位(e)
- 52 尾数,小数局部(即有效数字)
最大平安数字:Number.MAX_SAFE_INTEGER = Math.pow(2, 53) – 1,转换成整数就是 16 位,所以 0.1 === 0.1,是因为通过 toPrecision(16) 去无效位之后,两者是相等的。
在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会产生有限循环,而后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度失落。
所以总结:精度失落可能呈现在进制转换和对阶运算中
—- 问题知识点分割线 —-
过程与线程的概念
从实质上说,过程和线程都是 CPU 工作工夫片的一个形容:
- 过程形容了 CPU 在运行指令及加载和保留上下文所需的工夫,放在利用上来说就代表了一个程序。
- 线程是过程中的更小单位,形容了执行一段指令所需的工夫。
过程是资源分配的最小单位,线程是 CPU 调度的最小单位。
一个过程就是一个程序的运行实例。具体解释就是,启动一个程序的时候,操作系统会为该程序创立一块内存,用来寄存代码、运行中的数据和一个执行工作的主线程,咱们把这样的一个运行环境叫 过程 。 过程是运行在虚拟内存上的,虚拟内存是用来解决用户对硬件资源的有限需要和无限的硬件资源之间的矛盾的。从操作系统角度来看,虚拟内存即交换文件;从处理器角度看,虚拟内存即虚拟地址空间。
如果程序很多时,内存可能会不够,操作系统为每个过程提供一套独立的虚拟地址空间,从而使得同一块物理内存在不同的过程中能够对应到不同或雷同的虚拟地址,变相的减少了程序能够应用的内存。
过程和线程之间的关系有以下四个特点:
(1)过程中的任意一线程执行出错,都会导致整个过程的解体。
(2)线程之间共享过程中的数据。
(3)当一个过程敞开之后,操作系统会回收过程所占用的内存, 当一个过程退出时,操作系统会回收该过程所申请的所有资源;即便其中任意线程因为操作不当导致内存透露,当过程退出时,这些内存也会被正确回收。
(4)过程之间的内容互相隔离。 过程隔离就是为了使操作系统中的过程互不烦扰,每一个过程只能拜访本人占有的数据,也就避免出现过程 A 写入数据到过程 B 的状况。正是因为过程之间的数据是严格隔离的,所以一个过程如果解体了,或者挂起了,是不会影响到其余过程的。如果过程之间须要进行数据的通信,这时候,就须要应用用于过程间通信的机制了。
Chrome 浏览器的架构图:从图中能够看出,最新的 Chrome 浏览器包含:
- 1 个浏览器主过程
- 1 个 GPU 过程
- 1 个网络过程
- 多个渲染过程
- 多个插件过程
这些过程的性能:
- 浏览器过程:次要负责界面显示、用户交互、子过程治理,同时提供存储等性能。
- 渲染过程:外围工作是将 HTML、CSS 和 JavaScript 转换为用户能够与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该过程中,默认状况下,Chrome 会为每个 Tab 标签创立一个渲染过程。出于平安思考,渲染过程都是运行在沙箱模式下。
- GPU 过程:其实,GPU 的应用初衷是为了实现 3D CSS 的成果,只是随后网页、Chrome 的 UI 界面都抉择采纳 GPU 来绘制,这使得 GPU 成为浏览器广泛的需要。最初,Chrome 在其多过程架构上也引入了 GPU 过程。
- 网络过程:次要负责页面的网络资源加载,之前是作为一个模块运行在浏览器过程外面的,直至最近才独立进去,成为一个独自的过程。
- 插件过程:次要是负责插件的运行,因插件易解体,所以须要通过插件过程来隔离,以保障插件过程解体不会对浏览器和页面造成影响。
所以,关上一个网页,起码须要四个过程:1 个网络过程、1 个浏览器过程、1 个 GPU 过程以及 1 个渲染过程。如果关上的页面有运行插件的话,还须要再加上 1 个插件过程。
尽管多过程模型晋升了浏览器的稳定性、流畅性和安全性,但同样不可避免地带来了一些问题:
- 更高的资源占用:因为每个过程都会蕴含公共根底构造的正本(如 JavaScript 运行环境),这就意味着浏览器会耗费更多的内存资源。
- 更简单的体系架构:浏览器各模块之间耦合性高、扩展性差等问题,会导致当初的架构曾经很难适应新的需要了。
—- 问题知识点分割线 —-
对事件委托的了解
(1)事件委托的概念
事件委托实质上是利用了 浏览器事件冒泡 的机制。因为事件在冒泡过程中会上传到父节点,父节点能够通过事件对象获取到指标节点,因而能够把子节点的监听函数定义在父节点上,由父节点的监听函数对立解决多个子元素的事件,这种形式称为事件委托(事件代理)。
应用事件委托能够不必要为每一个子元素都绑定一个监听事件,这样缩小了内存上的耗费。并且应用事件代理还能够实现事件的动静绑定,比如说新增了一个子节点,并不需要独自地为它增加一个监听事件,它绑定的事件会交给父元素中的监听函数来解决。
(2)事件委托的特点
- 缩小内存耗费
如果有一个列表,列表之中有大量的列表项,须要在点击列表项的时候响应一个事件:
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
如果给每个列表项一一都绑定一个函数,那对于内存耗费是十分大的,效率上须要耗费很多性能。因而,比拟好的办法就是把这个点击事件绑定到他的父层,也就是 ul 上,而后在执行事件时再去匹配判断指标元素,所以事件委托能够缩小大量的内存耗费,节约效率。
- 动静绑定事件
给上述的例子中每个列表项都绑定事件,在很多时候,须要通过 AJAX 或者用户操作动静的减少或者去除列表项元素,那么在每一次扭转的时候都须要从新给新增的元素绑定事件,给行将删去的元素解绑事件;如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和指标元素的增减是没有关系的,执行到指标元素是在真正响应执行事件函数的过程中去匹配的,所以应用事件在动静绑定事件的状况下是能够缩小很多反复工作的。
// 来实现把 #list 下的 li 元素的事件代理委托到它的父层元素也就是 #list 上:// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
// 兼容性解决
var event = e || window.event;
var target = event.target || event.srcElement;
// 判断是否匹配指标元素
if (target.nodeName.toLocaleLowerCase === 'li') {console.log('the content is:', target.innerHTML);
}
});
在上述代码中,target 元素则是在 #list 元素之下具体被点击的元素,而后通过判断 target 的一些属性(比方:nodeName,id 等等)能够更准确地匹配到某一类 #list li 元素之上;
(3)局限性
当然,事件委托也是有局限的。比方 focus、blur 之类的事件没有事件冒泡机制,所以无奈实现事件委托;mousemove、mouseout 这样的事件,尽管有事件冒泡,然而只能一直通过地位去计算定位,对性能耗费高,因而也是不适宜于事件委托的。
当然事件委托不是只有长处,它也是有 毛病 的,事件委托会影响页面性能,次要影响因素有:
- 元素中,绑定事件委托的次数;
- 点击的最底层元素,到绑定事件元素之间的
DOM
层数;
在必须应用事件委托的中央,能够进行如下的解决:
- 只在必须的中央,应用事件委托,比方:
ajax
的部分刷新区域 - 尽量的缩小绑定的层级,不在
body
元素上,进行绑定 - 缩小绑定的次数,如果能够,那么把多个事件的绑定,合并到一次事件委托中去,由这个事件委托的回调,来进行散发。
—- 问题知识点分割线 —-
为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
arguments
是一个对象,它的属性是从 0 开始顺次递增的数字,还有 callee
和length
等属性,与数组类似;然而它却没有数组常见的办法属性,如 forEach
, reduce
等,所以叫它们类数组。
要遍历类数组,有三个办法:
(1)将数组的办法利用到类数组上,这时候就能够应用 call
和apply
办法,如:
function foo(){Array.prototype.forEach.call(arguments, a => console.log(a))
}
(2)应用 Array.from 办法将类数组转化成数组:
function foo(){const arrArgs = Array.from(arguments)
arrArgs.forEach(a => console.log(a))
}
(3)应用开展运算符将类数组转化成数组
function foo(){const arrArgs = [...arguments]
arrArgs.forEach(a => console.log(a))
}
—- 问题知识点分割线 —-
代码输入后果
function fn1(){console.log('fn1')
}
var fn2
fn1()
fn2()
fn2 = function() {console.log('fn2')
}
fn2()
输入后果:
fn1
Uncaught TypeError: fn2 is not a function
fn2
这里也是在考查变量晋升,关键在于第一个 fn2(),这时 fn2 仍是一个 undefined 的变量,所以会报错 fn2 不是一个函数。
—- 问题知识点分割线 —-
label 的作用是什么?如何应用?
label 标签来定义表单控件的关系:当用户抉择 label 标签时,浏览器会主动将焦点转到和 label 标签相干的表单控件上。
- 应用办法 1:
<label for="mobile">Number:</label>
<input type="text" id="mobile"/>
- 应用办法 2:
<label>Date:<input type="text"/></label>
—- 问题知识点分割线 —-
LRU 算法
实现代码如下:
// 一个 Map 对象在迭代时会依据对象中元素的插入程序来进行
// 新增加的元素会被插入到 map 的开端,整个栈倒序查看
class LRUCache {constructor(capacity) {this.secretKey = new Map();
this.capacity = capacity;
}
get(key) {if (this.secretKey.has(key)) {let tempValue = this.secretKey.get(key);
this.secretKey.delete(key);
this.secretKey.set(key, tempValue);
return tempValue;
} else return -1;
}
put(key, value) {
// key 存在,仅批改值
if (this.secretKey.has(key)) {this.secretKey.delete(key);
this.secretKey.set(key, value);
}
// key 不存在,cache 未满
else if (this.secretKey.size < this.capacity) {this.secretKey.set(key, value);
}
// 增加新 key,删除旧 key
else {this.secretKey.set(key, value);
// 删除 map 的第一个元素,即为最长未应用的
this.secretKey.delete(this.secretKey.keys().next().value);
}
}
}
// let cache = new LRUCache(2);
// cache.put(1, 1);
// cache.put(2, 2);
// console.log("cache.get(1)", cache.get(1))// 返回 1
// cache.put(3, 3);// 该操作会使得密钥 2 作废
// console.log("cache.get(2)", cache.get(2))// 返回 -1 (未找到)
// cache.put(4, 4);// 该操作会使得密钥 1 作废
// console.log("cache.get(1)", cache.get(1))// 返回 -1 (未找到)
// console.log("cache.get(3)", cache.get(3))// 返回 3
// console.log("cache.get(4)", cache.get(4))// 返回 4
—- 问题知识点分割线 —-
为什么要有 WebSocket
曾经有了被广泛应用的 HTTP 协定,为什么要再出一个 WebSocket 呢?它有哪些益处呢?
其实 WebSocket 与 HTTP/2 一样,都是为了解决 HTTP 某方面的缺点而诞生的。HTTP/2 针对的是“队头阻塞”,而
WebSocket 针对的是“申请 - 应答”通信模式
。
那么,“申请 – 应答”有什么不好的中央呢?
- “申请 – 应答”是一种“半双工”的通信模式,尽管能够双向收发数据,但同一时刻只能一个方向上有动作,传输效率低。更要害的一点,它是一种“被动”通信模式,服务器只能“被动”响应客户端的申请,无奈被动向客户端发送数据。
- 尽管起初的 HTTP/2、HTTP/3 新增了 Stream、Server Push 等个性,但“申请 – 应答”仍然是次要的工作形式。这就导致 HTTP 难以利用在动静页面、即时消息、网络游戏等要求“实时通信”的畛域。
- 在 WebSocket 呈现之前,在浏览器环境里用 JavaScript 开发实时 Web 利用很麻烦。因为浏览器是一个“受限的沙盒”,不能用 TCP,只有 HTTP 协定可用,所以就呈现了很多“变通”的技术,“轮询”(polling)就是比拟罕用的的一种。
- 简略地说,轮询就是不停地向服务器发送 HTTP 申请,问有没有数据,有数据的话服务器就用响应报文回应。如果轮询的频率比拟高,那么就能够近似地实现“实时通信”的成果。
- 但轮询的毛病也很显著,重复发送有效查问申请消耗了大量的带宽和 CPU 资源,十分不经济。
- 所以,为了克服 HTTP“申请 – 应答”模式的毛病,WebSocket 就“应运而生”了
WebSocket 的特点
- WebSocket 是一个真正“全双工”的通信协议,与 TCP 一样,客户端和服务器都能够随时向对方发送数据
- WebSocket
采纳了二进制帧构造
,语法、语义与 HTTP 齐全不兼容,但因为它的次要运行环境是浏览器,为了便于推广和利用,就不得不“搭便车”,在应用习惯上尽量向 HTTP 聚拢,这就是它名字里“Web”的含意。 - 服务发现方面,WebSocket 没有应用 TCP 的“IP 地址 + 端口号”,而是延用了 HTTP 的 URI 格局,但结尾的协定名不是“http”,引入的是两个新的名字:“ws”和“wss”,别离示意明文和加密的 WebSocket 协定。
WebSocket 的默认端口也抉择了 80 和 443
,因为当初互联网上的防火墙屏蔽了绝大多数的端口,只对 HTTP 的 80、443 端口“放行”,所以 WebSocket 就能够“假装”成 HTTP 协定,比拟容易地“穿透”防火墙,与服务器建设连贯
ws://www.chrono.com
ws://www.chrono.com:8080/srv
wss://www.chrono.com:445/im?user_id=xxx
WebSocket 的握手
和 TCP、TLS 一样,WebSocket 也要有一个握手过程,而后能力正式收发数据。
这里它还是搭上了 HTTP 的“便车”,利用了 HTTP 自身的“协定降级”个性,“假装”成 HTTP,这样就能绕过浏览器沙盒、网络防火墙等等限度,这也是 WebSocket 与 HTTP 的另一个重要关联点。
WebSocket 的握手是一个规范的 HTTP GET 申请,但要带上两个协定降级的专用头字段:
- “Connection: Upgrade”,示意要求协定“降级”;
- “Upgrade: websocket”,示意要“降级”成 WebSocket 协定。
另外,为了避免一般的 HTTP 音讯被“意外”辨认成 WebSocket,握手音讯还减少了两个额定的认证用头字段(所谓的“挑战”,Challenge):
Sec-WebSocket-Key
:一个 Base64 编码的 16 字节随机数,作为简略的认证密钥;Sec-WebSocket-Version
:协定的版本号,以后必须是 13。
服务器收到 HTTP 申请报文,看到下面的四个字段,就晓得这不是一个一般的 GET 申请,而是 WebSocket 的降级申请,于是就不走一般的 HTTP 解决流程,而是结构一个非凡的“101 Switching Protocols”响应报文,告诉客户端,接下来就不必 HTTP 了,全改用 WebSocket 协定通信
小结
浏览器是一个“沙盒”环境,有很多的限度,不容许建设 TCP 连贯收发数据,而有了 WebSocket,咱们就能够在浏览器里与服务器间接建设“TCP 连贯”,取得更多的自在。
不过自在也是有代价的,WebSocket 尽管是在应用层,但应用形式却与“TCP Socket”差不多,过于“原始”,用户必须本人治理连贯、缓存、状态,开发上比 HTTP 简单的多,所以是否要在我的项目中引入 WebSocket 必须慎重考虑。
HTTP
的“申请 – 应答”模式不适宜开发“实时通信”利用,效率低,难以实现动静页面,所以呈现了 WebSocket;WebSocket
是一个“全双工”的通信协议,相当于对 TCP 做了一层“薄薄的包装”,让它运行在浏览器环境里;WebSocket
应用兼容 HTTP 的 URI 来发现服务,但定义了新的协定名“ws”和“wss”,端口号也沿用了80 和 443
;WebSocket
应用二进制帧,构造比较简单,非凡的中央是有个“掩码”操作,客户端发数据必须掩码,服务器则不必;WebSocket
利用 HTTP 协定实现连贯握手,发送 GET 申请要求“协定降级”,握手过程中有个非常简单的认证机制,目标是避免误连贯。
—- 问题知识点分割线 —-
CSS 预处理器 / 后处理器是什么?为什么要应用它们?
预处理器, 如:less
,sass
,stylus
,用来预编译 sass
或者 less
,减少了css
代码的复用性。层级,mixin
,变量,循环,函数等对编写以及开发 UI 组件都极为不便。
后处理器, 如:postCss
,通常是在实现的样式表中依据 css
标准解决 css
,让其更加无效。目前最常做的是给css
属性增加浏览器公有前缀,实现跨浏览器兼容性的问题。
css
预处理器为 css
减少一些编程个性,无需思考浏览器的兼容问题,能够在 CSS
中应用变量,简略的逻辑程序,函数等在编程语言中的一些根本的性能,能够让 css
更加的简洁,减少适应性以及可读性,可维护性等。
其它 css
预处理器语言:Sass(Scss)
, Less
, Stylus
, Turbine
, Swithch css
, CSS Cacheer
, DT Css
。
应用起因:
- 构造清晰,便于扩大
- 能够很不便的屏蔽浏览器公有语法的差别
- 能够轻松实现多重继承
- 完满的兼容了
CSS
代码,能够利用到老我的项目中
—- 问题知识点分割线 —-
== 操作符的强制类型转换规定?
对于 ==
来说,如果比照单方的类型 不一样 ,就会进行 类型转换。如果比照 x
和 y
是否雷同,就会进行如下判断流程:
- 首先会判断两者类型是否 雷同,雷同的话就比拟两者的大小;
- 类型不雷同的话,就会进行类型转换;
- 会先判断是否在比照
null
和undefined
,是的话就会返回true
- 判断两者类型是否为
string
和number
,是的话就会将字符串转换为number
1 == '1'
↓
1 == 1
- 判断其中一方是否为
boolean
,是的话就会把boolean
转为number
再进行判断
'1' == true
↓
'1' == 1
↓
1 == 1
- 判断其中一方是否为
object
且另一方为string
、number
或者symbol
,是的话就会把object
转为原始类型再进行判断
'1' == {name: 'js'} ↓'1' == '[object Object]'
—- 问题知识点分割线 —-
如果 new 一个箭头函数的会怎么样
箭头函数是 ES6 中的提出来的,它没有 prototype,也没有本人的 this 指向,更不能够应用 arguments 参数,所以不能 New 一个箭头函数。
new 操作符的实现步骤如下:
- 创立一个对象
- 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的 prototype 属性)
- 指向构造函数中的代码,构造函数中的 this 指向该对象(也就是为这个对象增加属性和办法)
- 返回新的对象
所以,下面的第二、三步,箭头函数都是没有方法执行的。
—- 问题知识点分割线 —-
常见的 HTTP 申请办法
- GET: 向服务器获取数据;
- POST:将实体提交到指定的资源,通常会造成服务器资源的批改;
- PUT:上传文件,更新数据;
- DELETE:删除服务器上的对象;
- HEAD:获取报文首部,与 GET 相比,不返回报文主体局部;
- OPTIONS:询问反对的申请办法,用来跨域申请;
- CONNECT:要求在与代理服务器通信时建设隧道,应用隧道进行 TCP 通信;
- TRACE: 回显服务器收到的申请,次要⽤于测试或诊断。
—- 问题知识点分割线 —-
cookie 和 session
session
:是一个抽象概念,开发者为了实现中断和持续等操作,将user agent
和server
之间一对一的交互,形象为“会话”,进而衍生出“会话状态”,也就是session
的概念cookie
:它是一个世纪存在的货色,http
协定中定义在header
中的字段,能够认为是session
的一种后端无状态实现
当初咱们常说的
session
,是为了绕开cookie
的各种限度,通常借助cookie
自身和后端存储实现的,一种更高级的会话状态实现
session
的常见实现要借助 cookie
来发送 sessionID
—- 问题知识点分割线 —-
js 脚本加载问题,async、defer 问题
- 如果依赖其余脚本和 DOM 后果,应用 defer
- 如果与 DOM 和其余脚本依赖不强时,应用 async
—- 问题知识点分割线 —-
对 BFC 的了解,如何创立 BFC
先来看两个相干的概念:
- Box: Box 是 CSS 布局的对象和根本单位,⼀个⻚⾯是由很多个 Box 组成的,这个 Box 就是咱们所说的盒模型。
- Formatting context:块级高低⽂格式化,它是⻚⾯中的⼀块渲染区域,并且有⼀套渲染规定,它决定了其⼦元素将如何定位,以及和其余元素的关系和互相作⽤。
块格式化上下文(Block Formatting Context,BFC)是 Web 页面的可视化 CSS 渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其余元素的交互限定区域。
艰深来讲:BFC 是一个独立的布局环境,能够了解为一个容器,在这个容器中依照肯定规定进行物品摆放,并且不会影响其它环境中的物品。如果一个元素合乎触发 BFC 的条件,则 BFC 中的元素布局不受内部影响。
创立 BFC 的条件:
- 根元素:body;
- 元素设置浮动:float 除 none 以外的值;
- 元素设置相对定位:position (absolute、fixed);
- display 值为:inline-block、table-cell、table-caption、flex 等;
- overflow 值为:hidden、auto、scroll;
BFC 的特点:
- 垂直方向上,自上而下排列,和文档流的排列形式统一。
- 在 BFC 中高低相邻的两个容器的 margin 会重叠
- 计算 BFC 的高度时,须要计算浮动元素的高度
- BFC 区域不会与浮动的容器产生重叠
- BFC 是独立的容器,容器外部元素不会影响内部元素
- 每个元素的左 margin 值和容器的左 border 相接触
BFC 的作用:
- 解决 margin 的重叠问题:因为 BFC 是一个独立的区域,外部的元素和内部的元素互不影响,将两个元素变为两个 BFC,就解决了 margin 重叠的问题。
- 解决高度塌陷的问题:在对子元素设置浮动后,父元素会产生高度塌陷,也就是父元素的高度变为 0。解决这个问题,只须要把父元素变成一个 BFC。罕用的方法是给父元素设置
overflow:hidden
。 - 创立自适应两栏布局:能够用来创立自适应两栏布局:右边的宽度固定,左边的宽度自适应。
.left{
width: 100px;
height: 200px;
background: red;
float: left;
}
.right{
height: 300px;
background: blue;
overflow: hidden;
}
<div class="left"></div>
<div class="right"></div>
左侧设置float:left
,右侧设置overflow: hidden
。这样左边就触发了 BFC,BFC 的区域不会与浮动元素产生重叠,所以两侧就不会产生重叠,实现了自适应两栏布局。
—- 问题知识点分割线 —-
数组扁平化
数组扁平化就是将 [1, [2, [3]]] 这种多层的数组拍平成一层 [1, 2, 3]。应用 Array.prototype.flat 能够间接将多层数组拍平成一层:
[1, [2, [3]]].flat(2) // [1, 2, 3]
当初就是要实现 flat 这种成果。
ES5 实现:递归。
function flatten(arr) {var result = [];
for (var i = 0, len = arr.length; i < len; i++) {if (Array.isArray(arr[i])) {result = result.concat(flatten(arr[i]))
} else {result.push(arr[i])
}
}
return result;
}
ES6 实现:
function flatten(arr) {while (arr.some(item => Array.isArray(item))) {arr = [].concat(...arr);
}
return arr;
}