JS闭包及常见应用场景

1. 闭包的理解

理解闭包的关键在于:外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象。

它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的所有局部变量组成。

2. 闭包概念

能够读取其他函数内部变量的函数。 或简单理解为定义在一个函数内部的函数,内部函数持有外部函数内变量的引用。

3. 闭包用途

a. 读取函数内部变量
b. 让变量值始终保持在内存中,不会在调用后被自动清除
c.方便调用上下文的局部变量,利于代码的封装

4. 如何产生一个闭包函数

创建闭包最常见方式,就是在一个函数内部创建另一个函数。

function outer() {
    let name = "hello"; // 闭包创建时所能访问的局部变量
    function sayHello() // 闭包函数
    { 
        alert(name);
    }
    return sayHello; // 返回闭包函数
}
let myFunc = outer();
myFunc();

注意:outer在被muFunc引用后,内存得不到释放,这样的函数多了会造成内存溢出。

解决方法:手动释放

myFunc = null;

5. 闭包的注意事项(如何防止内存泄漏)

通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。(注意注意!!相关的解释都注释在代码里)

function makeAdder(x) {
    return function(y) 
    {
        return x + y;
    };
}

//add5 和 add10 都是闭包
//它们共享相同的函数定义,但是保存了不同的词法环境
let add5 = makeAdder(5);//add5中,x为5
let add10 = makeAdder(10);//add10中,x为10

console.log(add5(2));  // 7
console.log(add10(2)); // 12

//通过null释放对闭包的引用
add5 = null;
add10 = null;

在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

6. 闭包应用之回调

<!DOCTYPE html>
	<html>
	<head>
	    <meta charset="utf-8">
	    <meta http-equiv="X-UA-Compatible" content="IE=edge">
	    <title>闭包应用之回调</title>
	</head>
	<style>
	    body{
	        font-size: 10px;
	        /*将文本字体大小设为10px*/
	    }
	</style>
	<body>
	    <h1>闭包的应用</h1>
	    <p>闭包</p>
	    <a href="#" id="size-12">12</a>
	    <a href="#" id="size-14">14</a>
	    <a href="#" id="size-16">16</a>
	<script>
	    //定义行为,然后把它关联到某个用户事件上(点击或者按键)。
	    //代码通常会作为一个回调(事件触发时调用的函数)绑定到事件上
	    function changeSize(size){
	        function change(){
	            document.body.style.fontSize = size + 'px';
	        };
	        return change;
	    }
	    //通过拿到a标签的id来调用函数
	    document.getElementById('size-12').onclick = changeSize(12);
	    document.getElementById('size-14').onclick = changeSize(14);
	    document.getElementById('size-16').onclick = changeSize(16);
	</script>
	</body>
	</html>

7. 闭包应用之setTimeout

无论是setTimeout还是setInterval,传递的第一个函数都不能带参数(解决方法如下)

setTimeout(function(param)
{
    alert(param)
},1000)
//通过闭包实现传参效果
function func(a){
    return function()
    {
        alert(a)
    }
}
var f1 = func(1);
setTimeout(f1,1000);

顺带一提前端面试的经典题:(此处敲黑板~叩叩叩)

修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5
for (var i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log(i);
	}, i*1000 );
}

此处理想的输出结果应该为:1,2,3,4,5
实际输出结果:5,5,5,5,5

原因:setTimeout()执行的是一个异步操作,当所有代码都执行完毕后才会执行setTimeout(),所以当setTimeout()函数执行时,for循环早已执行完毕,此时i的值已经变成5,所以输出的结果就为:5,5,5,5,5。

这道题觉得理解上有困难的小盆友可以看看下面这篇文章

解决方法利用闭包。(当然还有其它方法,此处只讲闭包方法)

for (var i=1; i<=5; i++) { 
	    function timer(i){
	        return function()//返回一个闭包函数
	        {
	            console.log(i);
	        }
	    }
	    var f1=timer(i);
	    setTimeout(f1,1*1000);
	}

此时输出结果就为:1,2,3,4,5

8. 闭包应用之函数内部定时器

当函数内部的定时器引用了外部函数的变量对象时,该变量对象不会被销毁。

function() {
        let a = 0;
        setInterval(function(){
        console.log(a++);
        }, 1000)
    }

如果是之前没有了解过闭包概念的同学,希望这篇文章能给你们一点点帮助啦,偶也是花了点时间(其实不止一点…)研究了些(海量)资料才搞明白的(其实还是太菜了)。如果本文中有错误的欢迎大神指出~

本文涉及到的相关参考资料:
https://www.jianshu.com/p/aa2da2bee95f(简书)