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值,這樣就可以達到預期的效果。