文章

从代码实现方式优化性能

从代码实现方式优化性能

1. 使用多态代替条件判断

2. 参数传入使用平铺参数代替对象参数

没有必要包裹一层对象,增加创建和 GC 开销 benchmark

3. 高频调用函数避免使用 rest/spread 运算符

编译到 ES5 要使用循环,还要创建数组,要避免在高频场景下使用(相比正常写法相差 6 倍) benchmark,还有额外的 GC 开销

4. 手写 map 性能不如原生

benchmark

5. 局部化极高频变量

例如原来是 o.a.b,优化后直接访问 abenchmark

6. instanceof 的条件判断可以用 map 优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// setup.js
"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Origin = /** @class */ (function () {
    function Origin() {
    }
    return Origin;
}());
var Base = /** @class */ (function (_super) {
    __extends(Base, _super);
    function Base() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return Base;
}(Origin));
var A = /** @class */ (function (_super) {
    __extends(A, _super);
    function A() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return A;
}(Base));
var B = /** @class */ (function (_super) {
    __extends(B, _super);
    function B() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return B;
}(Base));
var C = /** @class */ (function (_super) {
    __extends(C, _super);
    function C() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return C;
}(Base));
var D = /** @class */ (function (_super) {
    __extends(D, _super);
    function D() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return D;
}(Base));
var E = /** @class */ (function (_super) {
    __extends(E, _super);
    function E() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return E;
}(Base));
var F = /** @class */ (function (_super) {
    __extends(F, _super);
    function F() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return F;
}(Base));
var list = [];
for (var i = 0; i < 1000; i++) {
    list.push(new D());
}
function a() {}
function b() {}
function c() {}
function d() {}
function e() {}
function f() {}
const HANDLER_BY_CONSTRUCTOR = new Map([
	[A, a],
	[B, b],
	[C, c],
	[D, d],
	[E, e],
	[F, f]
]);

function getCommonElementCreator(element) {
    // 对于大多数单层继承的类,使用更快的方法
    if (HANDLER_BY_CONSTRUCTOR.get(element.constructor) !== undefined) {
        return HANDLER_BY_CONSTRUCTOR.get(element.constructor);
    }
    // 其他情况使用循环查找
    let prototype = Object.getPrototypeOf(Object.getPrototypeOf(element));

    while (prototype && prototype !== Base.prototype) {
        const elementType = HANDLER_BY_CONSTRUCTOR.get(prototype.constructor);
        if (elementType !== undefined) {
            return elementType;
        }
        prototype = Object.getPrototypeOf(prototype);
    }
    return;
}

// case 1
list.forEach(function (element) {
    if (element instanceof A) {
    	a();
    }
    else if (element instanceof B) {
    	b();
    }
    else if (element instanceof C) {
    	c();
    }
    else if (element instanceof D) {
    	d();
    }
    else if (element instanceof E) {
    	e();
    }
    else if (element instanceof F) {
    	f();
    }
});

// case 2
list.forEach(function (element) {
    const handler = getCommonElementCreator(element);
    
    if (handler) {
        handler();
    }
});

7. ES6 的迭代器经过编译性能都很差,而且 helper 函数会创建多余的数组,造成 GC 开销

  • 遍历数组:TS 编译的 for...of 循环比 for 循环和 forEach 慢了约 70% 和 50% benchmark

  • 多元素 push 到数组:TS 编译结果比 Array.prototype.push.apply 慢了约 90%,即 a.push(...b)Array.prototype.push.apply(a, b) 的差别。benchmark

  • 数组拼接:[…a, …b] 比 a.concat(b) 慢了 95% benchmark

  • 数组复制:[…a] 比 a.slice() 慢了 95% benchmark

  • Set 转换到 Array:TS 编译的比 Array.from 慢了 95%,即 […set] 和 Array.from(set) 的差别 benchmark

  • 遍历 Map:forEachfor...of 快约 50% benchmark

8. importHelpers 与 tslib

每个源文件编译后都要加入一份 helper 函数,造成体积浪费,对 JS 引擎优化也不友好。可以开启 tsconfigimportHelpers 选项,将 helper 从每个文件中移除,放到单独模块(通常配合 tslib),从而减少重复代码体积。

importHelpers 是 TypeScript 编译器的配置选项:在进行向下兼容转换(如类的扩展、数组或对象的展开、异步等)时,默认会把辅助函数插入每个使用它们的文件导致重复;启用后改为从 tslib 导入这些 helper,避免重复。

本文由作者按照 CC BY 4.0 进行授权