威林厄姆教授的《为什么学生不喜欢上学》中阐述了,思考是把环境信息和长期记忆信息进行重新加工的过程。长期记忆是你对已有知识经验的认知,包括事实性知识和过程性知识。当某个问题超出了你的认知范围时人是没有办法思考的(譬如问你鲁菜中的清汤如何能做到煮时无泡沫)。所以对基础知识(事实性知识)的学习是有必要的。推演到技术上,很多前端前辈强调面试很看中面试者解决问题的能力和思路,但是这些都不是凭空产生的。你必须要知道很多的基础知识和日积月累的项目经验(过程性知识),才有可能通过推理把你存在长期记忆中的基础知识和面临的问题进行结合来解决问题。你不可能让一个完全不知道 HTTP 协议的人回答“当你在浏览器敲下地址后会发生什么”。
以下用一个我自己 debug 的实例来说明下基础知识(原理)和经验发挥的作用。
项目技术背景:
移动端页面,使用 react 和 material-ui,淘宝 lib.flexible 做移动端适配。
bug现象
当页面加载时,按钮上的字会从下往上升的动画效果,见下图:
对于这个问题 debug 的思路及分析步骤如下:
看上去是一个动画效果,通常是 js 修改了 css 某个属性引起的。我们项目自己代码没有加过动画,查了下元素 css 看到 material-ui 在元素上加了
transition:all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms
,根据all
这个值,难以推测是哪个 css 属性变化引起的动画。现象是在页面加载完后立即发生的,按经验一般要么是 dom ready 的时候,要么是组件的 componentDidMount 中写有 js 去修改了元素样式。先去 componentDidMount 中逐一注释代码,幸运的是通过注释找到了产生此现象的函数调用。
查看步骤2中定位出来的函数,其中有一句是获取某个元素的 offsetHeight,就是这条语句触发了这个 bug(注释掉此行代码,现象消失),而获取一个元素的 offsetHeight 会导致页面 reflow。我猜测是这个 reflow 导致某个不应该生效的样式生效了,但是此时依然无法定位那个样式的变化导致这个 bug。
仔细查看此按钮的样式,在我的知识范围里面没有找出可疑的样式(这里就是我此次 debug 的难点,因为导致此 bug 涉及到的一个 css 特性我不知道)。但是经过我对自己已知 css 知识的排查,猜测可能是行高引起的问题,并不经意间看到 body 设置了一个不太常见的 inline style:
font-size: 12px
。我想验证下是否是字体大小影响了垂直居中。所以我起 demo 测试获知:一般来说一个 div 高度和行高一样时,内部文字会垂直居中,但是前提是这个 div 的字体要小于高度。如果字体大于高度则文字会在中心线以下(具体原理还不清楚,遂记录下来,需要去调研下行高和字体的关系)。用 devtool 查看了下按钮的字体样式,果然继承自 body,这个 body 上设置的字体样式是 lib.flexible 生成的,因为之前调研过一点 lib.flexible 知道其会在 html 上也会设置字体样式,而且 html 的字体是大于按钮行高的。难道是因为 lib.flexible 先设置了 html 上的字体然后再设置 body 上的字体导致这个按钮的字体样式继承关系从 html 变成了 body 导致了动画效果的产生?
那步骤3的触发条件如何解释呢?注释掉获取某个元素的 offsetHeight 这行代码,页面上的 lib.flexible 并不会产生这个 bug 啊(说明先设置 html 字体,再设置 body 字体,导致的按钮字体样式从继承 html 再继承 body 这一过程不会触发动画)。而且从 js 引用关系来看(先引用的 flexible.js,然后再引用的项目 index.js),获取某个元素的 offsetHeight 导致的 reflow 应该是在 html body 被设置完字体样式后啊?根据已有线索:一、reflow 触发此次 bug,二、元素字体样式继承关系从 html 变成了 body。瞬间猜测设置 html 和 body 字体样式的代码可能是异步的,而 reflow 就发生在他们两步中间。遂去查看 lib.flexible 源码,果然设置 body 的代码是异步的:
doc.addEventListener('DOMContentLoaded', function(e) {
doc.body.style.fontSize = 12 * dpr + 'px';
}, false);
那基本就定下来产生这个 bug 是因为先设置了 html 的字体,然后获取某个元素 offsetHeight 这行代码产生的 reflow 导致按钮字体样式生效(继承自 html 字体),最后设置 body 字体样式导致按钮字体样式又变成了 body 的字体(继承优先级被 body 覆盖),按钮字体样式的变化导致行高变化触发了动画效果。
- 那为什么没有那行导致 reflow 的代码就不会产生这个 bug 呢?我自己依稀记得浏览器并不会在 js 每次修改样式时都及时去让修改生效,而会合并某些修改。遂想 google 这方面的内容,但是短时间内没有查到细节,只查到了:
浏览器不会在每一次样式变化后就去重新relfow一次,一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些时候,我们的脚本会阻止浏览器这么干,比如:如果我们请求下面的一些DOM值:offsetTop, offsetLeft, offsetWidth, offsetHeight。
所以此次 debug 顺利找到了产生 bug 的原因,此过程中产生了两个待调研的新知识点:一、字体和行高的关系。二、浏览器如何优化 js 对样式的修改。
回溯下整个 debug 过程,步骤1,2是项目经验起了作用(如何凭借第六感去定位问题,遇到类似现象以前是如何定位问题的),步骤3,5,6,7是基础知识起了作用(步骤3 - reflow 知识,步骤5 - css 样式继承知识,步骤6 - js 异步原理,步骤7 - 笼统的浏览器绘制原理)。而此次 debug 花费时间最长的是步骤4,也就是我 css 基础知识盲点导致的。所以经验可以积累,但基础知识还得靠我们程序员自己花力气去学习及完善。