天天看點

JavaScript學習筆記(三)函數

函數

定義函數

兩種方式定義函數,完全等價

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

由于JavaScript允許傳入任意個參數而不影響調用,是以傳入的參數比定義的參數多也沒有問題,雖然函數内部并不需要這些參數:

傳入的參數比定義的少也沒有問題:

arguments

JavaScript還有一個免費贈送的關鍵字arguments,它隻在函數内部起作用,并且永遠指向目前函數的調用者傳入的所有參數。arguments類似Array但它不是一個Array:

function foo(x) {
    alert(x); // 10
    for (var i=; i<arguments.length; i++) {
        alert(arguments[i]); // 10, 20, 30
    }
}
foo(, , );
           

利用arguments,你可以獲得調用者傳入的所有參數。也就是說,即使函數不定義任何參數,還是可以拿到參數的值:

function abs() {
    if (arguments.length === ) {
        return ;
    }
    var x = arguments[];
    return x >=  ? x : -x;
}

abs(); // 0
abs(); // 10
abs(-); // 9
           

實際上arguments最常用于判斷傳入參數的個數。你可能會看到這樣的寫法:

// foo(a[, b], c)
// 接收2~3個參數,b是可選參數,如果隻傳2個參數,b預設為null:
function foo(a, b, c) {
    if (arguments.length === ) {
        // 實際拿到的參數是a和b,c為undefined
        c = b; // 把b賦給c
        b = null; // b變為預設值
    }
    // ...
}
           

rest參數

function foo(a, b, ...rest) {
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

foo(, , , , );
// 結果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]

foo();
// 結果:
// a = 1
// b = undefined
// Array []
           

注意把result和arguments當作數組來使用,但不是Array.

小心你的return語句

當return語句有多行時記得加上尖括号,否則由于javascript的末尾分号自動補全機制會出問題

function foo() {
    return
        { name: 'foo' };
}

foo(); // undefined
           

要記得加上尖括号

function foo() {
    return { // 這裡不會自動加分号,因為{表示語句尚未結束
        name: 'foo'
    };
}
           

變量

JavaScript的函數在查找變量時從自身函數定義開始,從“内”向“外”查找。如果内部函數定義了與外部函數重名的變量,則内部函數的變量将“屏蔽”外部函數的變量。

JavaScript的函數定義有個特點,它會先掃描整個函數體的語句,把所有申明的變量“提升”到函數頂部:

'use strict';

function foo() {
    var x = 'Hello, ' + y;
    alert(x);
    var y = 'Bob';
}

foo();
           

雖然是strict模式,但語句var x = ‘Hello, ’ + y;并不報錯,原因是變量y在稍後申明了。但是alert顯示Hello, undefined,說明變量y的值為undefined。這正是因為JavaScript引擎自動提升了變量y的聲明,但不會提升變量y的指派。

由于JavaScript的這一怪異的“特性”,我們在函數内部定義變量時,請嚴格遵守“在函數内部首先申明所有變量”這一規則。最常見的做法是用一個var申明函數内部用到的所有變量:

全局作用域

不在任何函數内定義的變量就具有全局作用域。實際上,JavaScript預設有一個全局對象window,全局作用域的變量實際上被綁定到window的一個屬性:

名字空間

全局變量會綁定到window上,不同的JavaScript檔案如果使用了相同的全局變量,或者定義了相同名字的頂層函數,都會造成命名沖突,并且很難被發現。

減少沖突的一個方法是把自己的所有變量和函數全部綁定到一個全局變量中。例如:

// 唯一的全局變量MYAPP:
var MYAPP = {};

// 其他變量:
MYAPP.name = 'myapp';
MYAPP.version = ;

// 其他函數:
MYAPP.foo = function () {
    return 'foo';
};
           

把自己的代碼全部放入唯一的名字空間MYAPP中,會大大減少全局變量沖突的可能。

許多著名的JavaScript庫都是這麼幹的:jQuery,YUI,underscore等等。

局部作用域

由于JavaScript的變量作用域實際上是函數内部,我們在for循環等語句塊中是無法定義具有局部作用域的變量的:

'use strict';

function foo() {
    for (var i=; i<; i++) {
        //
    }
    i += ; // 仍然可以引用變量i
}
           

為了解決塊級作用域,ES6引入了新的關鍵字let,用let替代var可以申明一個塊級作用域的變量:

'use strict';

function foo() {
    var sum = ;
    for (let i=; i<; i++) {
        sum += i;
    }
    i += ; // SyntaxError
}
           

常量

由于var和let申明的是變量,如果要申明一個常量,在ES6之前是不行的,我們通常用全部大寫的變量來表示“這是一個常量,不要修改它的值”:

ES6标準引入了新的關鍵字const來定義常量,const與let都具有塊級作用域:

'use strict';

const PI = ;
PI = ; // 某些浏覽器不報錯,但是無效果!
PI; // 3.14
           

方法

在一個對象中綁定函數,稱為這個對象的方法。

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

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

綁定到對象上的函數稱為方法,和普通函數也沒啥差別,但是它在内部使用了一個this關鍵字,這個東東是什麼?

在一個方法内部,this是一個特殊變量,它始終指向目前對象,也就是xiaoming這個變量。是以,this.birth可以拿到xiaoming的birth屬性。

讓我們拆開寫:

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

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

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

單獨調用函數getAge()怎麼傳回了NaN?請注意,我們已經進入到了JavaScript的一個大坑裡。

JavaScript的函數内部如果調用了this,那麼這個this到底指向誰?

答案是,視情況而定!

如果以對象的方法形式調用,比如xiaoming.age(),該函數的this指向被調用的對象,也就是xiaoming,這是符合我們預期的。

如果單獨調用函數,比如getAge(),此時,該函數的this指向全局對象,也就是window。

var fn = xiaoming.age; // 先拿到xiaoming的age函數
fn(); // NaN
           

也是不行

apply

雖然在一個獨立的函數調用中,根據是否是strict模式,this指向undefined或window,不過,我們還是可以控制this的指向的!

要指定函數的this指向哪個對象,可以用函數本身的apply方法,它接收兩個參數,第一個參數就是需要綁定的this變量,第二個參數是Array,表示函數本身的參數。

用apply修複getAge()調用:

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

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

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

另一個與apply()類似的方法是call(),唯一差別是:

apply()把參數打包成Array再傳入;

call()把參數按順序傳入。

比如調用Math.max(3, 5, 4),分别用apply()和call()實作如下:

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

對普通函數調用,我們通常把this綁定為null

繼續閱讀