深刻理解javascript函數系列第一篇——函數概述

前面的話

  函數對任何一門語言來講都是核心的概念。經過函數能夠封裝任意多條語句,並且能夠在任何地方、任什麼時候候調用執行。在javascript裏,函數即對象,程序能夠隨意操控它們。函數能夠嵌套在其餘函數中定義,這樣它們就能夠訪問它們被定義時所處的做用域中的任何變量,它給javascript帶來了很是強勁的編程能力。本文是深刻理解javascript函數系列第一篇——函數概述javascript

 

函數定義

  總共有三種函數定義的方式html

【1】函數聲明語句java

  使用function關鍵字,後跟一組參數以及函數體chrome

function funcname([arg1 [,arg2 [...,argn]]]){
    statement;
}

  funcname是要聲明的函數名稱的標識符。函數名以後的圓括號中是參數列表,參數之間使用逗號分隔。當調用函數時,這些標識符則指代傳入函數的實參express

  [注意]function語句裏的花括號是必需的,這和while循環和其餘一些語句所使用的語句塊是不一樣的,即便函數體內只包含一條語句,仍然必須使用花括號將其括起來編程

function test()//SyntaxError: Unexpected end of input
function test(){};//不報錯
while(true);//不報錯

提高數組

  在做用域系列博文的第三篇中,提到過函數聲明提高(hoisting),函數名稱和函數體都提高瀏覽器

foo();
function foo(){
    console.log(1);//1
}

  上面這個代碼片斷之因此可以在控制檯輸出1,就是由於foo()函數聲明進行了提高,以下所示:app

function foo(){
    console.log(1);
}
foo();

重複函數

  變量的重複聲明是無用的,但函數的重複聲明會覆蓋前面的聲明(不管是變量仍是函數聲明)

//變量的重複聲明無用
var a = 1;
var a;
console.log(a);//1
//因爲函數聲明提高優先於變量聲明提高,因此變量的聲明無做用
var a;
function a(){
    console.log(1);
}
a();//1
//後面的函數聲明會覆蓋前面的函數聲明
a();//2
function a(){
    console.log(1);
}
function a(){
    console.log(2);
}

  因此,應該避免在同一做用域中重複聲明

刪除

  和變量聲明同樣,函數聲明語句建立的變量沒法刪除

function foo(){
    console.log(1);
}
delete foo;//false
console.log(foo());//1

【2】函數定義表達式

  以表達式方式定義的函數,函數的名稱是可選的

var functionName = function([arg1 [,arg2 [...,argn]]]){
    statement;
}

var functionName = function funcName([arg1 [,arg2 [...,argn]]]){
    statement;
}

  匿名函數(anonymous function)也叫拉姆達函數,是function關鍵字後面沒有標識符的函數

  一般而言,以表達式方式定義函數時都不須要名稱,這會讓定義它們的代碼更加緊湊。函數定義表達式特別適合用來定義那些只會使用一次的函數

var tensquared = (function(x) {return x*x;}(10));

  而一個函數定義表達式包含名稱,函數的局部做用域將會包含一個綁定到函數對象的名稱。實際上,函數的名稱將成爲函數內部的一個局部變量

var test = function fn(){
   return fn;
}
console.log(test);//fn(){return fn;}
console.log(test());//fn(){return fn;}
console.log(test()());//fn(){return fn;}

  我的理解,對於具名的函數表達式來講,函數名稱至關於函數對象的形參,只能在函數內部使用;而變量名稱至關於函數對象的實參,在函數內部和函數外部均可以使用

var test = function fn(){
   return fn === test;
}
console.log(test());//true
console.log(test === fn);//ReferenceError: fn is not defined

  函數定義了一個非標準的name屬性,經過這個屬性能夠訪問到給定函數指定的名字,這個屬性的值永遠等於跟在function關鍵字後面的標識符,匿名函數的name屬性爲空

//IE11-瀏覽器無效,均輸出undefined
//chrome在處理匿名函數的name屬性時有問題,會顯示函數表達式的名字
function fn(){};
console.log(fn.name);//'fn'
var fn = function(){};
console.log(fn.name);//'',在chrome瀏覽器中會顯示'fn'
var fn = function abc(){};
console.log(fn.name);//'abc'

【3】Function構造函數

  Function構造函數接收任意數量的參數,但最後一個參數始終都被當作是函數體,而前面的參數則枚舉出了新函數的參數

var functionName = new Function(['arg1' [,'arg2' [...,'argn']]],'statement;');

  [注意]Function構造函數沒法指定函數名稱,它建立的是一個匿名函數

  從技術上講,這是一個函數表達式。但,不推薦使用,由於這種語法會致使解析兩次代碼。第一次是解析常規javascript代碼,第二次解析傳入構造函數中的字符串,影響性能

var sum = new Function('num1','num2','return num1 + num2');
//等價於
var sum = function(num1,num2){
    return num1+num2;
}

  Function()構造函數建立的函數,其函數體的編譯老是會在全局做用域中執行。因而,Function()構造函數相似於在全局做用域中執行的eval()

var test = 0;
function fn(){
    var test = 1;
    return new Function('return test');
}
console.log(fn()());//0

  [注意]並非全部的函數均可以成爲構造函數

var o = new Math.min();//Uncaught TypeError: Math.min is not a constructor

 

函數返回值

  函數中的return語句用來返回函數調用後的返回值

return expression;

  return語句只能出如今函數體內,若是不是會報語法錯誤

return 1;//SyntaxError: Illegal return statement

  若是沒有return語句,則函數調用僅僅依次執行函數體內的每一條語句直到函數結束,最後返回調用程序。這種狀況下,調用表達式的結果是undefined

var test = function fn(){}
console.log(test());//undefined

  當執行到return語句時,函數終止執行,並返回expression的值給調用程序

var test = function fn(){
    return 2;
};
console.log(test());//2

  [注意]並非函數中return語句後的全部語句都不執行,finally語句是例外,return語句不會阻止finally子句的執行

function testFinnally(){
    try{
        return 2;
    }catch(error){
        return 1;
    }finally{
        return 0;
    }
}
testFinnally();//0

  [注意]因爲javascript能夠自動插入分號,所以在return關鍵字和它後面的表達式之間不能有換行

var test = function fn(){
    return
    2;
};
console.log(test());//undefined

  一個函數中能夠有多個return語句

function diff(iNum1, iNum2) {
  if (iNum1 > iNum2) {
    return iNum1 - iNum2;
  } else {
    return iNum2 - iNum1;
  }
}

  return語句能夠單獨使用而沒必要帶有expression,這樣的話也會向調用程序返回undefined

var test = function fn(){
    return;
};
console.log(test());//undefined

  return語句常常做爲函數內的最後一條語句出現,這是由於return語句可用來使函數提早返回。當return被執行時,函數當即返回而再也不執行餘下的語句

//並無彈出1
var test = function fn(){
    return;
    alert(1);
};
console.log(test());//undefined

  若是函數調用時在前面加上了new前綴,且返回值不是一個對象,則返回this(該新對象)

function fn(){
    this.a = 2;
    return 1;
}
var test = new fn();
console.log(test);//{a:2}
console.log(test.constructor);//fn(){this.a = 2;return 1;}

  若是返回值是一個對象,則返回該對象

function fn(){
    this.a = 2;
    return {a:1};
}
var test = new fn();
console.log(test);//{a:1}
console.log(test.constructor);//Object() { [native code] }

 

函數調用

  只有函數被調用時,纔會執行。調用運算符是跟在任何產生一個函數值的表達式以後的一對圓括號,圓括號內可包含零個或多個用逗號隔開的表達式。每一個表達式產生一個參數值,每一個參數值被賦予函數聲明時定義的形參名

  javascript一共有4種調用模式:函數調用模式、方法調用模式、構造器調用模式和間接調用模式

【1】函數調用模式

  當一個函數並不是一個對象的屬性時,那麼它就是被當作一個函數來調用的。對於普通的函數調用來講,函數的返回值就是調用表達式的值

function add(x,y){
    return x+y;
}
var sum = add(3,4);
console.log(sum)//7

  使用函數調用模式調用函數時,非嚴格模式下,this被綁定到全局對象;在嚴格模式下,this是undefined

function add(x,y){
    console.log(this);//window
}    
add();
function add(x,y){
    'use strict';
    console.log(this);//undefined
}    
add();//window

  所以,'this'能夠用來判斷當前是不是嚴格模式

var strict = (function(){return !this;}());

重寫

  由於函數調用模式的函數中的this綁定到全局對象,因此會發生全局屬性被重寫的現象

var a = 0;
function fn(){
    this.a = 1;
}
fn();
console.log(this,this.a,a);//window 1 1

【2】方法調用模式

  當一個函數被保存爲對象的一個屬性時,咱們稱它爲一個方法。當一個方法被調用時,this被綁定到該對象。若是調用表達式包含一個提取屬性的動做,那麼它就是被當作一個方法來調用

var o = {
    m: function(){
        console.log(1);
    }
};
o.m();//1

  方法可使用this訪問本身所屬的對象,因此它能從對象中取值或對對象進行修改。this到對象的綁定發生在調用的時候。經過this可取得它們所屬對象的上下文的方法稱爲公共方法

var o = {
    a: 1,
    m: function(){
        return this;
    },
    n: function(){
        this.a = 2;
    }
};
console.log(o.m().a);//1
o.n();
console.log(o.m().a);//2

  任何函數只要做爲方法調用實際上都會傳入一個隱式的實參——這個實參是一個對象,方法調用的母體就是這個對象,一般來說,基於那個對象的方法能夠執行多種操做,方法調用的語法已經很清晰地代表了函數將基於一個對象進行操做

rect.setSize(width,height);
setRectSize(rect,width,height);

  假設上面兩行代碼的功能徹底同樣,它們都做用於一個假定的對象rect。能夠看出,第一行的方法調用語法很是清晰地代表這個函數執行的載體是rect對象,函數中的全部操做都將基於這個對象

  和變量不一樣,關鍵字this沒有做用域的限制,嵌套的函數不會從調用它的函數中繼承this。若是嵌套函數做爲方法調用,其this的值指向調用它的對象。若是嵌套函數做爲函數調用,其this值不是全局對象(非嚴格模式下)就是undefined(嚴格模式下)

var o = {
    m: function(){
         function n(){
             return this;
         }
         return n();
    }
}
console.log(o.m());//window
var o = {
    m: function(){
         function n(){
             'use strict';
             return this;
         }
         return n();
    }
}
console.log(o.m());//undefined

  若是想訪問這個外部函數的this值,須要將this的值保存在一個變量裏,這個變量和內部函數都同在一個做用域內。一般使用變量self或that來保存this

var o = {
    m: function(){
        var self = this;
        console.log(this === o);//true
         function n(){
             console.log(this === o);//false
             console.log(self === o);//true
             return self;
         }
         return n();
    }
}
console.log(o.m() === o);//true

【3】構造函數調用模式

  若是函數或者方法調用以前帶有關鍵字new,它就構成構造函數調用

function fn(){
    this.a = 1;
};
var obj = new fn();
console.log(obj.a);//1

  若是構造函數調用在圓括號內包含一組實參列表,先計算這些實參表達式,而後傳入函數內

function fn(x){
    this.a = x;
};
var obj = new fn(2);
console.log(obj.a);//2

  若是構造函數沒有形參,javascript構造函數調用的語法是容許省略實參列表和圓括號的。凡是沒有形參的構造函數調用均可以省略圓括號

var o = new Object();
//等價於
var o = new Object;

  [注意]儘管構造函數看起來像一個方法調用,它依然會使用這個新對象做爲調用上下文。也就是說,在表達式new o.m()中,調用上下文並非o

var o = {
    m: function(){
        return this;
    }
}
var obj = new o.m();
console.log(obj,obj === o);//{} false
console.log(obj.constructor === o.m);//true

  構造函數一般不使用return關鍵字,它們一般初始化新對象,當構造函數的函數體執行完畢時,它會顯式返回。在這種狀況下,構造函數調用表達式的計算結果就是這個新對象的值

function fn(){
    this.a = 2;
}
var test = new fn();
console.log(test);//{a:2}

  若是構造函數使用return語句但沒有指定返回值,或者返回一個原始值,那麼這時將忽略返回值,同時使用這個新對象做爲調用結果

function fn(){
    this.a = 2;
    return;
}
var test = new fn();
console.log(test);//{a:2}

  若是構造函數顯式地使用return語句返回一個對象,那麼調用表達式的值就是這個對象

var obj = {a:1};
function fn(){
    this.a = 2;
    return obj;
}
var test = new fn();
console.log(test);//{a:1}

【4】間接調用模式

  javascript中函數也是對象,函數對象也能夠包含方法。call()和apply()方法能夠用來間接地調用函數

  這兩個方法都容許顯式指定調用所需的this值,也就是說,任何函數能夠做爲任何對象的方法來調用,哪怕這個函數不是那個對象的方法。兩個方法均可以指定調用的實參。call()方法使用它自有的實參列表做爲函數的實參,apply()方法則要求以數組的形式傳入參數

var obj = {};
function sum(x,y){
    return x+y;
}
console.log(sum.call(obj,1,2));//3
console.log(sum.apply(obj,[1,2]));//3

 

參考資料

【1】  W3School-Javascript高級教程——函數概述 http://www.w3school.com.cn/js/pro_js_functions.asp
【2】  阮一峯Javascript標準參考教程——函數概述 http://javascript.ruanyifeng.com/grammar/function.html【3】《javascript權威指南(第6版)》第8章 函數【4】《javascript高級程序設計(第3版)》第3章 基本概念【5】《javascript DOM編程藝術(第2版)》第2章 javascript語法【6】《javascript語句精粹》第4章 函數

相關文章
相關標籤/搜索