JavaScript中的函數:閉包,this,高階函數

jopen 9年前發布 | 10K 次閱讀 閉包 JavaScript開發 JavaScript

一.函數基本理論

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

來自: http://www.cnblogs.com/bdbk/p/5100065.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!