前面两篇文章介绍了Javascript文件在页面中位置以及异步加载问题对前端性能的影响。不过受限于单线程的原因,不管采用哪种方法,只要Javascript进行了耗时的工作,就都会引起页面的阻塞。所以在写代码的过程中应该注意一些会影响代码性能的问题,这样才能让我们的优化尽量做到极致。下面我跟大家分享关于标识符查找方面的优化问题。
函数中变量的查找
作用域链
每个函数都有一个[[Scope]]属性,指向函数的作用域链,作用域链由多个变量对象组成。这个作用域链在函数定义的时候被创建,在函数定义时,会创建一个变量对象,这个变量对象包括了函数包含块所能访问到的变量,其实就是函数包含块的执行上下文。这个变量对象会首先被放入作用域链中。
function add(num1,num2){
var sum = num1 + num2 ;
return sum ;
}
在函数执行的时候,会创建一个叫做执行上下文的对象,这个执行上下文会用来进行函数的变量查找。执行上下文也有一个作用域链,这个作用域链就是用来进行变量查找的。当执行上下文创建时,它的作用域链会用函数的[[Scope]]属性来初始化。创建完执行上下文之后,又会创建一个叫做活动对象的变量对象并且放入作用域链的头部。这个活动对象包括所有局部变量,函数参数,arguments和this。这也说明了一个问题,就是this的值是在运行的时候决定的,而不是定义的时候。
var sum = add(1,2) ;
每次进行变量查找时,我们都要遍历执行上下文的作用域链,直到找到为止或者返回undefined。在这个过程中,变量查找的深度就会对性能产生影响。变量查找得越深,性能就越低。
由于现代浏览器对于JS的执行已经进行了优化,所以经过我的测试发现,变量查找的优化对于性能的提升不会产生太大的影响。虽然这么说,但是关于变量查找的优化方法还是可以借鉴的。
作用域扩大
对于正常的情况,函数执行上下文的作用域在函数执行的时候是不会发生变化的,但是有几个特例情况会使作用域发生变化,这种现象叫做动态作用域。
with
with语句用来在特定作用域中引入变量对象,比如下面这段代码:
function test(){
with(document){
var link = getElementsByTagName("a") ;
}
}
通过with语句,在with作用域内的代码都可以直接访问document中的属性和方法。这个方法虽然可以方便得访问属性,但是也带来了一些副作用。在使用with语句时,会向函数执行上下文的作用域链的头部插入一个变量对象,这个变量对象的值就是with中包含的对象,这就意味着原来在作用域中的变量对象都被往后移了一位,也就是说查找这些变量要遍历更深一层作用域链。所以我们应该尽量避免使用with语句,可以通过声明局部变量然后赋值为document的方式来取代with语句。
try catch
try catch语句跟with一样,也会产生同样的影响,只是这次插入到作用域链中的变量对象是catch括号中的对象,一般就是错误对象e。
eval
eval函数的坏处我想大家都应该知道了,通过eval执行的代码会污染全局变量,进而也会拉长变量的查找深度。
对象属性查找
原型和原型链
每个对象都有一个指向原型prototype的属性__proto__
,多个对象的继承就形成了原型链。下图是一个关于原型和原型链的图:
关于原型链的介绍网上已经有很多了,我这里就不做介绍了。Javascript中的继承是通过原型链实现的,这就会导致一个问题,就是经过多次继承之后,原型链会变得很长,然后对于对象属性的查找就需要遍历更长的原型链,而遍历的越深效率必然就会越低。这就是对象属性查找的时候需要优化的一个地方。
上图是一个book对象的原型继承图,很容易发现如果要访问title属性那么只要直接再book对象上查找就可以了,但是如果要访问toString方法的话,那么就需要遍历到原型链最深处,这样的话效率肯定更低。一个常用的优化的方法就是用局部变量来保存对象的属性,后面只要直接访问这个局部变量就可以了。这方法跟函数中变量查找是一个道理。
现代浏览器对于对象属性的查找同样进行了优化,所以通过测试性能方面也没有太大的差别。
总结
上面所说的是Javascript代码优化方面常见的两个问题,虽然现代浏览器对它们都进行了优化,但是如果要适配一些比较老旧的浏览器的话,还是需要注意的。而且上面说到的优化策略也是一个很好的编程规范。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。