浏览器的渲染性能

Posted on

不知不觉,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 查询。

参考:

Rendering Performance

JavaScript中的数据类型

Posted on

隔了一个月,我又来发博客了。最近的工作老是在写CSS和HTML。但是我更想学习JS啊!我一直都觉得HTML和CSS是属于设计范畴的,而JS才是真正属于工程师的逻辑范畴。 况且最近Github上有一个神奇的项目Screenshot-to-code-in-Keras可以把截图直接生成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

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

(!a && typeof a === "object")

接下来是NaN的问题:

var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true
NaN === NaN // false

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

if (!Number.isNaN) {
  Number.isNaN = function(n) {
    return (
      typeof n === "number" &&
      window.isNaN(n)
    );
  };
}

并且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"

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

值和引用

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

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

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

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

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

参考:

You Don't Know JS

JavaScript对象的常见操作

Posted on

工作也算是稳定了,不过理想和现实的差距还是很大。程序员的职责是把枯燥的工作自动化,而不是去进行重复劳动。最近在写JavaScript程序的时候,遇到了很多对象相关的操作。所以写点东西来总结下这半个月的成长。

JavaScript对象复制

JavaScript对象默认全部是拷贝引用,也就是所谓的浅拷贝。所以我们在对象操作的时候,要记住是否需要进行拷贝。一般我们使用对象的时候,都是需要对其某个属性进行修改。所以正确的写法是:

    const bar = { a: 1, b: 2, c: {d: 4}}
    const foo = {...bar, b: 3}
    foo.c.d =10
    console.log(bar.c.d)
    // { a: 1, b: 2, c: {d: 10}}

这样就同时进行拷贝和修改。注意这里使用的是JavaScript的es6语法。如果要在浏览器运行,你需要 Babel 来进行转换。注意,如果对象里面还有对象的话,这种方式也仅仅是浅拷贝。深拷贝必须遍历所有属性进行复制,在效率上有很大问题,所以我们尽量不要去用深度拷贝来解决问题。 这里对象的复制是使用的Object.assign 针对对象的简单类型可以进行复制,但是对象还是引用。如果实际的应用场景确实需要进行深度拷贝,可以使用Lodash。提供了很多常用的类库。

JavaScript数组

针对数组,现在已经不推荐用for循环来进行处理了,请使用数组的map, filterreduce来处理数组的操作。相信这三个方法足以满足需求。

JavaScript开发规范

现在JavaScript已经是es6的时代,所以我们也需要顺应时代学习新知识。这里我建议大家读读airbnb的JavaScript Style Guide。这里不仅教会你正确的编码格式,更多的是优秀的写法。如何合理的拷贝对象,遍历数组等等。

目前就写到这里,工作之后并没有多少成长。写博客都发呆了很久该写什么。。。