JavaScript中的函數:閉包,this,高階函數
一.函數基本理論
function compare(val1,val2){ return val1 - val2; } var result = compare(5,10);
1,函數的定義沒什么意義,之后創建一個字符串,就是函數代碼
2,函數執行(被調用)的時候發生的事情:(以上面的代碼為例)
創建一個執行環境execution context ,該對象有一個特殊的屬性叫[scope chain] 作用域鏈,屬性的值是一個類數組對象,如上圖所示,第一個包含了,this,arguments,val1和val2的活動對象,第二個是包含了compare和result,this的活動對象。
理解函數的基本原理對于函數的理解函數閉包的概念很有幫助。
二.高階函數
1.函數作為參數傳遞
最經典的例子就是毀掉函數
var fs = require('fs'); fs.readFile('test.txt',function(data,err){ console.log(data); });
2.函數作為返回值
作為返回值時候,要注意此時的this指向。
3.函數柯里化
函數柯里化指首先接受一些參數,接受到的參數后不立即執行,而是返回一個新函數,剛才傳入的參數在函數形成的閉包中被保存起來,待到真正求值的時候剛才保存的參數才會真正的求值。
var cost = (function(){ var args = []; return function(){ if(arguments.length===0){ var money =0; for(var i-0;i<args.length;i++){ money+=args[i]; } return money; }else{ [].push.apply(args,arguments); } } })(); cost(100);//100 cost(200);//200 cost();//300
4.函數節流
函數節流的思想就是讓一些頻繁執行的函數減少執行頻率;比如因為瀏覽器窗口變化引起resize事件的頻繁執行,mouseover,上傳進度等等。
var throttle = function(fn,interval){ var _self = fn,timer,firstTime; 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); } }; window.onresize = throttle(function(){ console.log(1)},500);
代碼的解決辦法是利用定時器延遲執行,如果定時器在規定時間后還沒執行完,那么,下一次執行的時候就不會執行,直接返回;
5.分時函數
分時函數應用的場景比如,你的QQ好友有上千個,每一個好友是一個dom,這是加載的時候瀏覽器可能吃不消,就要用到setInterval函數來延遲加載。
//ary需要加載的數據,fn加載邏輯,count每一批加載的個數 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=0;i<1000;i++){ ary.push(i); } var renderFirendList = timeChunk(ary,function(n){ var div = document.createElement('div'); div.innerHTML = n; document.body.appendChild(div); },8); renderFirendList();
6.惰性加載函數
惰性 加載函數也很常見,比如瀏覽器嗅探中的時間綁定函數
var addEvent = function(elem,type,handler){ if(window.addEventListener){ return elem.addEventListener(type,handler,false); } if(window.addEvent){ return elem.addEvent('on'+type,handler); } }
以上代碼在非IE瀏覽器下每次都不會走第二個分支,并且每次添加一個事件就會執行一次判斷,雖然這不會增加性能開銷,但是可以利用惰性加載來解決
var addEvent = function(elem, type, handler){ if(window.addEventListener){ addEvent = function(elem, type, handler){ elem.addEventListener(type, handler, false) } }else if(window.addEvent){ addEvent = function(elem, type, handler){ elem.addEvent('on'+type, handler); } } addEvent(elem,type,handler); }
三.this
this的判斷只要記住一點,就是在執行的時候動態綁定的,而不是函數聲明時候綁定,以下是優先級
if(hava new){
this 就是new返回的這個對象
}else if(hava call,apply 綁定){
apply,call綁定的那個對象就是this
}else if(有對象調用){
this就是這個調用的對象
}else{
默認綁定到window //這種情況一般是閉包
}
window.name = 'globalname'; var obj = {}; obj.name = 'lucy'; obj.show = (function(){ console.log(this.name); return function(){ console.log(this.name)} })() obj.show(); VM928:6 globalname VM928:7 lucy
對于上面的代碼,obj.show定義為一個對象的方法,但是該方法立即執行,并且是在全局的作用域下執行的,所以輸出為globalname,當obj.show執行完之后返回了一個函數賦值給obj.show,說以obj.show此時才真正是對象的方法,所以第二個返回lucy,這個例子完美的證明了運行時動態綁定this。
四.閉包
閉包是有權訪問另外一個函數作用域中的變量的函數。《JavaScript高級程序設計第三版》。
典型的例子是一個內部函數訪問外部函數的變量,即使這個內部函數返回了或者是被調用了,仍然可以訪問外部變量,如下
function com(propertyName){ return function(obj1,obj2){ var value1 = obj1[propertyName]; var value2 = obj2[propertyName]; return value1 - value2; } } var obj1 ={'name':1}; var obj2 ={'name':2}; var compare = com('name'); console.log(compare(obj1,obj2));
上面例子中,匿名函數訪問了外部函數的局部變量propertyName,并且當它 返回了,而且是在其他地方被調用了 仍然可以訪問。比如com函數返回了一個匿名函數,并且在其他地方被調用了這個函數,但是仍然可以訪問propertyName變量對象,但是有一點就是,這里的propertyName是一個變量對象(活動對象)而不是變量本身,如果是在for等循環語句中就會出現錯誤。如下面的例子:
function foo(){ var result = []; for(var i = 0; i < 5; i++){ result[i] = function(){ return i; } } return result; } var s = foo(); console.log(s[1]());//5
上面代碼輸出結果是5的原因是每一個內部匿名函數包含的活動對象是i這個變量對象,所以最終foo()執行返回的每一個result[i](這里的i沒有變成5是因為它沒在匿名函數內),都是外部foo活動對象,所以最終結果就是5.避免這種結果的方法就是在外面繼續增加一層作用域,使每一個result[i]函數都持有自己i的活動對象。
function bar(){ var result = []; for(var j = 0; j < 5; j++){ result[j] = (function(num){ return function(){ return num; } })(j) } return result; }
這2段代碼的函數活動對象圖如下:
第一個代碼所有的result都引用函數foo中活動對象i所以當foo執行完返回后,i變量的值是5所以出現如上所示。
第二段代碼中,由于參數是按值復制傳遞的,所以j會一次賦值給num,最內層的匿名函數保存了3個活動對象,分別是立即執行函數,bar,和window,并且立即執行函數也是有5個,并且保存了5個num值,這樣就可以達到預期的效果。