JavaScript | 函式與方法

weixin_33807284發表於2018-08-31

Reference : JavaScript教程 - 廖雪峰的官方網站

JavaScript函式基礎

定義函式

在JavaScript中,定義函式的方式如下:

function abs (x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

定義函式abs(x)

  • function關鍵字指明這是函式定義
  • abs是函式名稱
  • (x)括號內列出函式的引數,多個引數以,分隔
  • {...}之間的程式碼是函式體

沒有return的函式返回undefined

由於JavaScript的函式也是一個物件,上述定義的abs(x)函式實際上是一個函式物件,而函式名abs可以視為指向該函式的變數

因此,第二種定義函式的方式為:

var abs = function (x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
};

在這種方式下,function(x) {...}是一個匿名函式,它沒有函式名。但是,這個匿名函式被賦值給了變數abs,所以,通過變數abs即可呼叫該函式。

上述兩種定義完全等價,注意第二種方式按照完整語法需要在函式體末尾加一個;,表示賦值語句結束。

呼叫函式

JavaScript的函式呼叫不規定傳入引數的數量,可以多,也可以少。如果呼叫abs(),則函式內引數x接收到undefined,計算結果為null

為了避免引數為undefined,可以進行引數檢查:

function abs(x) {
    if (typeof x !== 'number') {
        throw 'Not a number';
    }
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

arguments關鍵字

JavaScript的函式內部可以使用arguments關鍵字,它指向當前函式的呼叫者傳入的所有引數,包括多處的不需要的引數。

  • arguments.length獲取引數個數
  • arguments[i]通過下標索引引數
  • arguments的操作類似Array,但實際不是Array

arguments最常見的用法是判斷傳入引數的個數,從而在函式體內進行適當的操作(如設定預設值)。例如以下求圓面積函式,當引數pi不傳入時,預設為3.14。

function area_of_circle(r, pi) {
    if (arguments.length == 0) {
        return null;
    } else if (arguments.length == 1) {
        pi = 3.14;
    }
    return pi * r * r;
}

rest引數[ES6]

定義函式如下:

function foo (a, b, ...rest) {
    // ...
}

rest引數以陣列的形式獲取arguments從下標為2開始至結束的所有引數,此時arguments[0]被傳給aarguments[1]被傳給b

如果沒有多餘的引數,或者連指明的引數個數都不滿足,則rest接收空陣列。

rest引數可以用來定義引數長度可變的函式,例如下面的求和函式:

function sum(...rest) {
    var i;
    var res = 0;
    for (i = 0; i < rest.length; i ++) {
        res += rest[i];
    }
    return res;
}

深入學習:高階函式 - 廖雪峰的官方網站

深入學習2:箭頭函式 - 廖雪峰的官方網站

物件方法

原文:方法 - 廖雪峰的官方網站

在一個物件中繫結函式,稱為這個物件的方法。例如:

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};

xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年呼叫是25,明年呼叫就變成26了

在方法中,this關鍵字指向當前物件。從更一般的意義講,函式體中的this指向呼叫函式的物件(這是JavaScript的大坑,接下來將重點理解這一點)

this關鍵字

要點1:this關鍵字的值動態地確定

我們可以把方法寫在物件的外面

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25, 正常結果
getAge(); // NaN

單獨呼叫getAge()返回NaN,這是因為單獨呼叫的語句等價於window.getAge(),此時this指向呼叫者window物件,因此結果是NaN

進一步地,如果以下面的形式呼叫:

var fn = xiaoming.age; // 拿到xiaoming物件的age函式控制程式碼
fn(); // NaN

結果依然是NaN。所以想要保證this指向正確,必須用obj.xxx()的形式呼叫!

這是一個巨大的設計錯誤,為了修復它,ESMA決定在strict模式下讓函式的this指向undefined,因此,在strict模式下呼叫fn()會得到錯誤:

Uncaught TypeError: Cannot read property 'birth' of undefined

但這個辦法治標不治本,this還是沒有指向正確的位置。

更進一步,採用下面的方式定義方法:

'use strict';

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - this.birth;
        }
        return getAgeFromBirth();
    }
};

xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined

這樣this物件依然無法指向xiaoming物件。對於這種情況,解決的辦法很簡單:在方法的開頭獲取this,如報錯在that變數中,此時that具有整個函式體的作用域,這樣在方法內定義的函式中,就可以用that代替this,而不會產生任何錯誤了。

apply

apply是任何函式自帶的一個方法,使用它,我們可以控制函式體內this的指向!我們可以靠它根本地解決上述的this指向問題。

apply有兩個引數,第一個引數是this指向的物件,第二個引數是Array類的物件,表示函式本身的引數。

回到上面的例子,我們用apply來修復報錯:

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 引數為空

call

apply類似的方法是call,唯一的區別是:

  • apply把引數打包成Array再傳入;
  • call把引數按順序傳入。

比如呼叫Math.max(3, 5, 4),分別用applycall實現如下:

Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

對於普通函式呼叫,我們通常讓this指向null

裝飾器

利用apply(),我們還可以動態改變函式的行為。

JavaScript的所有物件都是動態的,即使內建的函式,我們也可以重新指向新的函式。例如:

現在假定我們想統計一下程式碼一共呼叫了多少次parseInt(),可以把所有的呼叫都找出來,然後手動加上count += 1,不過這樣做太傻了。最佳方案是用我們自己的函式替換掉預設的parseInt()

'use strict';

var count = 0;
var oldParseInt = parseInt; // 儲存原函式

window.parseInt = function () {
    count += 1;
    return oldParseInt.apply(null, arguments); // 呼叫原函式
};

// 測試:
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3

相關文章