闭包与高阶函数

文章详细阐述了JavaScript中的闭包概念,包括其作用于局部变量生命周期的延长,以及如何通过闭包实现数据封装和模拟私有变量。同时,讨论了高阶函数的应用,如回调函数、函数作为参数传递和返回值,以及如何利用高阶函数实现AOP和单例模式。文章还涉及到了内存泄露问题和解决方案,并举例说明了闭包在实际编程中的应用场景,如命令模式和函数节流。
摘要由CSDN通过智能技术生成

闭包

作用域

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

局部变量、全局变量。

变量的搜索是从内到外而非从外到内的。

变量的生命周期

对于全局变量莱索,全局变量的生命周期是永久的,除非我们主动销毁这个全局变量。

而对于函数内用var关键字生命的局部变量来说,当退出函数时,这些局部变量即失去了它们的价值,它们会随着函数调用的结束而被销毁。

闭包:f返回了一个匿名函数的应用,它可以访问到func()被调用时产生的环境,而局部变量a一直处于这个环境中,所以不会被销毁。局部变量的生命周期看起来被延续了。

	//现在来看看下面这段代码:
	var func = function(){
		var a = 1;
		return function(){
			a++;
			alert ( a );
		}
	};

	var f = func();

	f(); // 输出:2
	f(); // 输出:3
	f(); // 输出:4
	f(); // 输出:5
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<div>1111111111111</div>
	<div>2</div>
	<div>3</div>
	<div>4</div>
	<div>5</div>
	<script type="text/javascript">
	var nodes = document.getElementsByTagName('div');
	
	// 方法一:
	for (let i = 0; i < nodes.length; i++) {
		nodes[i].onclick = function(){
			alert(i)
		}
	}
	
	// 方法二:
	// for (var i = 0; i < nodes.length; i++) {
	// 	(function(a){
	// 		nodes[a].onclick = function(){
	// 			alert(a)
	// 		}
	// 	})(i)
	// }
	
	// 方法三:
	// for (var i = 0; i < nodes.length; i++) {
	// 	nodes[i].onclick = (() => {
	// 		var a = i;
	// 		return () => {
	// 			alert(a);
	// 		}
	// 	})();
	// }
	</script>
</body>
</html>

闭包的作用

  • 封装数据:闭包可以把一些不需要暴露在全局的变量封装成“私有变量”。


	//最好是把它们用闭包封闭起来。代码如下:
	var mult = (function(){
		var cache = {};
		var calculate = function(){ // 封闭calculate 函数
			var a = 1;
			for ( var i = 0, l = arguments.length; i < l; i++ ){
				a = a * arguments[i];
			}
			return a;
		};

		return function(){
			var args = Array.prototype.join.call( arguments, ',' );
			if ( args in cache ){
				return cache[ args ];
			}

			return cache[ args ] = calculate.apply( null, arguments );
		}

	})();
  • 延续局部变量的寿命

	//现在我们把img 变量用闭包封闭起来,便能解决请求丢失的问题:
	var report = (function(){
		var imgs = [];
		return function( src ){
			var img = new Image();
			imgs.push( img );
			img.src = src;
		}
	})();

闭包和面向对象设计

过程和数据的结合是形容面向对象中的“对象”时经常使用的表达。对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据。通常用面向对象思想能实现的功能,用闭包也能实现。

	//下面来看看这段跟闭包相关的代码:
	var extent = function(){
		var value = 0;
		return {
			call: function(){
				value++;
				console.log( value );
			}
		}
	};

	var extent = extent();
	extent.call(); // 输出:1
	extent.call(); // 输出:2
	extent.call(); // 输出:3

	//如果换成面向对象的写法,就是:
	var extent = {
		value: 0,
		call: function(){
			this.value++;
			console.log( this.value );
		}
	};

	extent.call(); // 输出:1
	extent.call(); // 输出:2
	extent.call(); // 输出:3

	//或者:

	var Extent = function(){
		this.value = 0;
	};

	Extent.prototype.call = function(){
		this.value++;
		console.log( this.value );
	};

	var extent = new Extent();

	extent.call();
	extent.call();
	extent.call();

用闭包实现命令模式

命令接受者会被封闭再闭包形成的环境中。


<script type="text/javascript">
	var Tv = {
		open: function(){
			console.log( '打开电视机' );
		},

		close: function(){
			console.log( '关上电视机' );
		}
	};

	var createCommand = function( receiver ){
		var execute = function(){
			return receiver.open(); // 执行命令,打开电视机
		}
		var undo = function(){
			return receiver.close(); // 执行命令,关闭电视机
		}
		return {
			execute: execute,
			undo: undo
		}
	};

	var setCommand = function( command ){
		document.getElementById( 'execute' ).onclick = function(){
			command.execute(); // 输出:打开电视机
		}
		document.getElementById( 'undo' ).onclick = function(){
			command.undo(); // 输出:关闭电视机
		}
	};

	setCommand( createCommand( Tv ) );

</script>

闭包和内存泄露

  • 内存泄露的原因:闭包不是罪魁祸首,全局作用域和闭包,对内存方便的影响是一样的,并不能说是内存泄露。是BOM和DOM引起的,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收。

  • 解决方法:手动将变量设为null。

高阶函数

高阶函数是指至少满足下列条件之一的函数:

  • 函数可以作为参数被传递;

  • 函数可以作为返回值输出;

函数作为参数传递

  • 回调函数

	var getUserInfo = function( userId, callback ){
		$.ajax( 'http://xxx.com/getUserInfo?' + userId, function( data ){
			if ( typeof callback === 'function' ){
				callback( data );
			}
		});
	}
	getUserInfo( 13157, function( data ){
		alert ( data.userName );
	});
  • Array.prototype.sort

	[ 1, 4, 3 ].sort( function( a, b ){
		return a - b;
	});

函数作为返回值输出

  • 判断数据的类型

	//我们还可以用循环语句,来批量注册这些isType 函数:
	var Type = {};
	for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){
		(function( type ){
			Type[ 'is' + type ] = function( obj ){
				return Object.prototype.toString.call( obj ) === '[object '+ type +']';
			}
		})( type )
	};

	Type.isArray( [] ); // 输出:true
	Type.isString( "str" ); // 输出:true
  • 单例模式

	var getSingle = function ( fn ) {
		var ret;
		return function () {
			return ret || ( ret = fn.apply( this, arguments ) );
		};
	};

高阶函数实现AOP

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。


	Function.prototype.before = function( beforefn ){
		var __self = this; // 保存原函数的引用
		return function(){ // 返回包含了原函数和新函数的"代理"函数
			beforefn.apply( this, arguments ); // 执行新函数,修正this
			return __self.apply( this, arguments ); // 执行原函数
		}
	};

	Function.prototype.after = function( afterfn ){
		var __self = this;
		return function(){
			var ret = __self.apply( this, arguments );
			afterfn.apply( this, arguments );
			return ret;
		}
	};

	var func = function(){
		console.log( 2 );
	};

	func = func.before(function(){
		console.log( 1 );
	}).after(function(){
		console.log( 3 );
	});

	func();

高阶函数的应用

  • currying:函数柯里化,又称部分求值,一个currying的函数首先会接受一些参数,接收了这些参数之后,该函数并不会立刻求值,而是继续返回另外一个函数,刚才传入的函数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

	var currying = function( fn ){
		var args = [];
		return function(){
			if ( arguments.length === 0 ){
				return fn.apply( this, args );
			}else{
				[].push.apply( args, arguments );
				return arguments.callee;
			}
		}
	};
	var cost = (function(){
		var money = 0;
		return function(){
			for ( var i = 0, l = arguments.length; i < l; i++ ){
				money += arguments[ i ];
			}
			return money;
		}
	})();

	var cost = currying( cost ); // 转化成currying 函数
	cost( 100 ); // 未真正求值
	cost( 200 ); // 未真正求值
	cost( 300 ); // 未真正求值
	alert ( cost() ); // 求值并输出:600
  • uncurrying:一个对象未必只能使用它自身的方法,可以使用别的对象的方法,比如call和apply就能实现。

	Function.prototype.uncurrying = function () {
		var self = this;
		return function() {
			var obj = Array.prototype.shift.call( arguments );
			return self.apply( obj, arguments );
		};
	};

	var push = Array.prototype.push.uncurrying();

	(function(){
		push( arguments, 4 );
		console.log( arguments ); // 输出:[1, 2, 3, 4]
	})( 1, 2, 3 );
  • 函数节流:

场景:比如window.resize事件,mousemove事件,上传进度

原理:指定时间间隔内只会执行一次任务。将即将被执行的函数用setTimeout延迟一段时间执行。如果该次延迟执行还没有完成,则忽略接下来的调用该函数的请求。

实现:

	var throttle = function ( fn, interval ) {
		var __self = fn, // 保存需要被延迟执行的函数引用
		timer, // 定时器
		firstTime = true; // 是否是第一次调用
		return function () {
			var args = arguments,
			__me = this;
			if ( firstTime ) { // 如果是第一次调用,不需延迟执行
				__self.apply(__me, args);
				return firstTime = false;
			}
			if ( timer ) { // 如果定时器还在,说明前一次延迟执行还没有完成
				return false;

			timer = setTimeout(function () { // 延迟一段时间执行
				clearTimeout(timer);
				timer = null;
				__self.apply(__me, args);
			}, interval || 500 );
		};
	};
  • 防抖:

场景:用户名输入后,判断用户是否存在

原理:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。

  • 分时函数

	var timeChunk = function( ary, fn, count ){
		var obj,
		t;
		var len = ary.length;
		var start = function(){
			for ( var i = 0; i < Math.min( count || 1, ary.length ); i++ ){
				var obj = ary.shift();
				fn( obj );
			}
		};
		return function(){
			t = setInterval(function(){
			if ( ary.length === 0 ){ // 如果全部节点都已经被创建好
				return clearInterval( t );
			}
			start();
			}, 200 ); // 分批执行的时间间隔,也可以用参数的形式传入
		};
	};

	var ary = [];
	for ( var i = 1; i <= 1000; i++ ){
		ary.push( i );
	};
	var renderFriendList = timeChunk( ary, function( n ){
		var div = document.createElement( 'div' );
		div.innerHTML = n;
		document.body.appendChild( div );
	}, 8 );
	renderFriendList();
  • 惰性加载函数

<html>
	<body>
		<div id="div1">点我绑定事件</div>
		<script>
			var addEvent = function( elem, type, handler ){
				if ( window.addEventListener ){
					addEvent = function( elem, type, handler ){
						elem.addEventListener( type, handler, false );
					}
				}else if ( window.attachEvent ){
					addEvent = function( elem, type, handler ){
						elem.attachEvent( 'on' + type, handler );
					}
				}
				addEvent( elem, type, handler );
			};

			var div = document.getElementById( 'div1' );
			addEvent( div, 'click', function(){
				alert (1);
			});
			addEvent( div, 'click', function(){
				alert (2);
			});
		</script>
	</body>
</html>

总结

很多模式都是通过闭包和高阶函数实现的。相对于模式的实现过程,我们更关注的是模式可以帮助我们完成什么!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值