0%

最近看Vue关于渲染函数一节发现这样一段代码:

1
2
3
4
5
6
7
render: function (createElement) {
return createElement('div',
Array.apply(null, { length: 20 }).map(function () {
return createElement('p', 'hi')
})
)
}

Array的apply方法来自Function.prototype.apply方法,第一个this参数这里没用,设置为null。而第二个参数接受一个类数组对象或者数组,将其作为arguments传入Array函数。相当于Array.apply(null, Array(20))

直接调用Array(20)会产生一个具有20个空索引的数组。map方法只能迭代实际的值,而不能迭代空索引。而上面的方法实际上可以产生一个长度为20,值全为undefined的数组,可以有效使用map方法。

主要成分

每一个文件都是一个ModuleModule形成ModuleGraph。打包过程将module组合成chunks。chunks组合成chunk group并形成一个由module互联的ChunkGraph。当指定entry point时,底层创建带有一个chunk的chunk group。

1
2
3
module.exports = {
entry: './index.js',
};

创建一个名为main的chunk group(main是入口点的默认名字)。这个chunk group包含 ./index.jsmodule,当parser处理index.js中的import时,新的module添加到这个chunk。

1
2
3
4
5
6
module.exports = {
entry: {
home: './home.js',
about: './about.js',
},
};

两个chunk group被创建了,名字分别是home和about。每一个都有一个chunk,home chunk group包含home,about chunk group包含about。

一个chunk group可能包含多个chunk。splitChunksPlugin将一个chunk分割成一个或者多个chunks。

chunks

chunk有两种形式,一种为initial,是入口点的main chunk。这个chunk包含为入口点指定的所有模块和依赖项。另外一种是non-initial,可能被懒加载,当使用dynamic import或者splitChunksPlugin时可能出现。

每个chunk可能有对应的asset,assets就是输出的文件,也就是打包的结果。

1
2
3
4
// webpack.config.js
module.exports = {
entry: './src/index.jsx',
};
1
2
3
4
5
6
7
// src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom';

import('./app.jsx').then((App) => {
ReactDOM.render(<App />, root);
});

名为main的initial chunk被创建了,包含index.jsx、react和react-dom和他们所有的依赖,除了app.jsx。

对于app.jsx,一个non-initial chunk被创建了,因为这个module是动态引入的。

最终输出两个chunk,分别是:

  • /dist/main.js——一个initialchunk
  • /dist/394.js——一个non-initialchunk

默认情况下,non-initialchunk没有名称,故使用了一个唯一id代替。当使用动态import时,可以用magic comment显式指定chunk的名称。

1
2
3
4
5
6
import(
/* webpackChunkName: "app" */
'./app.jsx'
).then((App) => {
ReactDOM.render(<App />, root);
});

输出:

  • /dist/main.js——initial chunk
  • /dist/app.js —— non-initial chunk

输出

输出文件的名称受配置文件两个字段的影响:

  • output.filename —— 针对initial chunk文件
  • output.chunkFilename —— 针对non-initialchunk文件
  • 某些情况下,initialnon-initial被用于chunk,这个时候output.filename被使用。

一些占位符可用,如:

  • [id] —— chunk id([id].js -> 555.js)
  • [name] —— chunk name, 没名字的,使用ID
  • [contenthash] —— 输出文件内容的md4 hash,如[contenthash].js -> 4ea6ff1de66c537eb9b2.js

TL;DR

  • 避免对可视化更新使用 setTimeoutsetInterval; 始终使用 requestAnimationFrame

  • 将长时间运行的 JavaScript 从主线程移动到 Web Workers

  • 使用微任务进行跨越多个帧的 DOM 更改

  • 使用开发者工具的timeline和JavaScript Profiler评估JS性能

使用requestAnimationFrame改变视觉表现

requestAnimationFrame可以在帧的开始运行一个指定的回调。

1
2
3
4
5
6
7
8
9
/**
* If run as a requestAnimationFrame callback, this
* will be run at the start of the frame.
*/
function updateScreen(time) {
// Make visual updates here.
}

requestAnimationFrame(updateScreen);

框架或示例可能会使用 setTimeout setInterval 来进行动画等可视化更改,但问题是回调会在帧中的某个点运行,可能就在帧的最后,这通常会导致我们错过一个帧,从而导致 jank

jQuery过去使用setTimeout处理动画行为,在版本3之后改用requestAnimationFrame,强烈建议patch旧版本的jQuery。

减少复杂性或者用Web Workers

js运行在主线程,还有其他注入样式计算布局和绘制等任务。如果JS运行太久就会阻塞这些任务,导致丢帧。

如果使用滚动动画,将JS保持3到4ms。如果处于idle时期,就无所谓了。

许多情况下,不需要DOM访问的计算操作可以转移到web workers。例如排序或者搜索非常适合这个模型,加载和生成模型也可以。

1
2
3
4
5
6
7
8
9
var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
var sortedData = evt.data;
// Update data on screen...
});

并不是所有的工作都适合这种模式: webworkers 没有 DOM 访问权限。如果您的工作必须在主线程上进行,那么考虑一种批处理方法,将较大的任务分割为微任务,每个任务的执行时间不超过几毫秒,并在 requestAnimationFrame 处理程序内部跨每一帧运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);

function processTaskList(taskStartTime) {
var taskFinishTime;

do {
// Assume the next task is pushed onto a stack.
var nextTask = taskList.pop();

// Process nextTask.
processTask(nextTask);

// Go again if there’s enough time to do the next task.
taskFinishTime = window.performance.now();
} while (taskFinishTime - taskStartTime < 3);

if (taskList.length > 0)
requestAnimationFrame(processTaskList);

}

这种方法影响UX和UI,您需要确保用户知道任务正在被处理,通过使用进度或活动指示符。在任何情况下,这种方法都可以保持应用程序的主线程空闲,帮助它保持对用户交互的响应。

Renderer进程包含什么

  • 主线程
  • worker线程(包括web worker和service worker)
  • compositor线程
  • raster线程

核心工作是将 HTML、 CSS 和 JavaScript 转换成用户可以与之交互的网页。

解析

构造DOM

DOM是浏览器对页面的内部表示,开发者具有可与之交互的API。根据HTML标准,HTML具有很好的容错性。

子资源加载

解析构建DOM时,有个preload scanner,它可以并发运行获取一些外部资源,如图片、CSS和JS。在浏览器进程中向网络线程发送请求。

JS阻止解析

HTML一旦遇到script标签,就停止解析HTML。继而加载解析和执行JS代码。因为可以通过document.write这样的API修改DOM结构。有一篇很好的关于V8如何执行JS的文章

提示浏览器如何加载资源

如果JS不使用document.write,可以标记scriptasync或者defer,浏览器就可以异步加载运行JS代码,不阻止解析。也可以使用javascript module<link rel="preloa">是一种告诉浏览器当前导航需要该资源的方式,希望尽快下载。

计算样式

使用CSS可以对页面进行样式化,主线程解析CSS确定每个DOM结点的计算样式。浏览器默认存在一个样式表。

布局

主线程遍历DOM和计算样式,构建布局树,其中包含元素坐标以及边界框大小。并且只包含课件内容。visibility:hidden位于布局树中,display:none不位于布局树中。伪元素::before会包含在布局树,而不在DOM树。

chrome有一个工程师团队负责布局,这是一些工作细节

绘制

该阶段负责判断绘制的顺序。如果设置了z-index则不能单纯按HTML元素顺序绘制。

主线程遍历布局树,创建paint records,是绘制过程的一个描述,例如“先画background,然后text,然后rectangle”。类似于JS绘制canvas。

更新渲染pipeline很昂贵

布局树发生变化,绘制顺序也会发生变化。前面的操作会影响后面的操作。在给元素配置动画,浏览器需要在每个帧之间操作动画,如果每一帧都移动物体,动画显得很流畅。但是如果错过中间帧,页面就会janky

应用程序运行JS时,动画也可能会被阻塞,因为他们运行在主线程上。从而显得janky

可以将JS操作分块。使用requestAnimationFrame在每一帧运行。也可以在web worker运行JS

合成

浏览器在这个过程开始前已经知道了文档结构,元素样式,页面的几何特征和绘制顺序。将这些信息转换为屏幕上的像素,叫做rasterizing(光栅化)。chrome将页面各部分分层,分别光栅化,然后组合成一个框架,当滚动页面,这些已经光栅化的层组合成一个新的框架。

分层

为了找出元素需要在哪里。主线程遍历布局树创建层树(layer tree),在开发者工具的performance面板中叫做update layer tree。如果有些部分应该是单独的层而没有在单独的层中,可以使用CSS的will-change属性提示浏览器。

raster和composite脱离主线程

创建层树,确定绘制顺序后,主线程将信息交给compositor线程。compositor线程光栅化每一层。一个层可以像页面那么大,所以compositor将其分成几个tiles,每一个tile因此发送给raster线程,raster线程光栅化每一个tile,存储在GPU内存。

compositor线程可以优先处理不同的raster线程,所以视口附近的东西可以优先光栅化。一个layer也有许多不同分辨率的tillings用于处理zoom-in这样的行为。

一旦tiles光栅化完毕,compositor线程就会收集draw quads来创建一个compositor frame。

draw quads包含一些信息,例如tile在内存中的位置,以及在考虑到页面合成的情况下要绘制tile的页面位置

compositor frame是一个表示页面frame的draw quads集合。

然后通过 IPC 将compositor frame提交到浏览器进程。此时,可以从 UI 线程为浏览器 UI变化添加另一个compositor frame,或者从其他renderer进程为extension添加另一个compositor frame。这些compositor frame被发送到 GPU 在屏幕上显示。如果出现滚动事件,compositor线程将创建另一个compositor frame并发送到 GPU。

compositing的好处是它不需要涉及主线程。compositor线程不需要等待样式计算或 JavaScript 执行。这就是为什么compositing动画被认为是最好的平稳性能。如果布局或者绘制需要再次计算,那么主线程必须参与。

service worker是位于浏览器和网络中间的代理,通过拦截请求,将请求重定向到cache,启用离线获取功能。web worker则可以用于执行主线程上的计算密集型操作。

1
2
3
4
5
6
7
8
9
10
import { getConnection } from "typeorm";

export default async () => {
const connection = getConnection();
const entities = connection.entityMetadatas;
for (const entity of entities) {
const repository = getConnection().getRepository(entity.name); // Get repository
await repository.delete({}); // Clear each entity table's content
}
};

Evaluate Lock

  1. The first is whether the lock does its basic task, which is to provide mutual exclusion.
  2. The second is fairness. Does each thread contending for the lock get a fair shot at acquiring it once it is free? Another way to look at this is by examining the more extreme case: does any thread contending for the lock starve while doing so, thus never obtaining it?
  3. The final criterion is performance, specifically the time overheads added by using the lock.
    1. One is the case of no contention; when a single thread is running and grabs and releases the lock.
    2. Multiple threads are contending for the lock on a single CPU; in this case, are there performance concerns?
    3. There are multiple CPUs involved, and threads on each contending for the lock?

Control Interupt

  • How: disable interrupts for critical sections by using hardware instruction as if iy were atomic. When finished, re-enable interrupts(via hardware instruction).
  • What: single-processor systems
  • Code:
    image
    Read more »

同源策略与跨域

同源策略是一种限制来自于一个源的脚本和文档如何与另一个源的资源进行交互。

Read more »

页面生命周期

在一个网页加载的过程中,有三个重要的阶段以及与之对应的事件:

  • DOMContentLoaded:HTML加载完成并构建了DOM树,但外部样式表资源或者图片资源等还尚未加载完成
  • load:加载了所有的外部资源
  • beforeunload/unload:用户将要离开页面时

每一种事件因其所处的特殊阶段,具有不同的意义:

  • DOMContentLoaded:DOM构建完成,处理函数可以查找DOM节点,初始化接口等
  • load:可以获取图片资源大小等(如果尚未在HTML/CSS中指定)
  • beforeunload/unload:询问用户是否保存了操作等
    Read more »

初识flex布局

flex布局中分为container元素和item元素两大部分,这两大部分分别有自己所能控制的一些属性,列举如下:

container

将container元素设置为display:flex即可使之内部呈现flex布局,设置为display:flex的container有如下属性:

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content
Read more »