JavaScript中的Function類型

Function與函數

函數是一段JavaScript代碼,它只定義一次,但可能被執行或調用屢次
Function類型是JavaScript提供的引用類型之一,經過Function類型建立Function對象
在JavaScript中,函數也是以對象的形式存在的。每一個函數都是一個Function對象
函數名,本質就是一個變量名,是指向某個Function對象的引用javascript

function fn(){
    console.log('前端');
}
console.log(fn instanceof Function);//true

構造函數

在JavaScript中,函數除了能夠經過函數定義語句或字面量表達式兩種方式定義以外,還能夠經過Function類型進行定義前端

var fn=new Function('num1','num2','var sum = num1+num2;return sum');

經過Function類型定義函數的效率遠不如經過函數定義語句或字面量表達式兩種方式定義
目前,定義函數具備三種方式,這三種方式之間存在必定差異
函數定義語句:函數名被聲明提早,不存在效率問題
字面量表達式:函數體固定,沒法動態執行,不存在效率問題
Function類型定義:函數體是字符串,能夠動態執行,效率低java

Function的apply()方法

Function的apply()方法用於調用一個函數,而且接收指定的this值,以及一個數組做爲參數,其語法結構以下:數組

func.apply(thisArg,[argsArray])
  • thisArg參數:可選項,在func函數運行時使用的this值
  • argsArray參數:可選項,一個數組或者類數組對象,其中的數組元素將做爲單獨的參數傳給func函數。也可使用arguments對象做爲該參數
  • 返回值:調用該函數的返回結果
var numbers=[1,2,3,4,5];
//經過apply()方法獲取數組中最大值和最小值
var max=Math.max.apply(null,numbers);
var min=Math.min.apply(null,numbers);

Function的call()方法

Function的call()方法用於調用一個函數,而且接收指定的this值做爲參數,以及參數列表。其語法結構以下:緩存

func.call(thisArg,arg1,arg2,...)
  • thisArg參數:在func函數運行時使用的this值
  • arg1,arg2,...參數:指定的參數列表
  • 返回值:調用該函數的返回結果

apply()與call()很是類似,不一樣之處在於提供參數的方式閉包

//經過call()方法獲取數組中最大值和最小值
var max=Math.max.call(null,5,6,2,3,7);
var min=Math.min.call(null,5,6,2,3,7);

Function的bind()方法

Function的bind()方法用於建立一個新的函數(稱爲綁定函數),而且接收指定的this值做爲參數,以及參數列表,其語法結構以下:app

fun.bind(thisArg[,arg1[,arg2[,...]]])
  • thisArg參數:當綁定函數被調用時,該參數會做爲原函數運行時的this指向
  • arg1,arg2,...參數:當綁定函數被調用時,這些參數將置於實參以前傳遞給被綁定的方法
  • 返回值:返回由指定的this值和初始化參數改造的原函數

Function的bind()方法示例以下:函數

this.x=9;
var module={
    x:81,
    getX:function(){return this.x;}
};

module.getX();//返回81

var retrieveX=module.getX;
retrieveX();//返回9,在這種狀況下,'this'指向全局做用域

//建立一個新函數,將'this'綁定到module對象
var boundGetX=retrieveX.bind(module);
boundGetX();//返回 81

沒有重載

在其餘開發語言中,函數具備一種特性,叫作重載。所謂重載,就是定義多個同名的函數,但每個函數接收的參數的個數不一樣,程序會根據調用時傳遞的實參個數進行判斷,具體調用的是哪一個函數。以下示例:oop

function add(a,b){
    return a+b;
}
function add(a,b,c){
    return a+b+c;
}
add(1,2);//結果爲3
add(1,2,3);//結果爲6

但在JavaScript中,函數是沒有重載現象的,也就是說,若是同時定義多個同名的函數,只有最後一個定義的函數是有效的。性能

function add(a,b){
    return a+b;
}
function add(a,b,c){
    return a+b+c;
}
add(1,2);//結果爲NaN
add(1,2,3);//結果爲6

不過,JavaScript提供了arguments對象,該對象能夠模擬函數重載的現象。arguments對象是函數內部的本地變量;arguments已經再也不是函數的屬性了。
arguments對象能夠獲取函數的全部參數,但arguments對象並非一個數組,而是一個類數組對象(沒有數組特有的方法)
arguments對象的屬性以下所示:
callee:表示當前執行的函數
length:表示傳遞給當前函數的參數數量
利用arguments對象實現模擬函數的重載現象,以下述示例

function doAdd(){
    if(arguments.length==1){
        console.log(arguments[0]+5);
    }else if(arguments.length==2){
        console.log(arguments[0]+arguments[1]);
    }
}

doAdd(10);//輸出'15'
doAdd(40,20);//輸出'60'

遞歸

在一個函數的函數體內,若是想調用自身函數的話,有以下兩種方式:

  • 經過使用自身函數名實現
  • 經過使用arguments對象的callee屬性實現

調用自身的函數被稱之爲遞歸函數,在某種意義上說,遞歸近似於循環。二者都重複執行相同的代碼,而且二者都須要一個終止條件以免無限循環或者無限遞歸。

function loop(x){
    if(x>=10){return;}
    loop(x+1);
}
loop(0);

上述代碼是一個經典的遞歸函數。雖然這個函數表面看起來並無什麼問題,若是執行下述代碼可能會致使出錯。

var anotherLoop=loop;
loop=null;
anotherLoop(0);//出錯

上述代碼將函數loop()保存到另外一個變量anotherLoop中,而後將loop設置爲null值。當執行anotherLoop時,必定會執行函數loop(),而loop已經再也不是一個函數,最終致使出錯
要想解決上述遞歸函數的問題,可使用arguments對象的callee屬性替換具體的函數名

function loop(x){
    if(x>=10){
        return;
    }
    arguments.callee(x+1);
}

匿名函數

JavaScript能夠將函數做爲數據使用,做爲函數本體,它像普通的數據同樣,不必定要有名字。默認名字的函數被稱之爲匿名函數。以下示例:

function(a){return a}

匿名函數的兩種方法:

  • 能夠將匿名函數做爲參數傳遞給其餘函數。這樣,接收方函數就能利用所傳遞的函數來完成某些事情
  • 能夠定義某個匿名函數來執行某些一次性任務

回調函數

當一個函數做爲參數傳遞給另外一個函數時,做爲參數的函數被稱之爲回調函數

function add(a,b){
    return a()+b();
}

var one=function(){return 1;}
var two=function(){return 2;}

console.log(add(one,two));//output 3
//能夠直接使用匿名函數來替代one()和two(),以做爲目標函數的參數
console.log(add(function(){return 1;},function(){return 2;}));

上述代碼中,函數one()和two()都做爲函數add()的參數傳遞。因此函數one()和two()都是回調函數。當將函數A傳遞給函數B,並由B來執行A時,A就成了一個回調函數,若是A仍是一個無名函數,就稱之爲匿名回調函數
回調函數優勢以下:

  • 它能夠在不作命名的狀況下傳遞函數(這意味着能夠節省全局變量)
  • 能夠將一個函數調用操做委託給另外一個函數(這意味着能夠節省一些代碼編寫工做)
  • 回調函數也有助於提高性能

自調函數

所謂自調函數就是在定義函數後自行調用。以下示例:

(function(){
    console.log('javascript');
})();

上述代碼的含義以下:

  • 第一對括號的做用,放置的是一個匿名函數。
  • 第二對括號的做用,是'當即調用'

自調函數只需將匿名函數的定義放進一對括號中,而後外面再跟一對括號便可
自調函數也能夠在調用時接收參數,以下示例:

(function(name){
    console.log('hello'+name+'!');
})('javascript');//hello javascript

上述代碼的含義以下:

  • 第一個括號中的匿名函數接受一個參數
  • 第二個括號,在調用時,向匿名函數傳遞參數內容

做爲值的函數

將一個函數做爲另外一個函數的結果進行返回,做爲結果返回的函數稱之爲做爲值得函數

function fn(f,args){
    return f(args);
}
function add(num){//做爲值的函數
    return num+10;
}

var result=fn(add,10);
console.log(result);//20

上述代碼還能夠編寫成以下方式:

function fn(args){
    return function add(){
        return args+10;
    }
}

上述兩段代碼的區別在於

var f=fn(10);//function add(){return 10+10}
var result=f();//20

做用域鏈

不少開發語言中都具備塊級做用域,但ECMAScript5版本中並無跨級做用域,這常常會致使理解上的困惑。以下示例:

if(true){
    var color='blue';
}
console.log(color);//blue

上述代碼在if語句中定義了變量color。但該變量的做用域是全局域,緣由是ECMAScript5版本中沒有塊級做用域
雖然ECMAScript5版本沒有塊級做用域,但具備函數做用域。在某個函數內部定義的變量的做用域就是該函數做用域。

function fun(){
    var v='this is function';
}
console.log(v);//輸出報錯

上述代碼在函數fun內部定義了變量v,該變量的做用域是fun函數做用域。因此在全局域訪問該變量時會出錯
每一段JavaScript(全局代碼或函數)都有一個與之關聯的做用域鏈。這個做用域鏈是一個對象列表或鏈表,這組對象定義了這段代碼'做用域中'的變量。以下示例:

var a=1;
//在全局域中只能訪問變量a
function f(){
    //在f函數做用域中能夠訪問變量a和b
    var b=2;
}

閉包

JavaScript容許函數嵌套,而且內部函數能夠訪問定義在外部函數中的全部變量和函數,以及外部函數能訪問的全部變量和函數。可是,外部函數卻不可以訪問定義在內部函數中的變量和函數。
當內部函數以某一種方式被任何一個外部函數做用域訪問時,一個閉包就產生了
閉包就是該函數能使用函數外定義的變量
以下代碼就是一個最簡單形式的閉包結構:

var b;
function f(){
    var a='a';
    b=function(){
        return a+'b';
    }
    return a;
}
//測試
console.log(f());//a
console.log(b());//ab

閉包的特色與做用
閉包的特色:

  • 局部變量:在函數中定義有共享意義(如:緩存、計數器等)的局部變量。(注意:定義成全局變量會對外形成污染)
  • 內部函數:在函數(f)中聲明有內嵌函數,內嵌函數(g)對函數(f)中的局部變量進行訪問
  • 外部使用:函數(f)向外返回此內嵌函數(g),外部能夠經過此內嵌函數持有並訪問聲明在函數(f)中的局部變量,而此變量在外部是經過其餘途徑沒法訪問的

閉包的做用

  • 提供可共享的局部變量
  • 保護共享的局部變量。提供專門的讀寫變量的函數
  • 避免全局污染

閉包的應用
利用閉包保護共享的局部變量,提供專門的讀寫變量的函數

var getValue,setValue;
(function(){
    var secret=0;
    getValue=function(){
        return secret;
    };
    setValue=function(v){
        secret=v;
    }
})();
相關文章
相關標籤/搜索