性能篇

学习网站:
https://developers.google.com/web/fundamentals/performance/rendering

DevTools中查看Performance
image.png
12.5ms~80fps 表示每帧消耗12.5ms,1秒播放80帧。一秒播放帧数越大,页面越流畅。
提高fps : frame per second 。
image.png
Paint flashing : 可以实时查看到页面网页中被重绘的部分。
FPS meter : 实时帧和GPU。
image.png
火焰图
Scripting : js执行
Rendering : 重排
Painting : 重绘
image.png

image.png
红色小三角会影响性能。

解析html
加载文档document -> 构建dom树 -> 构建css dom,样式会更改dom,可能会导致页面重排(浏览器计算id,class和伪类等 -> 根据选择器获取样式规则)
window.requestAnimationFrame() : 方法可以将某些代码统一放到下一次重新渲染时执行
window.requestIdleCallback() : 将一个(即将)在浏览器空闲时间执行的函数加入队列,这使得开发者在主事件循环中可以执行低优先级工作,而不影响对延迟敏感的事件,如动画和输入响应。

JavaScript -> Style -> Layout(布局,回流) -> Paint(绘制) -> Composite(合成)
改变元素的宽度会触发整个页面的重新布局

渲染树

当元素display:none,即使在DOM中存在,在渲染树中也不存在;
当元素有伪类,即使在DOM中不存在,在渲染树中也存在;
image.png

渲染性能:js优化(requestAnimationFrame:动画效果的实现在该方法中实现,不要使用setTimeout和setInterval、Web Worker:WebWorker允许在后台线程运行脚本,不能操作DOM和使用window上的属性,worker和主线程通过postMessage通信,主页面和worker之间传递的数据是通过拷贝,而不是通过共享。Worker的优势在于能够执行CPU密集型任务而不会阻塞UI线程)
补充:
前端做CPU密集型任务:放到后端处理、使用Worker处理、转换成webAssembly进行。
webAssembly:(编译过程较复杂,编译后的.wasm文件可直接在浏览器运行,它可移植、加载快并且兼容web)
They can be compiled to native binaries which have faster startup times.
它们可以被编译成具有更快启动时间的本机二进制文件。

Native binaries also usually run considerably faster than JavaScript code.
本机二进制文件通常也比JavaScript代码运行得快得多。

Many languages make it much easier to parallelize code.
许多语言使代码的并行化变得容易得多。

Additionally, many non-JavaScript languages can now be compiled to WebAssembly, resulting in binaries that are almost as portable as JavaScript – and integrate well with it.
此外,许多非JavaScript语言现在可以编译成WebAssembly,从而生成几乎与JavaScript一样可移植的二进制文件,并与JavaScript很好地集成在一起。

渲染性能:缩小样式计算的范围并降低其复杂性。样式计算分两步:创建匹配选择器和从匹配选择器中获取所有样式规则。

  • 降低选择器的复杂性,使用以类 class 为中心的方法; BEM(块、元素和修饰符)

  • 减少要计算样式的元素数量

提升JS(Tree Shaking 和 Code Spliting)

与图像不同,图像下载后只需要相对较少的时间解码,JavaScript必须经过解析、编译,然后最终执行。
为了降低js执行的时间性能消耗,需要使用code spliting即代码拆分。将代码拆分,只将对应的代码块应用给需要的路由。但是这仍然会有解析和编译的无用消耗。
webpack的dynamic imports可以拆分代码为chunks。

//在最初的bundle中就会包含moduleA
import moduleA from "library";

form.addEventListener("submit", e => {
 e.preventDefault();
 someFunction();
});

const someFunction = () => {
 // uses moduleA
}

//如果没有在其他地方用到moduleA,可以使用以下方式动态导入,这种方式,在初始的bundle中不会包含moduleA,而是在"submit"后懒加载
form.addEventListener("submit", e => {
 e.preventDefault();
 import('library.moduleA')
 .then(module => module.default) // using the default export
 .then(someFunction())
 .catch(handleError());
});

const someFunction = () => {
 // uses moduleA
}

为了从根本上降低解析和编译的消耗,需要使用tree shaking。tree shaking只适用于静态的模块规范如ES Module.

import arrayUtils from "array-utils"; VS import { unique, implode, explode } from "array-utils";
在开发环境,两种写法都会导入整个module。
但在生产环境,可以通过配置webpack来剔除我们没有使用的内容,从而使包更小。

Babel
自动把ES Module语法转换成CommonJS规范,如使用require来代替import。以下配置可以让tree shaking生效。

{
 "presets": [
 ["env", {
 "modules": false
 }]
 ]
}

// This still pulls in all of lodash even if everything is configured right.
import { sortBy } from "lodash";

// This will only pull in the sortBy routine.
import sortBy from "lodash-es/sortBy";

If you prefer your import syntax to be consistent, you could just use the standard lodash package, and install babel-plugin-lodash. Once you add the plugin to your Babel config, you can use the typical import syntax you would otherwise use to shake unused exports.
如果希望导入语法保持一致,需要使用标准的lodash包,然后安装babel-plugin-lodash。
If you run into a stubborn library that won't respond to tree shaking, look to see if it exports its methods using the ES6 syntax. If it's exporting stuff in CommonJS format (e.g., module.exports), that code won't be tree shakeable by webpack. Some plugins provide tree shaking functionality for CommonJS modules (e.g., webpack-common-shake), but this may only go so far as there some CommonJS patterns you just can't shake. If you want to reliably shake unused dependencies from your applications, ES6 modules are what you should use going forward.
如果您遇到一个顽固的库,它不会对tree shaking做出响应,请查看它是否使用ES6语法导出其方法。如果是以CommonJS格式导出内容(例如。,module.exports),该代码将不会被网页tree shaking。一些插件为CommonJS模块提供了树震动功能(例如,webpack common shake),但这作用有限,因为某些CommonJS模式无法tree shaking。如果您想从应用程序中可靠地摆脱未使用的依赖关系,那么将来应该使用ES6模块。

内存泄漏

获取页面快照


image.png

查看未被释放的变量
Distance : 从根节点出发,到该对象的层数。
Shallow Size : 对象自身占用的内存大小,不包括它引用的对象。
Retained Size : 当前对象大小+当前对象可直接或间接引用到的对象的大小总和。
根据含义可知:Shallow 的值加起来为100%,Retained Size的最大值是100%,它 是一个包含关系,从上到下的数量是逐步减少的。

image.png
image.png

performace monitor面板
代码实例一

public showContextMenu(
        delegate: object,
        editor: IEditorInput,
        e: Event,
        node: HTMLElement,
        context: IEditorCommandsContext,
        close: Function
    ): void {
        //remove previous menu
        if (this.menu) {
            this.menu.remove();
            this.menu = null;
            if (this.menuDisposable != null) {
                dispose(this.menuDisposable);
            }
        }
        // setImmediate(() => {
        //compute the positon of menu
        let anchor: HTMLElement | { x: number; y: number; } = node;
        if (e instanceof MouseEvent) {
            const event = new StandardMouseEvent(e);
            anchor = { x: event.posx, y: event.posy };
        }
        this.menu = document.createElement("div");
        this.generateActions(this.menu, editor, context);
        if (!(anchor instanceof HTMLElement)) {
            this.menu.style.left = anchor.x + "px";
            this.menu.style.top = anchor.y + "px";
        }
        this.menu.classList.add("ucf-test-menu");
        document.body.appendChild(this.menu);
        //clickoutside
        const elm = document.querySelector(".ucf-test-menu");
        bind(elm, this.callback.bind(this));
        this.menu.focus();
    }
    private callback() {
        console.log('click out side', this);
        if (this.menu instanceof HTMLElement) {
                         // 区别代码,下面例子注释掉下面一行
             unbind(this.menu);
            this.menu.remove();
                        //  区别代码,下面例子注释掉下面一行
            this.menu = null;
        }
    }

    //generate contextmenu actions
    private generateActions(menu: HTMLElement | null, editor: IEditorInput, context: IEditorCommandsContext) {
        this.items.forEach((menuItem) => {
            let item = document.createElement("div");
            item.classList.add("ucf-test-menu-item");
            item.innerText = menuItem.label;
            item.onclick = () => {
                const command = CommandsRegistry.getCommand(menuItem.command);
                if (!command) {
                    return Promise.reject(
                        new Error(`command '${menuItem.command}' not found`)
                    );
                }
                this._instantiationService.invokeFunction(
                    command.handler,
                    editor.resource,
                    context
                );
                if (menu instanceof HTMLElement) {
                    unbind(menu);
                    menu.remove();
                    menu = null;
                }
            };
            if (menu instanceof HTMLElement) {
                menu.appendChild(item);
            }
        });
    }
}

结论:当dom nodes的数量达到一定数量,此次测试为700-900,会自动执行gc,回收。js heap size稳定在5.7M。手动执行GC可以回收dom nodes.
内存先阶梯式增加,然后降低到一个值。因为在内存足够时,垃圾回收机制还没开始工作,所以会阶梯式增加,当自动gc后,js heap size迅速降低。


image.png

代码实例二:在一的基础上注释了两行,详见代码。
结论:自动执行gc时,不会回收dom nodes。收哦的那个执行gc也不能回收dom nodes。


image.png

你可能感兴趣的:(性能篇)