第 3 章 闭包和高阶函数

第一部分 基础知识

第 3 章 闭包和高阶函数

3.1 闭包

3.1.1 变量的作用域

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

//作用域范围
	var func = function(){
		var a = 1;
		alert ( a ); // 输出: 1
	};
	func();
	alert ( a ); // 输出:Uncaught ReferenceError: a is not defined
        //嵌套函数
        var a = 1;
	var func1 = function(){
		var b = 2;
	var func2 = function(){
		var c = 3;
		alert ( b ); // 输出:2
		alert ( a ); // 输出:1
	}
	func2();
		alert ( c ); // 输出:Uncaught ReferenceError: c is not defined
	};
	func1();            

3.1.2 变量的生存周期

  对于全局变量来说,全局变量的生存周期是永久的;

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

        //生命周期
	var func = function(){
		var a = 1; // 退出函数后局部变量 a 将被销毁
		alert ( a );
	};
	func();

  

3.1.3 闭包的更多作用

  1. 封装变量

        //1. 封装变量
	var mult = (function(){
		var cache = {};
		var calculate = function(){
			var a = 1;
			for(var i = 0,l = arguments.length;i<l;i++){
				a = a * arguments[i];
				console.log(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);
		}
	})()
	console.log(mult( 1,2,3 ))
	console.log(mult( 1,2,3 ))

  2. 延续局部变量的寿命

3.1.4 闭包和面向对象设计

3.1.5 用闭包实现命令模式

  命令模式的意图是把请求封装为对象,从而分离请求的发起者和请求的接收者(执行者)之间的耦合关系。在命令被执行之前,可以预先往命令对象中植入命令的接收者。

<!--命令模式-->
<div>
	<button id='open'>打开</button>
	<button id='close'>关闭</button>
</div>

  

//命令模式
	var Tv = {
		open:function(){
			console.log("打开电视机");
		},
		close:function(){
			console.log("关闭电视机");
		}
	}

	var creatCommand = function(receiver){
		var open = function(){
			return receiver.open();
		}
		var close = function(){
			return receiver.close();
		}

		return {
			open:open,
			close:close
		}
	}

	var setCommand = function(command){
		document.getElementById("open").onclick = function(){
			command.open();
		}
		document.getElementById("close").onclick = function(){
			command.close();
		}
	}

	setCommand(creatCommand(Tv))

3.1.6 闭包与内存管理

  如果要解决循环引用带来的内存泄露问题,我们只需要把循环引用中的变量设为 null即可。

3.2 高阶函数

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

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

    1. 回调函数

    2.  Array.prototype.sort
3.2.2 函数可以作为返回值输出。

    1. 判断数据的类型 

    //判断数据的类型
	var isType = function( type ){
		return function( obj ){
			return Object.prototype.toString.call( obj ) === '[object '+ type +']';
		}
	};
	var isString = isType( 'String' );
	var isArray = isType( 'Array' );
	var isNumber = isType( 'Number' );
	console.log( isArray( [ 1, 2, 3 ] ) ); // 输出:true

  

	//判断数据的类型
	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)
	}
	console.log(Type.isArray([ 1, 2, 3 ] ));
	console.log(Type.isString('str'));

    2.  getSingle单例模式

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

	var getScript = getSingle(function(){
		return document.createElement('script');
	})
	var script1 = getScript();
	console.log(script1);

3.2.3 高阶函数实现AOP

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

在JavaScript中实现AOP,都是指把一个函数“动态织入”到另一个函数之中。

//通过扩展 Function.prototype来实现面向切面
	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 ref = _self.apply(this, arguments);
			afterfn.apply(this.arguments);
			return ref;
		}
	}

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

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

3.2.4 高阶函数的其他应用

  1.  currying

  函数柯里化(function currying):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(600);//未真正求值
	cost(900);//未真正求值
	console.log(cost())//求值

  2.  uncurrying

  在javaScript中,当我们调用对象的某个方法时,其实不用去关心该对象原本是否被设计为拥有该方法,这是动态类型语方的特点,也就是鸭子类型思想。一个对象未必只能使用它自身的方法,可以让对象去借用一个原本不属于它的方法。call和apply都可以完成这个需求:

	//借用call、apply
	var obj1 = {
		name:'name1'
	}
	var obj2 = {
		getName:function(){
			return this.name;
		}
	}
	console.log(obj2.getName.apply(obj1));//obj1借用obj2的getName方法

	// Array.prototype
	(function(){
		Array.prototype.push.call(arguments,4,5);//arguments借用Array.prototype.push方法
		console.log(arguments)
	})(1,2,3)

  

	//把泛化this的过程提取出来
	Function.prototype.currying = function(){
		var _self = this;// self 此时是 Array.prototype.push
		return function(){
			var obj = Array.prototype.shift.call(arguments);//arguments 对象的第一个元素被截去
			return _self.apply(obj,arguments);// 相当于 Array.prototype.push.apply( obj, 2 )
		}
	}
	for(var i=0,fn, ary = ['push','shift','forEach'];fn=ary[i++];){
		Array[fn] = Array.prototype[fn].currying();
	}

	var obj = {
		'length':'3',
		'0':1,
		'1':2,
		'2':3
	}

	Array.push(obj,4);//向对象中添加一个元素
	console.log(obj);

	var first = Array.shift(obj);//截取第一个元素
	console.log(first);//输出:1

	Array.forEach(obj,function(item,i){
		console.log(item);//输出2,3,4
	})

  

	//uncurrying另一种实现方式
	Function.prototype.currying = function(){
		var _self = this;
		return function(){
			return Function.prototype.call.apply(_self,arguments);
		}
	}

  3. 函数节流

  函数的触发不是由用户直接控制的时候,函数有可能会频繁的被调用,造成大的性能问题。

  (1)函数被频繁调用的场景:

   window.onresize 事件。当浏览器窗口大小被拖动而改变的时候。

   mousemove 事件。拖 拽事件。

    上传进度。

  (2) 函数节流的原理:

  上述三个场景面临的共同问题是函数被触发频率太高。可以借助setTimeout来完成这件事情。

  (3) 函数节流的代码实现

	//函数节流
	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)
		}
	}

  4. 分时函数

  由于用户调用的,导致一些函数严重影响页面性能。

	/*
	* 分时函数
	* ary:创建节点时用到的数据
	* fn:封装创建节点辑的函数
	* count每一批创建节点的数量
	*/
	var timeChunk = function(ary, fn, count){
		var timer;
		var start = function(){
			for(var i = 0;i<Math.min(count || 1,ary.length);i++){
				var obj = ary.shift();
				fn(obj);
			}
		};
		return function(){
			timer = setInterval(function(){
				if(ary.length === 0){//如果全部节点创建完毕
					return clearInterval(timer);
				}
				start();
			},200)
		}
	

	var ary = [];

	for(var i = 0;i<1000;i++){
		ary.push(i);
	}

	var renderFriendList = timeChunk(ary,function(node){
		var div = document.createElement('div');
		div.innerHTML = node;
		document.body.appendChild(div);
	},8);

	renderFriendList();

  5. 惰性加载函数

  避免每次执行都会进行判断,让程序避免这些重复的执行过程。

	//惰性加载函数
	var addEvent = function(elem, type, handler){
		if(window.addEventListener){
              //对addEvent函数进行重写,当下次再进入addEvent的时候不再重复进行判断 addEvent = function(elem, type, handler){ elem.addEventListener(type, handler, false); } }else if(window.attachEvent){ addEvent = function(elem, type, handler){ elem.attachEvent('on'+type,handler,false); } } addEvent(elem,type,handler); }; var div = document.getElementById('div1'); addEvent(div,'click',function(){ console.log(1); }); addEvent(div,'click',function(){ console.log(2); });

 3.3 小结

  许多设计模式在 JavaScript 之中的实现跟在一些传统面向对象语言中的实现相差很大。在JavaScript 中,很多设计模式都是通过闭包和高阶函数实现的。

原文地址:https://www.cnblogs.com/huyanluanyu/p/9923749.html