备战校招第一天

  1. BFC(块格式化上下文)
  2. 首页白屏
  3. 函数柯里化
  4. bind、apply、call
  5. 如果阻塞了JS中的主线程会发生什么情况
  6. 浏览器和Node 事件循环机制区别

BFC(块格式化上下文)

常见的定位方案分为三种:普通流、浮动、绝对定位。

块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。而这里的 BFC 属于属于普通流,相当于具有 BFC 特性的元素可以看作一个独立的容器,无论容器内部的元素怎么调整,都不会影响到外部的元素。

下列情况会创建BFC:

  • 根元素(<html>)
  • 浮动元素(元素的 float 不是 none
  • 绝对定位元素(元素的 positionabsolutefixed
  • 行内块元素(元素的 displayinline-block
  • overflow 值不为 visible 的块元素
  • display 值为 flow-root 的元素
  • contain 值为 layoutcontent或 paint 的元素
  • 弹性元素(displayflexinline-flex元素的直接子元素)
  • 网格元素(displaygridinline-grid 元素的直接子元素)

案例

1
2
3
4
 <div style="border: 1px solid #000;">
<div style="width: 45%; height: 300px; background-color: red; float: left;">123</div>
<div style="width: 45%; height: 300px; background-color: blue; float: right;">456</div>
</div>

这几行代码,如果直接运行会在网页上按照我们的预期显示出来两个色块,但仔细看下,会看出父元素的高度不正常,父元素看起来向空容器一样。原因在于子元素采用浮动,而浮动流不属于普通流,是独立定位的。

解决方法,按照上述规则,使用一个合适的方法,来进行创建BFC,从而告诉浏览器 ”我的最外层div需要创建BFC,按照常规方式渲染“

例如:在最外层 div 添加样式 float: left(上述规则指出只要 float 不为 none 即可创建BFC ) , 当然如果觉得 float 不够优雅,还可以使用 overflow: hidden; 。(通常情况下,我们可能会使用伪元素添加 clear:both; 占位来清除浮动,从而使最外层 div 恢复正常)。

详情请看,请看阮老师的文章

还有经典问题 margin 重叠问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<style>
p {
float: left;
color: #f55;
background: #fcc;
width: 200px;
line-height: 100px;
text-align: center;
margin: 100px;
}
</style>

<body>
<p>Haha</p>
<p>Hehe</p>
</body>

运行之后就会发现 两个 p标签 margin 发生重叠(同一个BFC内,两盒子垂直,就会发生 margin 塌陷重叠,而margin 值会以最大的为主。)

image-20200624115733942

解决方法:

  1. 将 margin 换为 padding
  2. 利用 BFC ,在 p 外层添加个一层 div,并在新添加的元素上添加样式(overflow: hidden;),创建新的BFC,这样两个p标签不属于同一个BFC,则自然不会再发生 margin 重叠问题。

首页白屏

现在多数都使用的为Vue、React和Angular三大框架其中之一,但它们有个共同的特点,均为 JS 驱动,所以在 JS 解析前,网页处于空白页,什么都不显示,这就是我们所说的白屏。

解决方法

  1. SSR,服务端渲染,有明显的好处,对SEO友好,还可以加快网页打开速度。
  2. 预渲染,插件 prerender-spa-plugin,编译阶段就将对应的页面渲染好,但是是没有数据的
  3. 骨架图,数据还未加载出来时,但会显示出网页的大致骨架,相对友好。

函数柯里化

柯里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function curry(fn) {
const len = fn.length
return function bindFn() {
if (arguments.length < len) {
return bindFn.bind(null, ...arguments)
} else {
return fn.call(null, ...arguments)
}
}
}

function add(a, b) {
console.log(a + b)
return a + b
}

// 原版
add(1,2)
// 柯里化后
var curryAdd = curry(add)
curryAdd(1)(2)

bindapplycall

func.bind(thisArg[, arg1[, arg2[, …]]])

调用函数后,返回绑定 this 之后的函数(以下为mdn文档中的案例),并不会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const module = {
x: 42,
getX: function() {
return this.x;
}
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // 直接调用,this指向全局
// expected output: undefined

const boundGetX = unboundGetX.bind(module); // 绑定this为module,this指向moudle
console.log(boundGetX());
// expected output: 42

func.apply(thisArg, [argsArray])

apply 接受一个参数数组 ,并会执行此函数,如果没有传递参数(或传入 nullundefined)则默认指向 window

1
2
3
4
5
6
7
8
9
10
11
12
const numbers = [5, 6, 2, 3, 7];

const max = Math.max.apply(null, numbers);

console.log(max);
// expected output: 7

const min = Math.min.apply(null, numbers);

console.log(min);
// expected output: 2

function.call(thisArg, arg1, arg2, …)

call 接受的是一个参数列表 ,并会执行此函数,如果没有传递参数(或传入 nullundefined)则默认指向 window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Product(name, price) {
this.name = name;
this.price = price;
}

function Food(name, price) {
// 将Product的this指向为Food,并传入参数
Product.call(this, name, price);
this.category = 'food';
}

console.log(new Food('cheese', 5).name);
// expected output: "cheese"

如果阻塞了JS中的主线程会发生什么情况,就是下面的代码。

1
2
setTimeout(() => console.log("hello world"))
while (true) {}
  • 首先,执行第一行,将 setTimeout 添加到宏任务队列
  • 其次,执行第二行,然后进入死循环,页面卡死
  • 同步代码都没有执行完,自然是不会执行宏任务队列中的任务

浏览器和Node 事件循环机制区别

浏览器和Node 环境下,microtask 任务队列的执行时机不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function test () {
console.log('start')
setTimeout(() => {
console.log('children2')
Promise.resolve().then(() => {console.log('children2-1')})
}, 0)
setTimeout(() => {
console.log('children3')
Promise.resolve().then(() => {console.log('children3-1')})
}, 0)
Promise.resolve().then(() => {console.log('children1')})
console.log('end')
}

test()

// 以上代码在node11以下版本的执行结果,执行完一个阶段的所有任务。
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1

// 以上代码在node11及浏览器的执行结果(顺序执行宏任务和微任务)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1

Node完整循环机制

引用官方解释文字和图片

image-20200706184509079

阶段概述

定时器:本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
待定回调:执行延迟到下一个循环迭代的 I/O 回调。
idle, prepare:仅系统内部使用。
轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
检测:setImmediate() 回调函数在这里执行。
关闭的回调函数:一些关闭的回调函数,如:socket.on(‘close’, …)。

参考链接

BFC

https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Block_formatting_context

http://www.ruanyifeng.com/blog/2009/04/float_clearing.html

https://juejin.im/entry/59c3713a518825396f4f6969

面经

https://www.nowcoder.com/discuss/177482

浏览器和Node循环机制区别

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/26#issuecomment-469188501

https://juejin.im/post/5c337ae06fb9a049bc4cd218#heading-0

https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/