在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);
爲了獲取除了已定義參數a
、b
之外的參數,我們不得不用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
}
常量
由於var
和let
申明的是變量,如果要申明一個常量,在ES6之前是不行的,我們通常用全部大寫的變量來表示“這是一個常量,不要修改它的值”:
var PI = 3.14;
ES6標準引入了新的關鍵字const
來定義常量,const
與let
都具有塊級作用域:
'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
可以拿到xiaoming
的birth
屬性。
要指定函數的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
中,我們調用Array
的map()
方法,傳入我們自己的函數,就得到了一個新的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