函數(shù)就是最基本的一種代碼抽象的方式原朝。
- 定義函數(shù)
function abs(x) {
if (x >=0){
return x;
}else{
return-x;
}
}
function 指出這是一個(gè)函數(shù)的定義;
abs是函數(shù)的名稱;
(x)括號(hào)內(nèi)列出函數(shù)的參數(shù),多個(gè)參數(shù)以逗號(hào)分隔;
{...}之間的代碼是函數(shù)體,可以包含若干語(yǔ)句,也可以沒(méi)有任何語(yǔ)句.
函數(shù)體內(nèi)部的語(yǔ)句在執(zhí)行時(shí)怔揩,一旦執(zhí)行到return時(shí),函數(shù)就執(zhí)行完畢,并將結(jié)果返回塑悼。因此沪猴,函數(shù)內(nèi)部通過(guò)條件判斷和循環(huán)可以實(shí)現(xiàn)非常復(fù)雜的邏輯。
如果沒(méi)有return語(yǔ)句辩稽,函數(shù)執(zhí)行完畢后也會(huì)返回結(jié)果,只是結(jié)果為undefined从媚。
由于JavaScript的函數(shù)也是一個(gè)對(duì)象逞泄,上述定義的abs()函數(shù)實(shí)際上是一個(gè)函數(shù)對(duì)象,而函數(shù)名abs可以視為指向該函數(shù)的變量拜效。
因此,第二種定義函數(shù)的方式如下:
var abs = function (x) {
if (x >=0){
return x;
}else{
return-x;
}
};
這種方式下:function(x){...}是個(gè)匿名函數(shù),它沒(méi)有函數(shù)名,但是這個(gè)匿名函數(shù)賦值給了變量abs,所以通過(guò)變量abs就可以調(diào)用該函數(shù).
上述兩種定義完全等價(jià),注意第二種方式按照完整語(yǔ)法需要在函數(shù)體末尾加一個(gè)分號(hào);表示賦值語(yǔ)句結(jié)束.
-
調(diào)用函數(shù)
調(diào)用函數(shù)時(shí)喷众,按順序傳入?yún)?shù)即可:
由于JavaScript允許傳入任意個(gè)參數(shù)而不影響調(diào)用,因此傳入的參數(shù)比定義的參數(shù)多也沒(méi)有問(wèn)題拂檩,雖然函數(shù)內(nèi)部并不需要這些參數(shù):
傳入的參數(shù)比定義的少也沒(méi)有問(wèn)題:
abs(); // 返回NaN
此時(shí)abs(x)函數(shù)的參數(shù)x將收到undefined侮腹,計(jì)算結(jié)果為NaN。
要避免收到undefined稻励,可以對(duì)參數(shù)進(jìn)行檢查:
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}if (x >= 0) {
return x;
} else {
return -x;
}
} arguments
JavaScript 有個(gè)免費(fèi)贈(zèng)送的關(guān)鍵字arguments,它只在函數(shù)內(nèi)部起作用,并且永遠(yuǎn)指向當(dāng)前函數(shù)的調(diào)用者傳入的所有參數(shù).
arguments類似數(shù)組,但它不是一個(gè)數(shù)組:
function foo(x){
alert(x);
for (var i = 0; i < arguments.length; i++) {
alert(arguments[i])
}
}
foo(10,20,30);
利用arguments父阻,你可以獲得調(diào)用者傳入的所有參數(shù)愈涩。也就是說(shuō),即使函數(shù)不定義任何參數(shù)加矛,還是可以拿到參數(shù)的值:
function abs() {
// body...
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
實(shí)際上arguments最常用于判斷傳入?yún)?shù)的個(gè)數(shù)履婉。你可能會(huì)看到這樣的寫(xiě)法:
// foo(a[, b], c)
// 接收2~3個(gè)參數(shù),b是可選參數(shù)斟览,如果只傳2個(gè)參數(shù)毁腿,b默認(rèn)為null:
function foo(a,b,c) {
// body...
if (arguments.length === 2) {
//實(shí)際拿到的參數(shù)是a和b c為undefined
c = b;//把b賦值給c
b = null;//b變?yōu)槟J(rèn)值
}
//...
}
要把中間的參數(shù)b變?yōu)椤翱蛇x”參數(shù),就只能通過(guò)arguments判斷苛茂,然后重新調(diào)整參數(shù)并賦值已烤。-
rest
由于JavaScript函數(shù)允許接收任意個(gè)參數(shù),于是我們就不得不用arguments來(lái)獲取所有參數(shù):
function foo(a,b) {
var i, rest = [];
if (arguments.length > 2){
for (var i = 2; i < arguments.length; i++) {
rest.push(arguments[i])
}
}
console.log('a = ' + a)
console.log('b = ' + b)
console.log(rest)}
為了獲取除了已定義參數(shù)a妓羊、b之外的參數(shù)胯究,我們不得不用arguments,并且循環(huán)要從索引2開(kāi)始以便排除前兩個(gè)參數(shù)躁绸,這種寫(xiě)法很別扭裕循,只是為了獲得額外的rest參數(shù),有沒(méi)有更好的方法净刮?
ES6標(biāo)準(zhǔn)引入了rest參數(shù),上面的函數(shù)可以改寫(xiě)為:
function foo(a,b,...rest) {
console.log('a = ' + a)
console.log('b = ' + b)
console.log(rest)
}
rest參數(shù)只能寫(xiě)在最后,前面用...標(biāo)示,從運(yùn)行結(jié)果可知,傳入的參數(shù)先綁定a,b,多余的參數(shù)以數(shù)組的形式交給變量rest,所以,不需要arguments我們就獲取了全部參數(shù).
如果傳入的參數(shù)連正常定義的參數(shù)都沒(méi)有填滿,rest參數(shù)會(huì)接受一個(gè)空數(shù)組,注意不是undefined
5.小心return語(yǔ)句JavaScript引擎有一個(gè)在行末自動(dòng)添加分號(hào)的機(jī)制剥哑,這可能讓你栽到return語(yǔ)句的一個(gè)大坑:
function foo() {
return { name: 'foo' };
}
如果把return語(yǔ)句拆成兩行:
function foo() {
return
{ name: 'foo' };
}
foo(); // undefined
要小心了,由于JavaScript引擎在行末自動(dòng)添加分號(hào)的機(jī)制淹父,上面的代碼實(shí)際上變成了:
function foo() {
return; // 自動(dòng)添加了分號(hào)株婴,相當(dāng)于return undefined;
{ name: 'foo' }; // 這行語(yǔ)句已經(jīng)沒(méi)法執(zhí)行到了
}
所以正確的多行寫(xiě)法是:
function foo() {
return{
name:'foo'
};
}
=================
變量
- 變量提升:
JavaScript的函數(shù)定義有個(gè)特點(diǎn),它會(huì)先掃描整個(gè)函數(shù)體的語(yǔ)句,把所有申明的變量'提升'到函數(shù)的頂部,但不會(huì)提升變量賦值:
'use strict';
function foo() {
var x = 'Hello, ' + y;
alert(x);
var y = 'Bob';
}
foo();
雖然是strict模式,但語(yǔ)句var x = 'Hello, ' + y;并不報(bào)錯(cuò)弹灭,原因是變量y在稍后申明了督暂。但是alert顯示Hello, undefined揪垄,說(shuō)明變量y的值為undefined穷吮。這正是因?yàn)镴avaScript引擎自動(dòng)提升了變量y的聲明,但不會(huì)提升變量y的賦值饥努。
對(duì)于上述foo()函數(shù)捡鱼,JavaScript引擎看到的代碼相當(dāng)于:
function foo() {
var y; // 提升變量y的申明
var x = 'Hello, ' + y;
alert(x);
y = 'Bob';
}
由于JavaScript的這一怪異的“特性”,我們?cè)诤瘮?shù)內(nèi)部定義變量時(shí)酷愧,請(qǐng)嚴(yán)格遵守“在函數(shù)內(nèi)部首先申明所有變量”這一規(guī)則驾诈。最常見(jiàn)的做法是用一個(gè)var申明函數(shù)內(nèi)部用到的所有變量:
function foo() {
var
x = 1, // x初始化為1
y = x + 1, // y初始化為2
z, i; // z和i為undefined
// 其他語(yǔ)句:
for (i=0; i<100; i++) {
...
}
}
2.全局作用域
不在任何函數(shù)內(nèi)定義的變量就具有全局作用域。實(shí)際上溶浴,JavaScript默認(rèn)有一個(gè)全局對(duì)象window乍迄,全局作用域的變量實(shí)際上被綁定到window的一個(gè)屬性:
'use strict';
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'
因此,直接訪問(wèn)全局變量course和訪問(wèn)window.course是完全一樣的士败。
由于函數(shù)定義有兩種方式闯两,以變量方式var foo = function () {}定義的函數(shù)實(shí)際上也是一個(gè)全局變量褥伴,因此,頂層函數(shù)的定義也被視為一個(gè)全局變量漾狼,并綁定到window對(duì)象:
'use strict';
function foo() {
alert('foo');
}
foo(); // 直接調(diào)用foo()
window.foo(); // 通過(guò)window.foo()調(diào)用
我們每次直接調(diào)用的alert()函數(shù)其實(shí)也是window的一個(gè)變量:
這說(shuō)明JavaScript實(shí)際上只有一個(gè)全局作用域重慢。任何變量(函數(shù)也視為變量),如果沒(méi)有在當(dāng)前函數(shù)作用域中找到逊躁,就會(huì)繼續(xù)往上查找似踱,最后如果在全局作用域中也沒(méi)有找到,則報(bào)ReferenceError錯(cuò)誤稽煤。
- 名字空間
全局變量會(huì)綁定到window上核芽,不同的JavaScript文件如果使用了相同的全局變量,或者定義了相同名字的頂層函數(shù)酵熙,都會(huì)造成命名沖突狞洋,并且很難被發(fā)現(xiàn)。
減少?zèng)_突的一個(gè)方法是把自己的所有變量和函數(shù)全部綁定到一個(gè)全局變量中绿店。例如:
// 唯一的全局變量MYAPP:
var MYAPP = {};
// 其他變量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函數(shù):
MYAPP.foo = function () {
return 'foo';
};
把自己的代碼全部放入唯一的名字空間MYAPP中吉懊,會(huì)大大減少全局變量沖突的可能。
許多著名的JavaScript庫(kù)都是這么干的:jQuery假勿,YUI借嗽,underscore等等。 - 局部作用域
由于JavaScript的變量作用域?qū)嶋H上是函數(shù)內(nèi)部转培,我們?cè)趂or循環(huán)等語(yǔ)句塊中是無(wú)法定義具有局部作用域的變量的:
'use strict';
function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用變量i
}
為了解決塊級(jí)作用域恶导,ES6引入了新的關(guān)鍵字let,用let替代var可以申明一個(gè)塊級(jí)作用域的變量:
'use strict';
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
i += 1; // SyntaxError
}
- 常量
由于var和let申明的是變量浸须,如果要申明一個(gè)常量惨寿,在ES6之前是不行的,我們通常用全部大寫(xiě)的變量來(lái)表示“這是一個(gè)常量删窒,不要修改它的值”:
var PI = 3.14;
ES6標(biāo)準(zhǔn)引入了新的關(guān)鍵字const來(lái)定義常量裂垦,const與let都具有塊級(jí)作用域:
'use strict';
const PI = 3.14;
PI = 3; // 某些瀏覽器不報(bào)錯(cuò),但是無(wú)效果肌索!
PI; // 3.14
6.方法:在一個(gè)對(duì)象中綁定函數(shù),稱為這個(gè)對(duì)象的方法
var xioming = {
name:'小明',
birth:1990,
age:function () {
var y = new Data().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年調(diào)用是27,明年調(diào)用就變成28了
綁定到對(duì)象上的函數(shù)稱為方法蕉拢,和普通函數(shù)也沒(méi)啥區(qū)別,但是它在內(nèi)部使用了一個(gè)this關(guān)鍵字诚亚,在一個(gè)方法內(nèi)部晕换,this是一個(gè)特殊變量,它始終指向當(dāng)前對(duì)象
JavaScript的函數(shù)內(nèi)部如果調(diào)用了this站宗,那么這個(gè)this到底指向誰(shuí)闸准?
答案是,視情況而定梢灭!
如果以對(duì)象的方法形式調(diào)用夷家,比如xiaoming.age()腕唧,該函數(shù)的this指向被調(diào)用的對(duì)象,也就是xiaoming瘾英,這是符合我們預(yù)期的枣接。
如果單獨(dú)調(diào)用函數(shù),比如getAge()缺谴,此時(shí)但惶,該函數(shù)的this指向全局對(duì)象,也就是window湿蛔。
坑爹鞍蛟!
更坑爹的是阳啥,如果這么寫(xiě):
var fn = xiaoming.age; // 先拿到xiaoming的age函數(shù)
fn(); // NaN
要保證this指向正確添谊,必須用obj.xxx()的形式調(diào)用!
有些時(shí)候察迟,喜歡重構(gòu)的你把方法重構(gòu)了一下:
'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
結(jié)果又報(bào)錯(cuò)了斩狱!原因是this指針只在age方法的函數(shù)內(nèi)指向xiaoming,在函數(shù)內(nèi)部定義的函數(shù)扎瓶,this又指向undefined了K弧(在非strict模式下,它重新指向全局對(duì)象window8藕伞)
修復(fù)的辦法也不是沒(méi)有秕岛,我們用一個(gè)that變量首先捕獲this:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法內(nèi)部一開(kāi)始就捕獲this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 25
用var that = this;,你就可以放心地在方法內(nèi)部定義其他函數(shù)误证,而不是把所有語(yǔ)句都堆到一個(gè)方法中继薛。
- apply
雖然在一個(gè)獨(dú)立的函數(shù)調(diào)用中,根據(jù)是否是strict模式愈捅,this指向undefined或window遏考,不過(guò),我們還是可以控制this的指向的改鲫!
要指定函數(shù)的this指向哪個(gè)對(duì)象诈皿,可以用函數(shù)本身的apply方法林束,它接收兩個(gè)參數(shù)像棘,第一個(gè)參數(shù)就是需要綁定的this變量,第二個(gè)參數(shù)是Array壶冒,表示函數(shù)本身的參數(shù)缕题。
用apply修復(fù)getAge()調(diào)用:
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, 參數(shù)為空
另一個(gè)與apply()類似的方法是call(),唯一區(qū)別是:
apply()把參數(shù)打包成Array再傳入胖腾;
call()把參數(shù)按順序傳入烟零。
比如調(diào)用Math.max(3, 5, 4)瘪松,分別用apply()和call()實(shí)現(xiàn)如下:
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
對(duì)普通函數(shù)調(diào)用,我們通常把this綁定為null锨阿。
- 裝飾器
利用apply()宵睦,我們還可以動(dòng)態(tài)改變函數(shù)的行為。
JavaScript的所有對(duì)象都是動(dòng)態(tài)的墅诡,即使內(nèi)置的函數(shù)壳嚎,我們也可以重新指向新的函數(shù)。
現(xiàn)在假定我們想統(tǒng)計(jì)一下代碼一共調(diào)用了多少次parseInt()末早,可以把所有的調(diào)用都找出來(lái)烟馅,然后手動(dòng)加上count += 1,不過(guò)這樣做太傻了然磷。最佳方案是用我們自己的函數(shù)替換掉默認(rèn)的parseInt():
var count = 0;
var oldParsetInt = parseInt;//保存原函數(shù)
window.parseInt = function () {
count += 1;
return oldParsetInt.apply(null,arguments);//調(diào)用原函數(shù)
}
===========================
高階函數(shù):函數(shù)的參數(shù)可以是另一個(gè)函數(shù).
1. map 由于map()方法定義在JavaScript的Array中郑趁,我們調(diào)用Array的map()方法,傳入我們自己的函數(shù)姿搜,就得到了一個(gè)新的Array作為結(jié)果:
2. reduce 再看reduce的用法寡润。Array的reduce()把一個(gè)函數(shù)作用在這個(gè)Array的[x1, x2, x3...]上,這個(gè)函數(shù)必須接收兩個(gè)參數(shù)舅柜,reduce()把結(jié)果繼續(xù)和序列的下一個(gè)元素做累積計(jì)算悦穿,
由于map()接收的回調(diào)函數(shù)可以有3個(gè)參數(shù):callback(currentValue, index, array)通常我們僅需要第一個(gè)參數(shù),而忽略了傳入的后面兩個(gè)參數(shù)业踢。
parseInt(string, radix)沒(méi)有忽略第二個(gè)參數(shù)
3.filter 過(guò)濾器
和map()類似栗柒,Array的filter()也接收一個(gè)函數(shù)。和map()不同的是知举,filter()把傳入的函數(shù)依次作用于每個(gè)元素瞬沦,然后根據(jù)返回值是true還是false決定保留還是丟棄該元素。
filter()接收的回調(diào)函數(shù)雇锡,其實(shí)可以有多個(gè)參數(shù)逛钻。通常我們僅使用第一個(gè)參數(shù),表示Array的某個(gè)元素锰提∈锒唬回調(diào)函數(shù)還可以接收另外兩個(gè)參數(shù),表示元素的位置和數(shù)組本身:
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
console.log(element); // 依次打印'A', 'B', 'C'
console.log(index); // 依次打印0, 1, 2
console.log(self); // self就是變量arr
return true;
});
3.sort Array的sort()方法默認(rèn)把所有元素先轉(zhuǎn)換為String再排序 sort()方法也是一個(gè)高階函數(shù)立肘,它還可以接收一個(gè)比較函數(shù)來(lái)實(shí)現(xiàn)自定義的排序边坤。
比較的過(guò)程必須通過(guò)函數(shù)抽象出來(lái)。通常規(guī)定谅年,對(duì)于兩個(gè)元素x和y茧痒,如果認(rèn)為x < y,則返回-1融蹂,如果認(rèn)為x == y旺订,則返回0弄企,如果認(rèn)為x > y台丛,則返回1胚委,
4.閉包 就是返回函數(shù)
返回的函數(shù)在其定義內(nèi)部引用了局部變量arr,所以烫罩,當(dāng)一個(gè)函數(shù)返回了一個(gè)函數(shù)后樱调,其內(nèi)部的局部變量還被新函數(shù)引用院究,所以,閉包用起來(lái)簡(jiǎn)單本涕,實(shí)現(xiàn)起來(lái)可不容易业汰。
返回的函數(shù)并沒(méi)有立刻執(zhí)行,而是直到調(diào)用了f()才執(zhí)行菩颖。我們來(lái)看一個(gè)例子:
返回閉包時(shí)牢記的一點(diǎn)就是:返回函數(shù)不要引用任何循環(huán)變量样漆,或者后續(xù)會(huì)發(fā)生變化的變量。
如果一定要引用循環(huán)變量怎么辦晦闰?方法是再創(chuàng)建一個(gè)函數(shù)放祟,用該函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值,無(wú)論該循環(huán)變量后續(xù)如何更改呻右,已綁定到函數(shù)參數(shù)的值不變:
注意這里用了一個(gè)“創(chuàng)建一個(gè)匿名函數(shù)并立刻執(zhí)行”的語(yǔ)法:
(function (x) {
return x * x;
})(3); // 9
理論上講跪妥,創(chuàng)建一個(gè)匿名函數(shù)并立刻執(zhí)行可以這么寫(xiě):
function (x) { return x * x } (3);
但是由于JavaScript語(yǔ)法解析的問(wèn)題,會(huì)報(bào)SyntaxError錯(cuò)誤声滥,因此需要用括號(hào)把整個(gè)函數(shù)定義括起來(lái):
(function (x) { return x * x }) (3);
通常眉撵,一個(gè)立即執(zhí)行的匿名函數(shù)可以把函數(shù)體拆開(kāi),一般這么寫(xiě):
(function (x) {
return x * x;
})(3);
換句話說(shuō)落塑,閉包就是攜帶狀態(tài)的函數(shù)纽疟,并且它的狀態(tài)可以完全對(duì)外隱藏起來(lái)。
5.箭頭函數(shù)
ES6標(biāo)準(zhǔn)新增了一種新的函數(shù):Arrow Function(箭頭函數(shù))憾赁。
為什么叫Arrow Function污朽?因?yàn)樗亩x用的就是一個(gè)箭頭:
x => x * x
箭頭函數(shù)相當(dāng)于匿名函數(shù),并且簡(jiǎn)化了函數(shù)定義龙考。箭頭函數(shù)有兩種格式蟆肆,一種像上面的,只包含一個(gè)表達(dá)式晦款,連{ ... }和return都省略掉了炎功。還有一種可以包含多條語(yǔ)句,這時(shí)候就不能省略{ ... }和return:
x => {
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}
如果參數(shù)不是一個(gè)柬赐,就需要用括號(hào)()括起來(lái):
// 兩個(gè)參數(shù):
(x, y) => x * x + y * y
// 無(wú)參數(shù):
() => 3.14
// 可變參數(shù):
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
如果要返回一個(gè)對(duì)象亡问,就要注意,如果是單表達(dá)式肛宋,這么寫(xiě)的話會(huì)報(bào)錯(cuò):
// SyntaxError:
x => { foo: x }
因?yàn)楹秃瘮?shù)體的{ ... }有語(yǔ)法沖突州藕,所以要改為:
// ok:
x => ({ foo: x })
箭頭函數(shù)完全修復(fù)了this的指向,this總是指向詞法作用域酝陈,也就是外層調(diào)用者obj:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj對(duì)象
return fn();
}
};
obj.getAge(); // 25
如果使用箭頭函數(shù)床玻,以前的那種hack寫(xiě)法:
var that = this;
就不再需要了。
由于this在箭頭函數(shù)中已經(jīng)按照詞法作用域綁定了沉帮,所以锈死,用call()或者apply()調(diào)用箭頭函數(shù)時(shí),無(wú)法對(duì)this進(jìn)行綁定穆壕,即傳入的第一個(gè)參數(shù)被忽略: