zoukankan      html  css  js  c++  java
  • 第 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 中,很多设计模式都是通过闭包和高阶函数实现的。

  • 相关阅读:
    Eclipse的安装及汉化图解
    Intent常用使用汇总
    Android Notification (转)
    垃圾回收
    svn常见错误汇总
    位运算
    FusionCharts简单教程(一)---建立第一个FusionCharts图形
    Delphi发送邮件...
    协程库的一些笔记
    学习日记之单例模式和Effective C++
  • 原文地址:https://www.cnblogs.com/huyanluanyu/p/9923749.html
Copyright © 2011-2022 走看看