加载性能
基础设施文件应保持小巧,尤其是类库与框架,文件体积上及代码设计上应尽可能的最优
通用组件只提供核心功能,其它个性功能在各个项目中进行定制
当需要使用社区类库或组件时,除基础类库外,我建议只把用到的功能拿过来,比如underscore,只用了数组操作?把数组操作拿过来。有空多读读源码,尝试把这些优秀的类库再做一些拆分,你会发现你常用的功能也不过就那几个,其它很多功能根本用不到。
运算性能
循环
经常去review
代码,尤其是复杂数据的处理,比如部分项目中有这样的代码
_.each(campaignModelData.mediaType, function(item) {
_.each(mediaTypeList, function(_item) {
if (_item.id == item) {
_item.checked = true
}
})
})
_.each(campaignModelData.terminalType, function(item) {
_.each(terminalTypeList, function(_item) {
if (_item.id == item) {
_item.checked = true
}
})
})
_.each(campaignModelData.shopId, function(item) {
_.each(shopIds, function(_item) {
if (_item.id == item) {
_item.checked = true
}
})
})
且不论`_.each vs for`的性能,看这3对嵌套的each,我们只分析第1对的each,后面2个同理
第1对就是如果mediaTypeList
数组元素在mediaType
元素中,则标识checked
为true
假设medaiTypeList的长度为10,medaiType长度也为10
则第1对each跑完需要100次
如果我们对代码改造一下成:
var map={};
_.each(campaignModelData.mediaType, function(item) {
map[item]=1;
});
_.each(mediaTypeList, function(_item) {
if (map[_item.id] == 1) {
_item.checked = true
}
})
减少嵌套,则这时候
假设medaiTypeList的长度为10,medaiType长度也为10
则each跑完需要20次
我们可以明显减少CPU的计算。
再比如这段代码
//获得不热门的定向数据,并提示
_.each(data.list, function(item) {
var videos = item.unSellVideoTheme
var videoArr = []
var videoStr = ''
var length = 0
_.each(videos, function(video) {
if (video.unSellVideoTags.length) {
length++
_.each(video.unSellVideoTags, function(tag) {
_.each(tag, function(name, id) {
videoArr.push(name)
})
})
}
})
})
我们可以看到是4个each嵌套,同样,如果每个数组有10个元素,则跑完这些循环需要10000次
再如这段
_.each(result, function(algoItem) {
_.each(data.campaignTransList, function(item) {
_.each(item.targetList, function(targetItem) {
if (targetItem.id == algoItem.id) {
targetItem.suggestPrice = algoItem.bidPrice
}
})
})
})
像这样的代码在很多项目中都有,而事实上项目中数组的数据要大于我举例中的10个,所以真实循环中可能几千、几万、几十万次都是有可能的。
有些循环是可以通过前面类似打平,不去嵌套的方式实现,而有些是很难这样做的,这时候可能需要我们变换或重新设计数据结构,从而减少数据循环查找的可能。
还有一些菜单数据的处理
case 2 :
menus = [
ms.source._cs([
ms.media_2._cs([
ms.media,
ms.adzone
])
])
];
break;
与其根据权限动态的拼凑出菜单,不如在设计菜单时这样设计
var ms = {
"promotion" : { name : "推广管理" , url : "/activity/index",children:['activity'],permission:'1'},
"activity" : {name:"活动管理" , url : "/activity/index", icon: ""}
]
我们可以通过设计合理的数据结构,减少一些循环的处理。
html结构方案
css选择器
类似这样的css选择器 .form-div .line-detail .line-resolution .dropdown-toggle
在我们项目中还是比较常见的,我们这之所以嵌套这么多就是把样式限定在特定的范围内,但这样做也给浏览器解析带来一定的压力。
样式问题我们可以通过 css模块 类似这样的方案来解决,让工具帮我们处理。
样式可以出一个选择器检查工具,避免嵌套过深的选择器
离线计算
把线上一些固定计算转移到线下来做。
- 模板字符串转换成函数
- 子模板分析
- 模板字符串分析
- css类名转换
其它-转换思路
如果我们做一个拖动排序或拖动到的功能,像邮件可以从收件箱拖到垃圾箱,常见的方案都是计算目标节点的位置与大小,然后再比较鼠标的位置。这是一个非常通用的,也很少有bug的方案,其它的也有监听目标节点mouseenter事件的,但是或多或少都有一些问题。监听drag事件,要看浏览器的支持情况
对于这个场景,其实有一个更高效的document.elementFromPoint方法,在拖动时检测鼠标下的节点是非常方便的。无论节点多少都不会引起性能问题,并不随节点的增多导致查找时的性能下降
同样,magix在设计DOM事件监听时,1个view有click,还是1000个view有click,查找性能是一样的,即使这些view是嵌套存在的。magix并不会把这些事件处理函数放到一个数组中去处理,因为这样会随着事件处理函数的增加而导致调用时性能下降。magix针对自身的特点,在事件这块重新设计:全局一个统一处理函数,不管事件类型,也不管有多少个view有什么样的事件,同类型的事件在全局只会向body上绑定一次,然后由全局处理函数统一处理。magix中的事件代理
印象深刻的另一件事是对搜索关键字的查询优化,关键字一万多个一次发送到前端,然后前端根据用户的输入过滤出匹配的项。按正常思路我们在用户输入后对整个关键字列表进行查询,然后把匹配的显示出来。每次输入都要查询一万次,在用户连续输入的情况下就无法及时的给出反馈。
当时给出的实现的方案是,拿到关键字列表后,做一个字典树,当用户输入变化时,沿着字典树向下查找,每次并不会遍历一万多次,即使用户在快速连续输入的情况下也能及时响应。
现在的硬件很好,速度很快,向上面我提的这些点不去改进项目也跑的很好。而有一些点我们直接去测试时,几乎没什么差别,比如循环100次和循环10万次,也就1ms的差别。那么我们是否应该去优化?
性能的问题是一个习惯的问题,我们平时如果看到了更优的解法,认可这种解法,就应该改变自己原有的习惯,然后把应用这种更优解法变成你的习惯。
不管当前硬件多强大,一台计算机的能力总是有限的,而你也无法预知你正在开发的应用将来会变得多庞大,也不会知道什么时间会出现性能瓶颈。性能的事情也是点滴的事情,你的页面不会因为多了1万次循环变得卡顿,也不会因为你减少了1万次循环变得异常流畅。一旦你的页面出现了卡顿,想再回头优化到流畅通常是很难的。
先做功能,回头再优化性能是不对的,性能的事情要在写代码前就已经解决的。
让原本很快、很流畅的应用变得更快更流畅不好么?