编程 JS范例

工作生活中碰到了太多不友好的代码和行为,实在是使人不能承受之重。有些点真的非常简单,大部分人其实也都知道,但是在工作中写代码就是那么写。

个人认为,代码优化的过程,是减少操作、封装操作的过程。

一、减少操作(基于性能)

将大量操作,减少为少量操作,将少量操作,变成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();
});
})();

不知道大家看到的不同点是不是一样,依次罗列开来。先看优化前的“问题”

  1. 减少外部 / 全局变量 / 全局属性:优化前,init方法,render方法,renderItems方法中的tplhtml变量都是不需要的,这些中间变量的声明并没有实际的(划分功能)意义,也没有必要。而resp,被作为对象的全局的属性,这显然不合理,下面会说到。

  2. 数据处理与渲染分离,松散耦合:优化前,渲染的处理主要是renderItems,但是其内部却不像他的函数名,还进行了数据的处理,然后才是模版的渲染,实际情况中,数据处理的代码甚至会比模版渲染的代码还多。

  3. 减少各个函数 / 模块间的依赖:优化前,resp被作为全局的属性,render方法因为调用renderItems,而renderItems需要resp的属性,也就需要fetch函数ajax返回之后。各个方法间,依赖得太紧。

  4. 函数式编程、思想,减少函数副作用:不知道大家看出来没,我想这应该是最关键的一点。优化前,init内部调用fetchfetch内部调用renderrender内部调用renderItemsrenderItems内部做完数据处理和渲染,调用handle。每一个逻辑与流程控制,都被 分散 到了各个方法中,而在fetch方法中,还改变了resp的值。单纯看逻辑,我就需要把page对象的所有方法全部过一遍

而对应优化后,去掉了page对象,改成了函数式的调用:

  1. 减少外部 / 全局变量 / 全局属性:去掉了init方法,render方法,renderItems方法中的tplhtml这些无意义的变量;去掉了resp属性,而通过函数的参数来传递;闭包中,没有一个变量是多余的,每一个函数都划分着对应的功能。

  2. 数据处理与渲染分离,松散耦合:从renderItems中抽出processItemsrenderItems的处理,只依赖传递进来的items参数。而processItems用于处理出需要的items。例如,后面功能修改,ajax接口换了,接口返回的数据也换了,那我实际只需要改动processItems函数或者加一个processItems2函数即可;例如,后面功能修改,数据不变但是,UI要变,那我只要修改renderItems方法即可。

  3. 减少各个函数 / 模块间的依赖:再看优化后的函数,任何一个函数都功能独立,都不依赖于其他函数。

  4. 函数式编程、思想,减少函数副作用:没有了外部变量、属性,每一个函数都相对独立,而通过

    1
    2
    3
    4
    fetch(function (resp) {
    render(processItems(resp.items));
    handle();
    });

将所有的调用和流程控制聚合到一起,一目了然。而不是每一个方法中,带上下一个函数的调用。

以上,为个人工作以来感受较深的一些点,应该适用于80%的应用场景。