xinglie / xinglie.github.io

blog
https://xinglie.github.io
153 stars 22 forks source link

极致的性能 #42

Open xinglie opened 5 years ago

xinglie commented 5 years ago

加载性能

基础设施文件应保持小巧,magix做为展示页view加载器,压缩后6kb、gzip后3kb。做为后台管理SPA框架,压缩后12kb,gzip后6kb

通用组件只提供核心功能,最多支持到觉功能,其它个性功能在各个项目中进行定制

当需要使用社区类库或组件时,除基础类库外,我建议只把用到的功能拿过来,比如underscore,只用了数组操作?把数组操作拿过来。有空多读读源码,尝试把这些优秀的类库再做一些拆分,你会发现你常用的功能也不过就那几个,其它很多功能根本用不到。

运算性能

循环

通读过妈妈下面很多项目的代码,循环这块的问题大家应该很少注意下,这里举例说明

_.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元素中,则标识checkedtrue

假设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次

再如钻展的这个页面http://gitlab.alibaba-inc.com/mm/zuanshi/blob/master/app/views/crab/premium_batch_cpc.js#L65

_.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模块 类似这样的方案来解决,让工具帮我们处理。

样式可以出一个选择器检查工具,避免嵌套过深的选择器

离线计算

把线上一些固定计算转移到线下来做。

  1. 模板字符串转换成函数
  2. 子模板分析
  3. 模板字符串分析
  4. 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万次循环变得异常流畅。一旦你的页面出现了卡顿,想再回头优化到流畅通常是很难的。

先做功能,回头再优化性能是不对的,性能的事情要在写代码前就已经解决的。

让原本很快、很流畅的应用变得更快更流畅不好么?