浅谈javascript的性能优化

对于大型的项目,js的性能优化始终都是个重点解决的问题,并且在面试的过程中,js的优化也始终是个老生常谈的话题,今天这篇文章就围绕js的性能优化,展开短浅的分析,如在文章过程中有理解错误或者不到位的情况,也欢迎各位同学提出宝贵的意见

1. 作用域意识

首先看下面这些代码

function foo() {
    let imgs = document.getElementsByTagName("img")
    for (let i = 0; i < imgs.length; i++) {
    imgs[i].title = '${document.title} - imgs - ${i}'    
    }
}

想必很多同学在dom编程的时候会采用如上遍历形式。在如上所示的遍历形式中,存在以下几个问题:

(1)在for循环中,多次用到了document.title , 我们都知道,document属于全局对象。如果页面的图片非常的多,那么for循环中就需要引用document成百上千次,每一次遍历都需要访问一遍作用域链,而对于一般的web项目来说,dom节点又很多,这种行为,可能会大大降低js的性能

(2)不知道各位小伙伴发现了没有,在for循环中,i < imgs.length这条语句,会在遍历的时候多次访问imgs这个属性,而在for循环中,imgs.length返回的又是一个number类型的常量,因此我们可以做如下优化

 

function foo() {
    /*
        js性能优化原则中,存在语句最少话原则,在
        let doc = document;
        let imgs = doc.getElementsByTagName("img");
        let len = imgs.length
        中存在4条语句(浪费)建议采用下面的定义方式
    */
    let doc = document,
        imgs = doc.getElementsByTagName("img"),
        len = imgs.length
    for (let i = 0; i < len; i++) {
        imgs[i].title = `${doc.title} - image - ${i}`    
    }
}

这里,先把document对象保存在局部变量doc中,然后用doc代替了代码中所有的document,这样调用这个函数只会查找一次作用域链;在for循环中,我们把imgs.length赋值给了一个常量,这样也就节省了每次访问imgs的时间。综上所述,本版本相比于上一个版本,性能上肯定会快很多

2. 不使用with语句

在性能很重要的代码中,应避免使用with语句,与函数类似,with语句会创建自己的作用域,因此也会加长其中代码的作用域链

3. 避免不必要的属性查找

想必很多热爱前端的小伙伴都知道在计算机科学中,存在时间复杂度这个概念,时间复杂度在计算机科学中用大写的O来表示。对于时间复杂度为1的用O(1)来表示,同理,对于时间复杂度为n的使用O(n)来表示

在javascript中,使用变量和数据相比于访问对象的属性效率会更高,访问变量或者数据的时间复杂度为O(1)

 (注:不考虑数据for循环,对于arr[0]或者arr[1]这种,时间复杂度为O(1))

而访问对象属性的时间复杂度为O(n)

(为什么是O(n)呢?!在js对象中,访问一个对象的属性的时候,他会一起访问了这个对象的原型链,比如一个对象上没有提前定义的constructor[个人理解,如果有不对的地方,请各位大佬不吝赐教!])

下面来看一个例子

let query = window.location.href.substring(window.location.href.indexof("?"))

类似于这行代码,是不是很多新手小伙伴都写过!!下面,咱们来分析下这行代码的问题:

这里有6次属性查找,三次是因为查找substring(), 三次是因为查找indexof(), 是不是一瞬间没明白!!为什么查找substring()查找了三次呢??window是一个对象,查找对象上的属性名,需要查找这个对象的原型链,从window.location.href.substring中的点(.)的个数不难看出,查找substring属性查找了三次,同理,indexof也是,那么有什么优化的地方吗?有!当然有!看下面这行代码

let url = window.location.href
let qurey = url.substring(url.indexOf("?"))

这个版本只要四次属性查找,比起前节约了(6-4)/6 = 33.33%,是不是感觉很神奇!

4. 循环优化

搞编程,离不开循环,对于循环的优化,可以大致分为如下几块

(1)简化终止条件:不难理解,如果对于要循环很多次的数据,我们可能在循环到某一次的时候,就已经拿到了想要的结果,这时,我们就可以提前终止循环

(2)简化循环体:这个也不难理解,有时候,我们可能要在循环中去做一些事情,但是,细心地小伙伴肯定发现了,循环体中的数据,在每一次循环中,都会执行一次

(3)使用后测试循环:什么是后测试循环??对于我们常见的for、while这都属于先测试循环,而do-while就是后测试循环,后测试循环,避免了对终止条件的初始评估,因此会更快

(4)展开循环:还记得上文提出的访问数组某一项如arr[0]的时间复杂度为1吗,如果循环次数有限,直接放弃循环!!采用arr[n]的方式

5. 其他性能优化

如果在逻辑判断中,存在大量的if-else 语句,那我们可以选择swich语句块,从性能上讲,switch语句会更快。在结合重新组织分支的方法,比如有极大可能的值放前面,不太可能的值放后面,可以进一步提高性能(why??小伙伴忘记了swich中的case后要跟break或者return了吗,终止操作啊!!)

最后,分享一个循环的8倍速算法...

这个算法的发明人Tom Duff,算法的名称就是用他的名字来命名的,叫做达夫设备,达夫设备的基本思路是以8的倍数作为迭代次数,从而将循环展开为一系列语句

// 假设values.length > 0
let iterations = Math.cell(values.length / 8)
let startAt = values.length % 8
let i = 0
do {
    switch(startAt) {
    // 注意没有break,如果走到6,则 0,7的分支不会走,反而6及以下的分支如6,5,4,3,2,1都会走
        case 0: procoess(value[i++]); 
        case 7: procoess(value[i++]);
        case 6: procoess(value[i++]);
        case 5: procoess(value[i++]);
        case 4: procoess(value[i++]);
        case 3: procoess(value[i++]);
        case 2: procoess(value[i++]);
        case 1: procoess(value[i++]);
    }
    startAt = 0;
} while (--iterations > 0)

在这个算法中,Math.cell会返回一个整数的去尾法,比如1.1会返回1,同理0.9会返回0,values.length % 8会返回余数,如10 % 8 = 2,在如上算法中,每次循环结束,会将startAt置为0,interations为循环多少次,结合本函数中的局部变量i,通过i++的形式,返回了数据的每一个元素

最后的最后,新人小白,学术甚浅,如有错误,还望不吝赐教!

参考书籍【javascript高级程序设计】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值