第一部分 基礎知識javascript
第 3 章 閉包和高階函數html
3.1 閉包java
3.1.1 變量的做用域node
變量的做用域,就是指變量的有效範圍。編程
//做用域範圍 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. 封裝變量app
//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 中,不少設計模式都是經過閉包和高階函數實現的。