工作生活中碰到了太多不友好的代码和行为,实在是使人不能承受之重。有些点真的非常简单,大部分人其实也都知道,但是在工作中写代码就是那么写。
个人认为,代码优化的过程,是减少操作、封装操作的过程。
一、减少操作(基于性能)
将大量操作,减少为少量操作,将少量操作,变成1次或0次操作。如何减少操作?
属性访问的中间变量保存
计算结果的缓存
1
2
3
4
5
6
7// 例1:
a.b.c.d.e.f = a.b.c.d.e.f + 1;
a.b.c.d.e.f = a.b.c.d.e.f * 2;
// 优化后==>
var temp = a.b.c.d.e;
temp.f = (temp.f + 1) * 2
JS中的属性搜索,是这样的,其会查找当前对象的属性、如果没有再查找其__proto__(一个内部指针,指向的是实例创建时构造函数的prototype对象)上的属性。
即使全部是当前对象的属性,例1未优化前,四次a.b.c.d.e.f,总共访问了20次属性。优化后,7次。其中,将+1操作的结果直接交给了下一步的操作,因为中间这个值,没有作用。
减少dom操作
1
2
3
4
5
6
7
8
9// 例2:
$btn.on('click', function () {
$('#J_etc').trigger('click');
});
// 优化后==>
var $etc = $('#J_etc');
$btn.on('click', function () {
$etc.trigger('click');
});
谁都知道JS中性能的瓶颈在于dom api的访问。例如要把循环append的操作,通过合并完以后一次性append进去。例如reactJS虚拟DOM的处理,主要还是为了将多次dom操作合并为一次,避免那么一群新人把老人积累下来的代码搞乱。以上的例子还只是click事件,还不会有太大影响。但是如果把这个事件放在手机端,再把事件换成scroll事件呢。现实工作中,还真有碰到这么写的。
减少无用的变量命名
附:注意内存泄漏与垃圾回收
1
2
3
4
5
6
7
8
9
10
11// 例3:
var $btn = $('#btn');
$btn.on('click', function () {
$(this).parent().remove();
});
$btn.on('mouseout', b);
$btn.on('mouseover', c);
// 优化后==>
$('#btn').on('click', function () {
$(this).parent().remove();
}).on('mouseout', b).on('mouseover', c);
jQuery、underscore等库为什么都提供了链式的调用,主要的作用也是为了减少中间变量的声明。因为这中间的变量只需要用到一次,而其会存在内存中。虽然也有垃圾回收机制,也并不是任何场景垃圾回收都适用。
如例子中,$btn的父元素移除后,但是$btn在内存中的引用还在,导致$btn元素,实际并没有被移除。其需要显式地设置 $btn = null;
来触发垃圾回收。
二、封装操作(基于可维护性)
其实也是减少操作的另一种表现,减少后续维护的操作
减少复制粘贴、代码重复率
1
2
3
4
5
6
7
8
9
10
11// 例4: 需要将一个对象的一些属性值处理成number类型
item.cid = Number(item.cid);
item.pid = Number(item.pid);
// ...
// 优化后:
_.each(['cid', 'event_id', 'product_id', 'gmt_create', 'gmt_modified', 'id', 'iid',
'num', 'origin_price', 'price', 'discount', 'sku_id', 'sku_num_left', 'uid'
], function (prop) {
item[prop] = Number(item[prop]);
});
优化前要写14行代码,并且是几乎一样的。改动一个属性,需要改两次;加一个属性,需要加一行代码。
减少外部 / 全局变量 / 全局属性
数据处理与渲染分离,松散耦合
减少各个函数 / 模块间的依赖
函数式编程、思想,减少函数副作用
在计算机科学中,函数副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。
函数副作用会给程序设计带来不必要的麻烦,给程序带来十分难以查找的错误,并且降低程序的可读性。严格的函数式语言要求函数必须无副作用。
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
// 例5:以上四项的例子
var page = {
resp: null,
init: function () {
this.fetch();
},
fetch: function () {
var self = this;
$.ajax({
url: 'xxx',
success: function (resp) {
self.resp = resp;
self.render();
}
});
},
render: function () {
this.renderItems();
},
renderItems: function () {
var resp = this.resp;
var items = [];
// 通过resp处理出items
// ......
var tpl = $('#item-tpl').html();
var html = lib.template(tpl, {
items: items
});
$('.J_Items').html(html);
this.handle();
},
handle: function () {
// 事件处理
}
};
// 优化后:
(function () {
var fetch = function (callback) {
$.ajax({
url: 'xxx',
success: function (resp) {
callback(resp);
}
});
};
var processItems = function (items) {
var items = [];
// 处理
return items;
};
var render = function (items) {
$('.J_Items').html(lib.template($('#item-tpl').html(), {
items: items
}));
};
var handle = function () {
// 事件处理
};
fetch(function (resp) {
render(processItems(resp.items));
handle();
});
})();
不知道大家看到的不同点是不是一样,依次罗列开来。先看优化前的“问题”:
减少外部 / 全局变量 / 全局属性:优化前,
init
方法,render
方法,renderItems
方法中的tpl
、html
变量都是不需要的,这些中间变量的声明并没有实际的(划分功能)意义,也没有必要。而resp
,被作为对象的全局的属性,这显然不合理,下面会说到。数据处理与渲染分离,松散耦合:优化前,渲染的处理主要是
renderItems
,但是其内部却不像他的函数名,还进行了数据的处理,然后才是模版的渲染,实际情况中,数据处理的代码甚至会比模版渲染的代码还多。减少各个函数 / 模块间的依赖:优化前,
resp
被作为全局的属性,render
方法因为调用renderItems
,而renderItems
需要resp
的属性,也就需要fetch
函数ajax
返回之后。各个方法间,依赖得太紧。函数式编程、思想,减少函数副作用:不知道大家看出来没,我想这应该是最关键的一点。优化前,
init
内部调用fetch
,fetch
内部调用render
,render
内部调用renderItems
,renderItems
内部做完数据处理和渲染,调用handle
。每一个逻辑与流程控制,都被 分散 到了各个方法中,而在fetch
方法中,还改变了resp
的值。单纯看逻辑,我就需要把page
对象的所有方法全部过一遍。
而对应优化后,去掉了page
对象,改成了函数式的调用:
减少外部 / 全局变量 / 全局属性:去掉了
init
方法,render
方法,renderItems
方法中的tpl
、html
这些无意义的变量;去掉了resp
属性,而通过函数的参数来传递;闭包中,没有一个变量是多余的,每一个函数都划分着对应的功能。数据处理与渲染分离,松散耦合:从
renderItems
中抽出processItems
,renderItems
的处理,只依赖传递进来的items
参数。而processItems
用于处理出需要的items
。例如,后面功能修改,ajax
接口换了,接口返回的数据也换了,那我实际只需要改动processItems
函数或者加一个processItems2
函数即可;例如,后面功能修改,数据不变但是,UI要变,那我只要修改renderItems
方法即可。减少各个函数 / 模块间的依赖:再看优化后的函数,任何一个函数都功能独立,都不依赖于其他函数。
函数式编程、思想,减少函数副作用:没有了外部变量、属性,每一个函数都相对独立,而通过
1
2
3
4fetch(function (resp) {
render(processItems(resp.items));
handle();
});
将所有的调用和流程控制聚合到一起,一目了然。而不是每一个方法中,带上下一个函数的调用。
以上,为个人工作以来感受较深的一些点,应该适用于80%的应用场景。