浏览器的渲染性能

不知不觉,2018 年的春节也要来临了。今年只是元旦回家了,春节并不打算回家。回家曾经是一件美好的事情,不知从何时开始,却是那么的揪心。最近也有读很多关于性能优化和底层的前端知识。不想就此停滞,只能不断前进。

# 浏览器渲染

我们都知道,现在主流显示器的频率是 60Hz,也就是 1 秒要刷新 60 次。这样才能保持画面的流畅,特别是玩游戏的时候,我们非常在意帧数。前端开发也是一样,所以我们每一项操作都最好在 10 毫秒之内完成,否则会产生所谓的卡顿现象,影响用户体验。

# 渲染过程

render pipeline

浏览器的渲染主要是分为 5 个步骤,我们需要了解这些知识才能编写性能更好的代码。

  1. JavaScript 我们经常使用 JS 来实现一些复杂的视觉效果,数据排序,DOM 操作等等。
  2. 样式计算 此过程是根据样式匹配选择器来计算哪些元素应用哪些 CSS 规则的过程。不过浏览器会对常用的选择器进行性能优化,例如类选择器。
  3. 布局 在知道每个元素的应用规则之后,浏览器开始计算所需要的空间大小以及其处在屏幕的位置。网页的布局中,一个元素的变化会影响到其他元素的位置。例如 Body 的宽度变窄之后其子元素的宽度也都会发生变化。
  4. 绘制 绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
  5. 合成 由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。

# JS / CSS > 样式 > 布局 > 绘制 > 合成

render pipeline

如果修改了元素的布局属性,也就是改变了元素的几何属性(例如宽度,高度)。那么浏览器就必须检查所有元素,然后重新排版页面。任何受影响的部分都要重新绘制,再重新合成。

# JS / CSS > 样式 > 绘制 > 合成

render pipeline no layout

如果只修改了元素的绘制属性,例如背景图片或者文字颜色,并不会对其他元素的布局造成影响。浏览器会跳过布局,但仍然执行绘制。

# JS / CSS > 样式 > 合成

render pipeline no layout

目前既不要绘制也不要布局的属性只有transform属性和opacity属性。所以在实现 CSS 动画的时候,优先使用这两个属性。

如何查询 CSS 属性触发上面 3 个流程的哪一个,可以去CSS Triggers (opens new window) 查询。

参考:

Rendering Performance (opens new window)

JavaScript中的数据类型

隔了一个月,我又来发博客了。最近的工作老是在写 CSS 和 HTML。但是我更想学习 JS 啊!我一直都觉得 HTML 和 CSS 是属于设计范畴的,而 JS 才是真正属于工程师的逻辑范畴。 况且最近 Github 上有一个神奇的项目Screenshot-to-code-in-Keras (opens new window)可以把截图直接生成 HTML 代码,我觉得只是单纯的从 PSD 翻译成页面的工作迟早要被淘汰。

最近在读 You Don't Know JS 这本书。书上讲解了很多关于 JS 的细节知识,对于深入了解 JS 有很大帮助。所以想在读的过程中把一些觉得有意思的东西记下来,便于以后复习。

# 类型

JavaScript 中有七种内置类型:

  1. 空值(null)
  2. 未定义(undefined)
  3. 布尔值(boolean)
  4. 数字(number)
  5. 字符串(string)
  6. 对象(object)
  7. 符号(symbol, ES6 新增)

除了对象之外,通称基本类型。

# JavaScript 中的设计 BUG

typeof null === 'object'; // true
1

正确的返回结果应该是 null。但这个 BUG 由来已久,修复反而会出问题。所以我们需要使用复合条件来判断:

!a && typeof a === 'object';
1

接下来是NaN的问题:

var a = 2 / 'foo';
var b = 'foo';
a; // NaN
b; // "foo"
window.isNaN(a); // true
window.isNaN(b); // true
NaN === NaN; // false
1
2
3
4
5
6
7

很显然"foo"不是 NaN,但显然它也不是数字。这个 BUG 也存在很久了,在 ES6 时代,我们可以使用 Number.isNaN 来解决。 ES6 之前的 polyfill 是:

if (!Number.isNaN) {
  Number.isNaN = function(n) {
    return typeof n === 'number' && window.isNaN(n);
  };
}
1
2
3
4
5

并且 NaN 是 JS 中唯一一个不等于自身的值。

# 值和类型

JavaScript 中的变量是没有类型的,只有值才有。变量可以随时持有任何类型的值。

undefined 和 undeclared. 变量在未持有的时候为 undefined, 此时 typeof 返回 undefined. 大多数开发者倾向于将 undefined 等同于 undeclared(未声明),但在 JavaScript 中它们完全是两回事。已在作用域中声明但还没有赋值的变量,是 undefined 的。相反,还没有在作用域中声明过的变量,是 undeclared 的。

var a;
a; // undefined
b; // ReferenceError: b is not defined
typeof a; // "undefined"
typeof b; // "undefined"
1
2
3
4
5

虽然 b 是一个 undeclared 变量,但 typeof b 并没有报错。这是因为 typeof 有一个特殊的安全防范机制。防止因为未定义导致程序终止运行。

# 值和引用

在许多编程语言中,赋值和参数传递可以通过值复制(value-copy)或者引用复制(reference-copy)来完成,这取决与我们使用什么语法。但是 JavaScript 对值和引用的赋值 / 传递在语法上没有区别,完全根据值的类型来决定。

简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值/传递,包括 null 、 undefined 、字符串、数字、布尔和 ES6 中的 symbol 。

复合值(compound value)——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值/传递。由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。

var a = [1, 2, 3];
var b = a;
a; //[1,2,3]
b; // [1,2,3]

// 然后
b = [4, 5, 6];
a; // [1,2,3]
b; // [4,5,6]
1
2
3
4
5
6
7
8
9

b=[4,5,6] 并不影响 a 指向值 [1,2,3] ,除非 b 不是指向数组的引用,而是指向 a 的指针,但在 JavaScript 中不存在这种情况!

参考:

You Don't Know JS (opens new window)

前端开发的技术栈

我发现自己博客没次开头都是在说近况和废话。这次也不例外。哈哈哈,工作也算顺畅,顺便总结下最近的前端开发的技术栈来回顾下这半年学到的知识。

# HTML 开发

对于大型项目,直接书写 HTML 代码是一个非常繁琐和头疼的事情,因为 HTML 需要闭合,每次找匹配的 HTML 标签都要非常花功夫。所以现在的开发都是使用预处理器来书写代码,例如主流的Pug (opens new window),通过缩进来控制元素的嵌套,还支持多种语法。非常适合大型项目的开发,再也不用担心修改代码的时候出现 HTML 元素标签没有闭合的情况发生了。而且还规避了一些语法错误,例如在p标签里嵌入block元素是非法的。如果强行嵌入的话,你会发现生成的 HTML 代码是错误的。

# CSS 开发

CSS 开发更多的需要是良好的模块化功能和合理的作用域。这时候也需要通过预处理器来进行操作,推荐使用SCSS (opens new window)。这里很多人对sassscss之间的区别有疑问。简单的来说,SCSS的格式更接近 CSS,所以比较容易上手。但是SASS是通过缩进来书写的,对新手不太友好。所以建议大家使用SCSS来书写模块化代码。

# JavaScript 开发

现在主流的浏览器支持的 JavaScript 版本是 es5。但是众所周知,JavaScript(es5)有很多陷阱和缺点,例如this指针问题和异步处理等等。基于原型连的继承对于面向对象开发者来说也很不友好。所以推荐使用es6 (opens new window)来书写代码。可以使用基于class的继承,和解决this指针问题。而且还能使用import进行模块化开发。虽然只是语法糖,但也提升了开发效率。

# 自动化构建工具

我们使用了预处理器来书写代码,并使用新版本的es6语法。但是目前浏览器并不支持直接解析这些内容。所以我们需要构建化工具来处理从 Pug 生成 HTML,从 SCSS 生成 CSS,把 es6 语法的 JavaScript 转换成 es5 语法。对于 SPA 网站推荐使用webpack,而对于普通网站的构建推荐使用Gulp。这里区别开的原因是,webpack必须指定入口文件,但是Gulp只需要指定需要处理的文件或文件夹就可以了,支持通配符匹配。对于多页面的传统网站来说非常便利。

# 浏览器兼容处理

这是每个前端工程师最头疼的地方了,因为每个浏览器支持程度都不一样。在使用比较新的 API 记得去Can I use (opens new window)查看下各个浏览器的支持情况,如果实在是需要这个功能的话,那就只能去寻找polyfill了。

参考:

Why p tag can't contain div tag (opens new window)