JavaScript闭包简介及干货分享

208 阅读3分钟

闭包简介

JavaScript是一门完整的OOP语言,但同时也有许多函数式语言的特性

在es6模块化出来之前,变量的作用于和生存周期,基本和闭包的形成有关

变量的作用域及生命周期

变量的作用域

function func(){
    var a = 1;
    console.info(a);
}
func();                         //1
console.info(a);                //Uncaught ReferenceError: a is not defined(…)

变量的生命周期

function func1(){         
    var a = 1;
    console.info(a);
}
func2();                        //执行完退出函数后局部变量a被销毁

function func2(){
    let a = 1;
    return function(){
        a++;
        console.info(a);
    }
}
let do = func2();
do();                        //2
do();                        //3
do();                        //4

func1中执行完退出函数后局部变量a被销毁

func2中a的值被保留下来

经典事例

我们现在想在页面中添加三个div,分别点击的时候打印出该div的索引值。

<!doctype html>
<html>
    <body></body>
    <script type = 'text/javascript'>
		'use strict';
		var arr = [0, 1, 2];
		var div;
		for(var i = 0 ; i < arr.length ; i++){
			div = document.createElement('div');
			div.innerHTML = i;
			div.onclick = function(){ console.info(i) };
			document.body.appendChild(div)
		}
    </script>
</html>

上面这段代码看上去行云流水,创建标签,赋值并添加onclick方法。但是在点击每一个div的时候,打印结果都是:3!

这是因为:

点击事件为异步触发时,循环已结束,所以无论点击哪个div,输出结果都是固定的(3)

解决方法1:

<!doctype html>
<html>
    <body></body>
    <script type = 'text/javascript'>
		'use strict';
		var arr = [0, 1, 2];
		var div;
		//将循环定义变量的var修改为let,es6之后块级作用域let,因为不是主讲内容,有兴趣的朋友可以去了解一下为什么
		for(let i = 0 ; i < arr.length ; i++){
			div = document.createElement('div');
			div.innerHTML = i;
			div.onclick = function(){ console.info(i) };
			document.body.appendChild(div)
		}
    </script>
</html>

解决方法2:

<!doctype html>
<html>
	<body></body>
	<script type = 'text/javascript'>
		'use strict';
		var arr = [0, 1, 2];
		var div;
		//在定义div的onclick方法时,使用闭包来处理
		//在闭包的帮助下,把每次循环的i都封闭起来,当时间函数顺着作用域链中从内到外查找变量i时,会优先找到封闭在闭包中的i
		for(var i = 0 ; i < arr.length ; i++){
			div = document.createElement('div');
			div.innerHTML = i;
			div.onclick = (function(i){ 
			    return function(){ console.info(i) } 
			})(i)
			document.body.appendChild(div)
		}
	</script>
</html>

闭包高阶函数应用

//函数柯里化(currying)

//计算每月的开销
let moneyCost = 0;
let cost = function(money){
    moneyCost += money;   
}
cost(100);
cost(200);
cost(300);
console.info(moneyCost);        //600

//柯里化重写 每天结束都计算,但我们关心月底会花掉多少钱,就是说在月底计算一次就行
let cost = (function(){
    let args = [];
    return function(){
        if(arguments.length === 0){
            let money = 0;
            for(let i = 0 ; i < args.length ; i++){
                money += args[i];   
            }
            //args = [];        //如果计算完成需要清空重新计算则打开此注释          
            return money;
        }else{
            [].push.apply(args,arguments)
        }
    }
})();
cost(100);                      //未真正求值
cost(200);                      //未真正求值
cost(300, 100);                 //未真正求值
cost();                         //700

习题环节(激动人心的做题时间)

分别打印出什么(答案自己试就行)

例1.

for (var i = 0; i < 5; i++) {
	console.log(i);
}
for (let i = 0; i < 5; i++) {
	console.log(i);
}

例2.

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

例3.

for (var i = 0; i < 5; i++) {
	setTimeout(function() {
		console.log(i);
	}, 1000 * i);
}
for (let i = 0; i < 5; i++) {
	setTimeout(function() {
		console.log(i);
	}, 1000 * i);
}

例4.

for(var i = 0 ; i < 5 ; i++){
	setTimeout(function(i){
		console.info(i)
	}, i * 1000, i)
}
for(let i = 0 ; i < 5 ; i++){
	setTimeout(function(i){
		console.info(i)
	}, i * 1000, i)
}

例5.

for (var i = 0; i < 5; i++) {
	(function(i) {
		setTimeout(function() {
			console.log(i);
		}, i * 1000);
	})(i);
}
for (let i = 0; i < 5; i++) {
	(function(i) {
		setTimeout(function() {
			console.log(i);
		}, i * 1000);
	})(i);
}

例6.

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

for (let i = 0; i < 5; i++) {
	(function() {
		setTimeout(function() {
			console.log(i);
		}, i * 1000);
	})(i);
}

例7.

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

for (let i = 0; i < 5; i++) {
	(function(i) {
		setTimeout(function() {
			console.log(i);
		}, i * 1000);
	})();
}

文章资源参考:《JavaScript设计模式与开发实践》 曾探