180327 函數

概述

函數是這樣一段代碼,它只定義一次,但可能被執行或調用多次。

函數定義會包括一個形參的標識符列表,這些參數在函數體中像局部變量一樣工作。函數調用會爲形參提供實參的值。函數使用它們實參的值來計算返回值,成爲該函數調用表達式的值。

除了實參之外,每次調用還會擁有另一個值,本次調用的上下文,這就是this關鍵字的值

如果函數掛載在一個對象上,作爲對象的一個屬性,就稱它爲對象的方法。當通過這個對象來調用函數時,該對象就是此次調用的上下文,也就是該函數的this的值

用於初始化一個新創建的對象的函數稱爲構造函數。

在JS裏,函數即對象。JS可以把函數賦值給變量,或者作爲參數傳遞給其它函數。

JS函數可以嵌套在其他函數中定義,這樣它們就可以訪問他們被定義時所處的作用域中的任何變量。這意味着JS函數構成了一個閉包。


1函數定義

1.1函數組成:

  • 名稱標識符(用途就像變量的名字一樣,新定義的函數對象會賦值給這個變量。)
  • 一對圓括號:參數列表
  • 一對花括號:語句塊

1.2.1函數聲明語句:

  • 一條函數聲明語句實際上聲明瞭一個變量,並把一個函數對象賦值給它。
  • 函數聲明語句“被提前”到外部腳本或外部函數作用域的頂部,這種方式聲明的函數,可以被在它定義之前出現的代碼所調用。
  • 函數聲明語句並非真正的語句,ES規範只是允許它們作爲頂級語句,它們可以出現在全局代碼裏,或者嵌套其他函數。但不能出現在循環條件判斷語句中。
function  funcName(){}

1.2.2函數定義表達式:

  • 相對而言定義函數表達式時並沒有聲明一個變量。

  • 函數表達式定義的函數,函數名是可選的。

  • 如果一個函數定義表達式包含名稱,函數的名稱將成爲函數內部的一個局部變量。
  • 通常而言,以表達式方式定義函數時不需要名稱,適合用來定義只會使用到一次的函數。
  • 以表達式方式定義的函數在定義之前無法調用。爲了調用一個函數,必須要能引用它,而要使一個以表達式方式定義的函數之前,必須把它賦值給一個變量。變量的聲明提前了,但是給變量賦值是不會提前的。
  • 函數定義表達式可以出現在任何地方。
var a = function (){}

1.3函數命名:

  • 函數名通常是動詞或動詞詞組
  • 通常函數名第一個字符小寫,這是一種編程約定
  • 當包含多個單詞時,一種約定是下劃線,一種是小駝峯
  • 有一些函數是內部函數或私有函數,函數名前加下劃線

1.4返回值:

  • return語句導致函數停止執行
  • 返回它的表達式的值給調用者
  • 如果沒有一個相關的表達式,返回undefined
  • 如果函數中沒有return語句,則執行函數中每條語句,返回undefined
  • 沒有返回值的函數有時候稱作過程

1.5嵌套函數

  • 嵌套函數變量作用域規則:
    • 他們可以訪問嵌套它們的函數的參數和變量。

2函數調用

構成函數主體的JS代碼在定義之時並不會執行,只有調用該函數時,它們纔會執行。

2.1調用方式:

  • 作爲函數的函數調用
//在一個調用中,每個參數表達式會計算出一個值,
//計算結果作爲參數傳遞給另外一個函數。
//這些值作爲實參傳遞給聲明函數時定義的形參。
//在函數體中存在一個形參的引用,指向當前傳入的實參列表,
//通過它可以獲得參數的值。
//函數的返回值成爲調用表達式的值。
//如果函數所有語句執行完返回值就是undefined,
//如果函數返回是因爲解釋器執行到一條return語句,
//返回值就是return語句之後的表達式的值。
//如果return語句沒有值,也返回undefined。

printprops({x:1});
var total = distance(0,0,2,1) + (2,1,3,5);
var probability = factorial(5)/factorial(13);

根據ES3和非嚴格ES5對函數調用的規定。調用上下文(this的值)是全局對象。然而在嚴格模式下,調用上下文是undefined。

以函數形式調用的函數通常不使用this關鍵字。不過,this可以用來判斷當前是否是嚴格模式。

// 定義並調用一個函數來確定當前腳本運行時是否爲嚴格模式
var strict  = (function(){return !this;}());
 // 翻譯成人類語言就是,當沒有this的時候返回true

  • 作爲方法的方法調用

一個方法就是保存在一個對象的屬性裏的JS函數。

o.m(x,y);
// 函數表達式本身就是一個屬性訪問表達式
// 方法調用和函數調用的重要區別:調用上下文。

// 屬性表達式由兩部分組成:一個對象和屬性名稱。[o.m]
// 在像這樣的方法調用表達式裏,對象o成爲調用期上下文,
// 函數體可以使用關鍵字this引用該對象。

關鍵字this引用對象:

var calculator = {
    operand1:1,
    operand2:1,
    add:function(){
        //注意this關鍵字的用法,this指代當前對象。
        this.result = this.operand1 + this.operand2;
    }
};
calculator.add();
console.log(calculator.result);

任何函數只要作爲方法調用實際上都會傳入一個隠式的實參,這個實參是一個對象,方法調用的母體就是這個對象。方法調用的語法已經很清晰地表明瞭函數將基於一個對象進行操作。

rect.setSize(width,height);  // 函數執行的載體是rect對象。
setRectSize(rect,width,height);

方法鏈:

當方法的返回值是一個對象,這個對象還可以再調用它的方法。
鏈式調用中,當方法不需要返回值時,最好直接返回this。

this:

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

很多人誤以爲調用嵌套函數this會指向調用外層函數的上下文。如果你想訪問這個外部函數的this值,需要將this的值保存在一個變量裏,這個變量和內部函數都同在一個作用域內。通常使用變量self來保存this:

var o = {                   // 對象o
    n: 7,
    m: function(){          // 對象中的方法m
        var self = this;             //this的值保存到一個變量
        console.log(this === o);     // true this就是這個對象o
        f();
        function f(){                // 定義一個嵌套函數
            console.log(this === o);   
            // falsethis的值是全局對象或undefined
            console.log(self === o);
            // true:self指外部函數的thisconsole.log(self.n);
        }
    }
};
o.m();
  • 作爲構造函數的調用

如果函數和方法前帶有關鍵字new,它就構成 構造函數調用。
如果構造函數沒有形參可以省略括號。

var o = new Object();
var o = new Object;

構造函數調用會創建一個新的空對象,這個對象繼承自構造函數的prototype屬性。
構造函數可以使用this關鍵字來引用這個新創建的對象,它會使用這個新對象作爲調用上下文。也就是說,在表達式 new o.m() 中,調用上下文不是o。

  • 通過call() & apply() 的 間接調用

3函數的實參和形參

3.1.1 可選形參

  • 當實參比形參少時,剩下的形參會設置成undefined。
  • 因此在調用函數時形參是否可選以及是否可以省略應當保持一個較好的適應性。可以給省略的參數賦一個合理的默認值。
// 將對象o中可枚舉的屬性名追加到數組a中,並返回這個數組a
// 如果省略a,則創建一個新數組並返回這個新數組
function getPropertyNames(o,/*optional*/a){
    if(a === undefined) a = [];

// 此處可以不使用if語句,使用||運算符
    a = a || [];
// 使用||運算符代替if語句的前提是a必須預先聲明。在這個例子中a是作爲形參傳入的,相當於var a。
    for(var property in o) a.push(property);
    return a;
}
// 這個函數調用可以傳入1個或2個實參
var a = getPropertyName(o); // 將o的屬性存儲到一個新數組中
getPropertyName(p,a);  // 將p的屬性追加到數組a中

3.1.2 實參對象

當傳入參數大於形參個數時,參數對象解決了問題。在函數體內,標識符arguments是指向實參對象的引用,實參對象是一個類數組對象,這樣可以通過數字下標訪問傳入函數的實參值。

JS默認行爲:省略的實參都是undefined,多出的參數會自動省略

實參對象可以讓函數操作任意數量的實參

function max(){
    var max = Number.NEGATIVE_INFINITY;
    // 遍歷實參,查找並記住最大值。
    for(var i = 0;i < arguments.length; i++){
        if(arguments[i] > max) max = arguments[i];
        // 返回最大值
        return max;
    }
}
var largest = max(1,10,100,2,3,1000,4,5,10000,6);

類似這種函數可以接收任意個數的實參,稱作 不定實參函數

function sum(){
    console.log(arguments);
    var num = 0;
    for (var i = 0; i < arguments.length; i++){
        console.log(arguments[i]);
        num = num + arguments[i];
    }
}

sum(1,3,443,5,32,546,3);

//實參列表:當傳入參數個數不定時使用

function sum(a,b){
    arguments[0];  // 10
    arguments[1];  // 20
    a   // 10
    b   // 20
    arguments[0] = 40;
    a   //  40
}
sum(10,20);

// 在滿足一定條件下(傳入實參個數與對應個數的實參列表數量相同時)
// 實參列表與形參具有一一映射關係

function sum (a,b,c) {
    arguments.length;  // 實參長度
    sum.length;   // 形參長度
}
sum(10,20);

4 作爲值的函數

function square(x){return x * x};

定義創建一個新的函數對象,並將其賦值給變量square。函數的名字實際上是看不見的,(square)僅僅是變量的名字,這個變量指代函數對象。


5 作爲命名空間的函數

  • 在函數聲明中的變量在整個函數體內都是可見的(包括嵌套函數)
    在函數外部是不可見的。
  • 不在任何函數內聲明的變量是全局變量,在整個程序中都是可見的。
  • 可以定義一個函數用作臨時的命名空間,這個命名空間內定義變量不會污染到全局命名空間。

6 閉包

  • 當內部函數被保存到外部時,將會生成閉包。閉包會導致原有作用域鏈不釋放,造成內存泄露。
  • 函數對象可以通過作用域鏈相互關聯起來,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學文獻中稱爲“閉包”。
  • 函數變量可以被隱藏於作用域鏈之內,因此看起來是函數將變量包裹了起來。
  • 所有的JS函數都是閉包:它們都是對象,它們都關聯到作用域鏈。
  • 定義大多數函數時的作用域鏈在調用函數時依然有效,但這並不影響閉包。

嵌套函數的詞法作用域規則:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){return scope;}
    return f();
}
checkscope()
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){return scope;}
    return f;
}
checkscope()()

JS函數的執行用到了作用域鏈,這個作用域鏈是函數定義時創建的。嵌套的函數f()定義在這個作用域鏈裏,其中的變量scope一定是局部變量,不管任何時候執行函數f(),這種綁定在執行f()時依然有效。因此最後一行代碼返回“local scope”。

  • 函數定義時的作用域鏈到函數執行時依然有效。
  • 每次調用JS函數的時候,都會爲之創建一個新的對象用來保存局部變量,把這個對象添加至作用域鏈中。當函數返回的時候,就從作用域鏈中將這個綁定變量的對象刪除
  • 如果不存在嵌套的函數,也沒有其他引用指向這個綁定對象,它就會被當做垃圾回收掉。
  • 如果定義了嵌套的函數,每個嵌套的函數都各自對應一個作用域鏈,並且這個作用域鏈指向一個綁定對象。
  • 但如果這些嵌套的函數對象在外部函數中保存下來,那麼他們也會和指向的變量綁定對象一樣當做垃圾回收。
  • 但是如果這個函數定義了嵌套的函數,並將它們作爲返回值返回或者存儲在某處的屬性裏,這時就會有一個外部引用指向這個嵌套的函數。它就不會被當做垃圾回收,並且它所指向的變量綁定對象也不會當做垃圾回收。

書寫閉包的時候還需要注意一個事情,this是JS的關鍵字,而不是變量。每個函數調用都包含一個this值,如果閉包在外部函數裏時無法訪問this的,除非外部函數將this轉存爲一個變量:

var self = this; 

綁定arguments的問題與之類似,arguments並不是一個關鍵字,但在調用每個函數時都會自動聲明它,由於閉包具有自己所綁定的arguments,因此閉包內無法直接訪問外部函數的參數數組,除非外部函數將參數數組保存到另外一個變量中:

var outerArguments = arguments;

7 函數屬性,方法和構造函數

  • length屬性

在函數體裏,arguments.length表示傳入函數的實參個數。而函數本身的length屬性則有着不同含義。
函數的length屬性是隻讀屬性,表示函數實參的數量。

  • prototype屬性

指向一個對象的引用,這個對象稱作“原型對象”。

  • call() & apply()
  • bind()
  • toString()

8 函數式編程

發佈了44 篇原創文章 · 獲贊 2 · 訪問量 5029
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章