做用域对性能的影响

做用域对性能的影响

做用域是理解JS的关键所在,一样做用域关系到性能。其实主要仍是标识符的解析会影响到性能。而咱们主要是从特别细微的地方去分析做用域的性能问题。 可能有一些地方的优化在引擎不断优化的状况下已经成效渐微,可是我以为仍是有必要去从根源了解为何咱们要这么作,javascript

追根溯源前端

咱们如下面这个函数为例java

function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}
var total = add(5, 10);
复制代码

咱们先来看一下在执行add函数的时候的做用域链 闭包

能够从图中看到全局对象位于做用域的末端,活动对象位于做用域的前端

标识符解析: 当函数在执行的时候,每遇到一个变量都会去搜索执行环境的做用域链,查找同名的标识符,且搜索过程是从做用域链的头部开始。搜索的时候先以当前运行函数的活动对象开始一直到做用域链的最后,若是搜索到则使用这个标识符对应的变量,没有则为视为标识符未定义。 因此一个标识符所在的位置越深,它的读写速度越慢,因为全局变量老是存在于执行环境做用域链的最末端。因此函数中局部变量 > 全局变量。函数

other: 当名字相同的两个变量在做用域链的不一样部分的时候,那么标识符则为最早找到的那个性能

var a = 1;
function test(){
    var a = 2;
    return a;
}
test();  // a = 2
复制代码

最佳实践优化

  • 用局部变量替换全局变量
  • 减小访问全局变量或者位于做用域链深处的标识符的次数
funtion init(){
    var doc = document,
        bd = doc.body;
        
        doc.getElementById('test').onclick = function(){}
        
        bd.className = '';
}
复制代码


**触类旁通**

问:既然做用域对性能有影响,那咱们有什么办法去临时改变做用域链么? 答案是有的,JS中with和try-catch是能够临时改变做用域链的。ui

疑惑spa

  • with语句主要是为了将代码的做用域设置到同一个特定的对象中(额,这句话有点抽象啊!!别急,咱们看一下追根溯源)
  • 为何都说with语句性能有问题?
  • try-catch又是怎么改变做用域链的,是否有性能问题,又该怎么避免性能问题?

追根溯源3d

咱们仍是要找个例子,好比:

funtion init(){
    with(document){
        var bd = body,
            links = getElementsByTagName('a');
        
        getElementById('test').onclick = function(){}
        
        bd.className = '';
    }
}
复制代码

咱们把前面的init函数改造了一下,当执行到with语句的时候,执行环境的做用域链被临时改变了,一个新的活动对象被建立,它包含了参数指定的对象的全部属性,并将新的活动对象推入做用域链的首位(这就解决了咱们的第一点疑惑,因此定义with语句的目的主要是简化屡次写同一个对象的工做)。如图:

能够从图中看到,第一位的是参数的对象,第二位是局部变量,第三位是全局变量。 虽然访问document对象的属性变快了,可是局部变量的访问变慢了。这就是with语句的性能问题(咱们的第二点疑惑也解决了)。

try-catch一样在try代码块中发生错误的时候,会自动跳转到catch,其将异常对象推入做用域链,并将其置于做用域链的首位。同with。

最佳实践

  • 简化catch语句的代码,最好没有局部变量的访问
try{
    init()
} catch(error) {
    handleError(error)
}
复制代码

因为只有一条语句,且没有对局部变量的访问,因此做用域链的临时改动并不会影响性能。



**触类旁通**

问:函数的闭包也依赖做用域,那闭包有没有性能问题? 答案:有的

追根溯源

再来个闭包例子:

function assignEvents() {
    var id = 'xdi9592';
    document.getElementById('save-btn').onclick = function(event) {
        saveDocument(id);
    }
}
复制代码

当执行assignEvents函数时,就会建立包含当前环境的第一个活动对象,而后再就是全局的一个活动对象。当闭包被建立的时候,它的[[scope]]属性被初始化为前面的两个活动对象。以下图:

一般函数执行完成以后,函数的活动对象会随着执行环境一块儿销毁,可是因为闭包的引用仍然存在,因此闭包中的活动对象没法销毁,所以须要更多的内存消耗,要注意内存泄漏的问题。除了这个以外,当闭包执行的时候,闭包的执行环境还会建立一个当前环境的第一活动对象,以前的两个引用再日后排,由此变成了下面的样子:
因为函数访问的id和saveDocument不在第一活动对象中,频繁的跨做用域访问标识符时,又是一次性能的损耗。

最佳实践

  • 当心的使用闭包
  • 经常使用的跨做用域链的变量存储在局部变量中,而后访问局部变量


参考资料:

《高性能JS》

相关文章
相关标签/搜索