一篇文章彻底理解Javascript闭包(含闭包高频面试题)

7,714 阅读4分钟

闭包是指有权访问另一个函数作用域中的变量的函数。

闭包简单理解就是内嵌函数,也即在函数中嵌套函数

由于在JS中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数——也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

闭包的形成、变量的作用域以及变量的生存周期密切相关。

1. 变量的作用域

变量的作用域,就是指变量的有效范围。

随着代码执行环境创建的作用域链往外层逐层搜索,一直搜索到全局对象为止。

2. 变量的生存周期

  • 对于全局变量来说生存周期是永久的,除非主动销毁。
  • 一般对于函数作用域或者局部作用域(let const),会随着函数调用的结束而被销毁。
  • 当函数内部调用外部变量就产生了闭包,这时候变量的回收策略可以参考引用计数垃圾回收策略

3. 内存泄漏

由于IE的js对象和DOM对象使用不同的垃圾收集方法,因此闭包在IE中会导致内存泄露问题,也就是无法销毁在内存中的dom元素。

因此我们在不需要使用dom时,应将dom解除引用来避免内存泄露

function closure(){
    // div用完之后一直存在内存中,无法被回收
    var div = document.getElementById('div');
    div.onclick = function () {
       console.log(div.innerHTML);// 这里用oDiv导致内存泄露
    };
}

// 解除引用避免内存泄露
function closure(){    
    var div = document.getElementById('div');    
    var test = div.innerHTML;
    div.onclick = function () {
        console.log(test);
    };
    div = null;
}

4. 闭包总结

  • 形成: 函数中嵌套函数
  • 作用: 函数内部调用外部变量、构造函数的私有属性、延长变量生命周期
  • 优点: 希望一个变量长期存在内存中、模块化代码避免全局变量的污染、私有属性
  • 缺点: 无法回收闭包中引用变量,容易造成内存泄漏

5. 闭包的应用

闭包可以使代码组织方式的自由度大大提升,在日常使用中有非常广泛的用途。

简单的有:

  • ajax请求的成功回调
  • 事件绑定的回调方法
  • setTimeout的延时回调
  • 函数内部返回另一个匿名函数

我们详细讲下以下几个应用场景:

  • 构造函数的私有属性
  • 计算缓存
  • 函数节流、防抖

5.1 构造函数的私有属性

  • 由于javascript中天然没有类的实现,某些不希望被外部修改的私有属性可以通过闭包的方式实现。
function Person(param) {
    var name = param.name; // 私有属性
    this.age = 18; // 共有属性

    this.sayName = function () {
        console.log(name);
    }
}

const tom = new Person({name: 'tom'});
tom.age += 1; // 共有属性,外部可以更改
tom.sayName(); // tom
tom.name = 'jerry';// 共有属性,外部不可更改
tom.sayName(); // tom

5.2 计算缓存

// 平方计算
var square = (function () {
    var cache = {};
    return function(n) {
        if (!cache[n]) {
            cache[n] = n * n;
        }
        return cache[n];
    }
})();

5.3 函数节流、防抖

// 节流
function throttle(fn, delay) {
    var timer = null, firstTime = true;
    return function () {
        if (timer) { return false;}
        var that = this;
        var args = arguments;
        fn.apply(that, args);
        timer = setTimeout(function () {
            clearTimeout(timer);
            timer = null;
        }, delay || 500);
    };
}
// 防抖
function debounce(fn, delay) {
    var timer = null;
    return function () {
        var that = this;
        var args = arguments;
        clearTimeout(timer);// 清除重新计时
        timer = setTimeout(function () {
            fn.apply(that, args);
        }, delay || 500);
    };
}

6. 闭包高频面试题(代码理解)

// 1
for ( var i = 0 ; i < 5; i++ ) {
    setTimeout(function(){
        console.log(i);
    }, 0);
}
// 5 5 5 5 5


// 2
for ( var i = 0 ; i < 5; i++ ) {
    (function(j){
        setTimeout(function(){
            console.log(j);
        }, 0);
    })(i);
    // 这样更简洁
    // setTimeout(function(j) {
    //     console.log(j);
    // }, 0, i);
}
// 0 1 2 3 4

setTimeout(function(j) {
    console.log(j);
}, 0, i);

// 3
for ( let i = 0 ; i < 5; i++ ) {
    setTimeout(function(){
        console.log(i);
    },0);
}
// 0 1 2 3 4


// 4
var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    console.log(scope);
}
// local scope


// 5
var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    return function f(){
        console.log(scope);
    };
}
var fn = checkscope(); 
console.log(fn()); // local scope


// 6
var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    return function f(){
        console.log(scope);
    };
}
checkscope()(); // local scope


var obj = {
    name: 'tom',
    sayName() {
        console.log(this.name);
    }
}
obj.sayName(); // tom


var obj = {
    name: 'tom',
    sayName() {
        var name = 'alan';
        console.log(this.name);
    }
}
obj.sayName();// 'tom'

var name = 'jerry';   
var obj = {  
    name : 'tom',  
    sayName(){  
        return function(){  
            console.log(this.name);  
        };
    }   
};  
obj.sayName()(); // jerry

// var name = 'jerry';
var obj = {
    name: 'tom',
    sayName() {
        var name = 'alan';
        console.log(this.name);
    }
};
var sayName = obj.sayName;
sayName(); // '' // jerry




function fun(a,b) {
    console.log(b)
    return {
        fun: function(c) {
            return fun(c,a);
        }
    };
}
var d = fun(0); // undefined
d.fun(1); // 2 0
d.fun(2); // 2 0
d.fun(3); // 2 0

var d1 = fun(0).fun(1).fun(2).fun(3);
// 2 undefined 2 0
// 2 1
// 2 2

var d2 = fun(0).fun(1); d2.fun(2);
// 2 undefined
// 2 0
// 2 1
// 2 1

d2.fun(3); // {fun: ƒ}