学习总览
JavaScript
- 运行机制
- 渲染引擎
- JavaScript引擎
- 调用栈
- 事件循环
CSS
- 盒模型的理解
HTML
- HTML语义化
学习内容
(1)JavaScript运行机制
谈谈线程问题
JavaScript是一门单线程运行的语言。那么为什么JavaScript是单线程呢?我们可以假设JavaScript是多线程的,线程1和线程2并行,线程1删除了DOM节点A,线程2却要修改DOM节点A,那么当线程2操作该节点的时候我们到底该怎么处理呢?这种不确定性导致我们必须保证DOM的状态是唯一的,所以JavaScript变成了一门单线程语言。
同步异步
既然都说了JavaScript是单线程的,那么必然就会存在执行内容拥堵的情况,例如最常见的网络请求,当请求发给后端的时候,我们是不会等到数据回来再执行接下来的代码的,所以任务执行就分成了两种:
- 同步任务
- 异步任务
当主线程遇到同步任务时会直接执行,并且这些同步任务的执行会形成一个执行栈,而当主线程碰到异步任务的时候,异步任务会暂时性地被跳过,让主线程接着去执行接下来其他的同步任务,当那些暂时被跳过的异步任务有了结果,就会立即被推进一个任务队列(task queue),等到主线程空出来后,主线程会主动去检查这个任务队列,看里面都有什么内容等待执行,然后依次执行即可,这里有一个很关键的点,如果我们写了两个平行关系的ajax请求,那么这两个异步请求的回调谁先执行是不确定的,原因就是异步请求谁先进入任务队列的顺序在没有经过限制(例如promise的then回调限制)的情况下是不确定的。
说了这么多,我们还是放个图来直观感受一下吧:
宏任务/微任务
在了解宏任务和微任务的特点之前,我们只需要记住一句话,在当前微任务没有完成之前,是不会执行下一个宏任务的。
在微任务、宏任务与Event-Loop一文中,作者借助银行办理业务的队伍形象地解释了宏任务和微任务的差别,我提炼了作者传达出的两个要点:
- 微任务的任务队列不为空时,主线程是不会去执行宏任务队列中的宏任务的
- 在不同的运行环境下宏/微任务的种类有细微差别
在浏览器以及node环境下,宏任务和微任务列表如下:
我们结合上面两个表格来看一段代码:
我大致绘制了一个流程如下:
回调队列
回调队列也称为事件队列或者消息队列,本身就是用于帮助Web API模块处理异步操作的。在Web API模块中,异步操作在相应的线程中处理完成得到结果之后,会把结果注入异步回调函数的参数中,并且把回调函数推入回调队列中。
虽然我们将异步操作的结果推入了回调队列中,但什么时候将这些结果拿给主线程调用栈去执行呢?这就要说到事件循环了。
回调队列
队列是一个FIFO,先进先出的存储结构,
这样意味着异步操作的回调函数会按照进入队列的顺序被执行,而不是调用的顺序被执行。
事件循环(Event Loop)
事件循环所做的事情就是不断不断在主线程调用栈与回调队列中之间来回检查,首先检查主线程调用栈是否为空了,如果为空就去回调队列中拿取第一个任务放入调用栈,依次循环,直到调用栈以及回调队列都为空。
借助一道面试题小试牛刀
在搜集资料的过程中,我发现了一道频繁出现的头条面试题,在体现JavaScript运行机制与运行顺序这两个知识点上很有代表性,代码如下:
上面这段代码的运行结果,我们事先给出来:
首先需要补充一下ES6中关于async函数的内容。
待完善
(2)CSS盒模型
在前端页面中,元素会被渲染成一个一个的矩形区域,而渲染的依据就是CSS盒模型(basic box model)。每一个盒子由四个部分组成,内容区域、内边距区域、边框区域以及外边距区域。
内容区域通常放着元素真实的内容,如文本、图像或者播放器等。
内边距区域与内容区域相邻,负责扩展内容区域的背景,填充在内容区域与边框之间。
边框区域与内边距区域相邻,是容纳边框的区域。
外边距区域与边框区域相邻,可以将相邻的元素相互分隔开(这里时常会出现边距融合的现象)。
在CSS发展的过程中,出现了两种盒模型,W3C标准盒模型以及怪异盒模型。这两种盒模型的主要差异在于对于渲染的元素块的尺寸计算不同。
在W3C标准盒模型中,width以及height指定的宽高就是内容区域的实际宽高,真正的盒子宽高计算就变成了
realWidth = width + padding宽度 + border宽度
realHeight = height + padding高度 + border高度
在怪异盒模型中,我们设置的width/height就是盒子的宽高,真实的内容区域的宽度是width减去padding以及border的宽度后的结果,高度算法相同。
虽然W3C标准盒模型是公开的标准,但是它并不好用,相反微软坚持使用的IE怪异盒模型在开发中体验十分友好。举一个典型的例子:假设一行有四个盒子,每一个盒子有1px宽的边框,按照标准盒模型,每一个盒子给出25%的宽度,四个盒子并不能并行排列在一行,而按照怪异盒模型,每一个盒子的宽度会被设置为25%,其内容宽度则是在25%盒子宽度的基础上减去2px的边框宽度后得到的结果,无论我们如何设置边框宽度都不会影响渲染块的宽度,这就体现了怪异盒模型的好处。这么一看标准也未必都是完美的,所以为了灵活地在两个模式下切换CSS中提供了box-sizing去设置border-box属性值让我们可以在某一个确定的元素上使用怪异模式去计算宽高。
前文介绍margin外边距的时候,我们提到了一个外边距融合的问题,这个现象是怎么产生的呢?我们假设元素块A的外边距是20px,而元素B的外边距是30px,前面也提到外边距是用来分隔相邻元素的,那么我们是不是只要保证当前块和相邻块之间的间距大于等于我们设置的值就可以了,我们给这个值起个名字叫做最小安全距离,所以元素A和元素B之间我们会取到其中一个较大的值,这个值就是最小安全距离。所以我们给出的这个例子最终A和B之间的距离会是30px。
解释完为什么会出现外边距融合后,我们还需要了解一点外边距融合只会在垂直方向上发生,即marginTop和marginBottom融合,水平方向的margin是不会融合的,大致情况如图:
根据MDN的说法,margin的合并大致会有以下几种场景:
- 相邻元素之间
- 父元素与第一个以及最后一个元素之间
- 空的块级元素自身
首先,我们来理一下第一种情况,前文提到过margin合并只会出现在垂直方向,在这里补充一点margin合并还仅发生在块级元素上,甚至不包括行内块级元素,所以这里所谓的相邻指的就是两个相邻的块级元素。
[图片上传失败...(image-129f27-1574575972909)]
第二种情况如下图所示,当我们给子元素设置上边距和下边距时,时常会发现我们并没有将子元素和父元素之间挤出一段空隙,反而将父元素一块向下顶了一段距离出来。
第三种情况就比较抽象了,我绘制了一个示例,如下图:
实际上第三种情况,简单理解就是当一个元素是空块(无高度、无内容、无border/padding)的时候,它本身的上下margin会合并在一起,上下两个Margin值谁大取谁。