javascript(注意點) 函數

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

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

第二種定義函數的方式如下:

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

由於JavaScript允許傳入任意個參數而不影響調用,因此傳入的參數比定義的參數多也沒有問題,雖然函數內部並不需要這些參數:

abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9

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

abs(); // 返回NaN

此時abs(x)函數的參數x將收到undefined,計算結果爲NaN

arguments

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

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

爲了獲取除了已定義參數ab之外的參數,我們不得不用arguments,並且循環要從索引2開始以便排除前兩個參數,這種寫法很彆扭,只是爲了獲得額外的rest參數,有沒有更好的方法?

ES6標準引入了rest參數,上面的函數可以改寫爲:

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

變量提升

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的賦值。

對於上述foo()函數,JavaScript引擎看到的代碼相當於:

function foo() {
    var y; // 提升變量y的申明
    var x = 'Hello, ' + y;
    alert(x);
    y = 'Bob';
}

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

function foo() {
    var
        x = 1, // x初始化爲1
        y = x + 1, // y初始化爲2
        z, i; // z和i爲undefined
    // 其他語句:
    for (i=0; i<100; i++) {
        ...
    }
}

全局作用域

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

'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

局部作用域

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

'use strict';

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

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

'use strict';

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

常量

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

var PI = 3.14;

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

'use strict';

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

如果我們給xiaoming綁定一個函數,就可以做更多的事情。比如,寫個age()方法,返回xiaoming的年齡:

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是一個特殊變量,它始終指向當前對象,也就是xiaoming這個變量。所以,this.birth可以拿到xiaomingbirth屬性。

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

apply修復getAge()調用:

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, 參數爲空

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

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

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

比如調用Math.max(3, 5, 4),分別用apply()call()實現如下:

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

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

由於map()方法定義在JavaScript的Array中,我們調用Arraymap()方法,傳入我們自己的函數,就得到了一個新的Array作爲結果:

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

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]

再看reduce的用法。Array的reduce()把一個函數作用在這個Array[x1, x2, x3...]上,這個函數必須接收兩個參數,reduce()把結果繼續和序列的下一個元素做累積計算,其效果就是:

[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)

比方說對一個Array求和,就可以用reduce實現:

var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x + y;
}); // 25



















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章