JavaScript基础——闭包及其使用场景

JavaScript基础——闭包及其使用场景

一、概念

一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域

理解:闭包 = 内层函数 + 外层函数的变量

eg:

function output(){
	let i = 1
	function fn(){
		console.log(i)
	}
	return fn()
}
const fun = output()
fun()

二、作用

封闭数据,提供操作,外部也可以访问函数内部的变量

解析(参照上面代码):因为let是有作用域的,只能在output函数内部使用,要想在外部使用是不可以的,采用闭包的形式,在fn函数中使用 i 数据,然后将fn函数return出去,外部的output() === function fn(){} ,最后用fun()调用函数就可以使用 i 数据了

三、使用场景

闭包应用:实现数据的私有(不会被外部串改)

风险:存在内存泄漏(垃圾回收机制的标记清除法)

使用场景:返回值、函数赋值、函数参数、IIFE(自执行函数)、循环赋值、getter/setter、迭代器、首次区分、缓存、节流函数

以下例子参考:https://blog.csdn.net/dkm123456/article/details/111644638

  1. 返回值(最常用)
    function fn(){
    	var name = "Hello";
    	return function(){
    		return name;
    	}
    }
    let fnc = fn();
    console.log(fnc())	//Hello
    
  2. 函数赋值

    在闭包里面给fn2函数设置值,闭包的形式把name属性记忆下来,执行输出Hello

    let fn2;
    function fn(){
        let name = "Hello"
        //将函数赋值给fn2
        fn2 = function(){
            return name
        }
    }
    fn()//执行后将函数赋给fn2
    console.log(fn2())//执行输出fn2
    
  3. 函数参数

    闭包返回callback函数给到fn1,在fn2函数中充当参数,然后在fn2中执行即可拿到name值

    		function fn(){
    			var name="hello";
    			return function callback(){
    				return name;
    			}
    		}
    		var fn1 = fn()//执行函数将返回值(callback函数)赋值给fn1,
    		
    		function fn2(f){
    			//将函数作为参数传入
    			console.log(f());//执行函数,并输出
    		}
    		fn2(fn1)//执行输出fn2
    
  4. IIFE【Immediately Invoked Function Expression(立即调用的函数表达式)】 ===> 自执行函数

    跟第3点差不多,一样把函数当参数传给fn2,不同的是这个是自执行的不用调用再次赋函数给fn1

    	(function(){
            var name="hello";
            var fn1= function(){
                return name;
            }
            //直接在自执行函数里面调用fn2,将fn1作为参数传入
            fn2(fn1);
        })()
        function fn2(f){
            //将函数作为参数传入
            console.log(f());//执行函数,并输出
        }
    
  5. 循环赋值

    需要注意的是setTimeout是循环结束后才执行的,因为var是有变量提升的,每次赋值都会被新值覆盖,如果不使用闭包形式,那么setTimeout打印出的值将为10次11。如果了解垃圾回收机制就容易理解了。闭包可使i值记忆起来,在打印出来就可以依次打印1-10了

    	//每秒执行1次,分别输出1-10
    	for(var i=1;i<=10;i++){
    		(function(j){
    			//j来接收
    			setTimeout(()=>{
    				console.log(j);
    			},j*1000);
    		})(i)//i作为实参传入
    	}
    
  6. getter/setter

    这里体现了闭包的数据私有,在包里面封装getter和setter函数,return出去给外部使用,外部可以在不影响包内的值对name进行获取和修改

    	function fn(){
    		var name='hello'
    		setName=function(n){
    			name = n;
    		}
    		getName=function(){
    			return name;
    		}
    		
    		//将setName,getName作为对象的属性返回
    		return {
    			setName,
    			getName
    		}
    	}
    	var fn1 = fn();//返回对象,属性setName和getName是两个函数
    	console.log(fn1.getName());//getter
    		fn1.setName('world');//setter修改闭包里面的name
    	console.log(fn1.getName());//getter
    
  7. 迭代器(执行一次函数往下取一个值)

    闭包可以用作记录函数调用次数,迭代器也是利用这个方法,第一次调用函数i为0所以是arr[0],第二次调用i为1所以是arr[1],以此类推!如果不懂i为什么为0和1的可以看看++i和i++的区别(i++:先用后加)

    		var arr =['aa','bb','cc'];
    		function iterator(arr){
    			var i=0;
    			return function(){
    				//这个函数每次被执行都返回数组arr中 i下标对应的元素
    				 return arr[i++] || '数组值已经遍历完';
    			}
    		}
    		var next = iterator(arr);
    		console.log(next());//aa
    		console.log(next());//bb
    		console.log(next());//cc
    		console.log(next());//数组值已经遍历完
    
  8. 首次区分(相同的参数,函数不会重复执行)

    这里的fn就是return的函数,因为最后加了()自动执行让fn获取到return的函数,调用函数的时候通过indexOf()判断数组中是否存在,存在则不执行,否则反之。

             var fn = (function(){
                 var arr=[];//用来缓存的数组
                 return function(val){
                     if(arr.indexOf(val)==-1){//缓存中没有则表示需要执行
                         arr.push(val);//将参数push到缓存数组中
                         console.log('函数被执行了',arr);
                         //这里写想要执行的函数
                     }else{
                         console.log('此次函数不需要执行');
                     }
                     console.log('函数调用完打印一下,方便查看已缓存的数组:',arr);
                 }
             })();
    		fn(10); //执行
    		fn(10);	//不执行
    		fn(1000);	//执行
    		fn(200);	//执行
    		fn(1000);	//不执行
    
  9. 缓存

    通过 tSum = cache[key] 判断是否有缓存,有则返回值,无则undefined。

    补充:

    1. arguments(通过索引操作数据,也可以获取数据长度,所传递的实参都会在arguments中保存)

    2. join():通过连接输入数组中的所有条目来返回一个新字符串。如果数组只有一项,则该项将在不使用分隔符的情况下返回。

    3. slice():可从已有的数组中返回选定的元素 ===> array.slice(start, end)

    4. call():改变this的指向

      所以Array.prototype.slice.call(arguments)最后形成一个数组,可简写为:[].slice.call(arguments)

    //求和操作,如果没有缓存,每次调用都要重复计算,采用缓存已经执行过的去查找,查找到了就直接返回,不需要重新计算(能减轻机器压力)
    	 
    	 var fn=(function(){
    	 	var cache = {};//缓存对象
    	 	var calc = function(arr){//计算函数
    	 		var sum = 0;
    	 		//求和
    	 		for(var i = 0;i<arr.length;i++){
    		 		sum += arr[i]; //累加
    		 	}
    		 	return sum;
    	 	}
    	 	
    	 	return function(){
    	 		var args = Array.prototype.slice.call(arguments,0);//arguments转换成数组
    	 		var key=args.join(",");//将args用逗号连接成字符串
    		 	var result , tSum = cache[key];
    		 	if(tSum){//如果缓存有	
    		 		console.log('从缓存中取:',cache)//打印方便查看
    		 		result = tSum;
    		 	}else{
    		 		//重新计算,并存入缓存同时赋值给result
    			 	result = cache[key]=calc(args);
    			 	console.log('存入缓存:',cache)//打印方便查看
    		 	}
    		 	return result;
    	 	}
    	 })();
    	fn(1,2,3,4,5);
    	fn(1,2,3,4,5);
    	fn(1,2,3,4,5,6);
    	fn(1,2,3,4,5,8);
    	fn(1,2,3,4,5,6);
    
  10. 节流函数

    目的:降低回调函数的执行频率,节省计算资源

    区别:节流函数和防抖函数有点类似,但是不一样。防抖是当一个动作连续触发,只执行最后一次;节流是限制一个函数在一定时间内只能执行一次

    补充:apply:与call类似改变this的指向

 
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=GBK">
        <style>
        *{
        	margin:0;padding:0;
        }
        </style>
        </head>
        <body>
       		<div class="box" id="div_box" >
       			<button onclick="fn1()">test</button>
       		</div>
        </body>
        
<script>
//节流函数
function throttle(fn,delay){
	var flag=false;
	var timer=null; //用于记录定时器,方便清除
	return function(){
		var args=[].slice.call(arguments,0);//将参数转成数组
		var context=this;
		if(flag){//如果在限定的时间内 flag是true 则直接返回,不让执行
			return;
		}
		flag=true; //函数正在控制中
		//执行函数
		fn.apply(context,args);
		clearTimeout(timer);//清除定时器
		timer =setTimeout(function(){
			flag=false;//延时时间过了以后,放开函数控制
		},delay)
		
	}	
}
function fn(){
	console.log(123);
}
 
var fn1 = throttle(fn,2000);//绑定节流函数 
 
 
</script>
</html>

总结:

有数据的私有要求的时候会用到闭包,还有进行数据记录的时候也会用到闭包!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值