JavaScript学习之闭包

240 阅读2分钟

  作为前端er,在学习JS过程中,对于闭包的理解是很重要也是很基础的一件事。本篇主要从闭包的定义、原理、优缺点以及应用场景说一下个人对闭包的理解。
定义: 有权访问另一个函数作用域的函数。

原理: 一个函数创建时,会创建一个包含全局变量对象的作用域链保存在内部的scope属性中。函数调用时,通过复制scope中的对象构建执行环境的作用域链。然后,自身的活动对象被推至执行环境作用链的顶端。闭包是在函数被返回时,初始化外部函数活动对象及全局变量对象进作用域链。外部函数执行完毕后,作用域链会被销毁,但活动对象依然留存在内存中,因为闭包还有对它的引用。

优缺点: 优点在应用场景中一起合并介绍,缺点主要是闭包可能会引起内存泄漏(原因是在IE9之前,IE的Java引擎使用的是COM对象,并且使用的垃圾回收机制是引用计数。因此,如果在闭包的作用域内如果存在一个HTML元素,那就有可能出现内存泄漏);并且由于携带了外部函数的作用域,会比其它函数占用更多的内存。

内存泄漏:
function assignHandler(){
    var ele = document.getElementById('element');
    ele.onclick = function(){                       //ele的onclik属性指向闭包
        console.log(ele.id);                       //闭包指向ele的id属性,形成循环引用,造成内存泄漏
    }
}

使用场景:

  1. 如果将函数当作值类型到处传递,会看到闭包。在定时器,事件监听器等,只要使用了回调函数,就在使用闭包。
function wait(message){
    setTimeout(function timer(){
        console.log(message);
    }, 1000);
}
  1. 模块。隐藏函数的私有变量,通过公共的API来访问或修改内部变量。
function CoolModule(){
    let something = 'cool';           //私有变量
    let another = [1, 2, 3];         //私有变量
    
    function doSomething(){
        console.log(something);
    }
    
    function doAnother(){
        console.log(another);
    }
    
    return {
        doSomething,
        doAnother
    }
}

let foo = CoolMoudule();             //外部函数已经执行完毕,但内部变量还没被回收,因为doSomething和doAnother函数还持有该变量
foo.doSomething();
foo.doAnother();
  1. 避免全局变量的污染。
var a = 1;
function B(){
    a ++;
}

function B(){
  var a = 1;
  return function(){
    return a++;
  }  
}

  1. 使用 I I F E构造独立作用域。
for(var i = 0; i < 5; i++){
    setTimeout(function(){
        console.log(i);
    }, 1000);                  //最终打印5个5
}

//正确打印0,1,2,3,4
for(var i = 0; i < 5; i++){
    (function(j){                //构造一个独立作用域
        setTimeout(function(){
            console.log(j);
        }, 1000)
    })(i);
}