JavaScript使用if () { ... } else { ... }來進行條件判斷卿啡。例如千扔,根據(jù)年齡顯示不同內(nèi)容,可以用if語句實現(xiàn)如下:
var age =20;if(age >=18) {//如果age >=18為true畦粮,則執(zhí)行if語句塊? ? alert('adult');}else{//否則執(zhí)行else語句塊? ? alert('teenager');}
其中else語句是可選的绍填。如果語句塊只包含一條語句,那么可以省略{}:
varage =20;if(age >=18)? ? alert('adult');elsealert('teenager');
省略{}的危險之處在于内贮,如果后來想添加一些語句产园,卻忘了寫{},就改變了if...else...的語義夜郁,例如:
var age =20;if(age >=18)? ? alert('adult');elseconsole.log('age < 18');//添加一行日志? ? alert('teenager');//<- 這行語句已經(jīng)不在else的控制范圍了
上述代碼的else子句實際上只負責執(zhí)行console.log('age < 18');什燕,原有的alert('teenager');已經(jīng)不屬于if...else...的控制范圍了,它每次都會執(zhí)行竞端。
相反地屎即,有{}的語句就不會出錯:
varage =20;if(age >=18) {? ? alert('adult');}else{? ? console.log('age < 18');? ? alert('teenager');}
這就是為什么我們建議永遠都要寫上{}。
多行條件判斷
如果還要更細致地判斷條件事富,可以使用多個if...else...的組合:
varage =3;if(age >=18) {? ? alert('adult');}elseif(age >=6) {? ? alert('teenager');}else{? ? alert('kid');}
上述多個if...else...的組合實際上相當于兩層if...else...:
varage =3;if(age >=18) {? ? alert('adult');}else{if(age >=6) {? ? ? ? alert('teenager');? ? }else{? ? ? ? alert('kid');? ? }}
但是我們通常把else if連寫在一起技俐,來增加可讀性。這里的else略掉了{}是沒有問題的统台,因為它只包含一個if語句雕擂。注意最后一個單獨的else不要略掉{}。
請注意贱勃,if...else...語句的執(zhí)行特點是二選一井赌,在多個if...else...語句中,如果某個條件成立贵扰,則后續(xù)就不再繼續(xù)判斷了仇穗。
試解釋為什么下面的代碼顯示的是teenager:
'use strict';
var age = 20;
?Run
由于age的值為20,它實際上同時滿足條件age >= 6和age >= 18戚绕,這說明條件判斷的順序非常重要纹坐。請修復后讓其顯示adult。
如果if的條件判斷語句結(jié)果不是true或false怎么辦列肢?例如:
vars ='123';if(s.length) {// 條件計算結(jié)果為3//}
JavaScript把null恰画、undefined、0瓷马、NaN和空字符串''視為false拴还,其他值一概視為true,因此上述代碼條件判斷的結(jié)果是true欧聘。
循環(huán)
要計算1+2+3片林,我們可以直接寫表達式:
1+2+3;//6
要計算1+2+3+...+10,勉強也能寫出來。
但是费封,要計算1+2+3+...+10000焕妙,直接寫表達式就不可能了。
為了讓計算機能計算成千上萬次的重復運算弓摘,我們就需要循環(huán)語句焚鹊。
JavaScript的循環(huán)有兩種,一種是for循環(huán)韧献,通過初始條件末患、結(jié)束條件和遞增條件來循環(huán)執(zhí)行語句塊:
varx =0;vari;for(i=1; i<=10000; i++) {? ? x = x + i;}x;// 50005000
讓我們來分析一下for循環(huán)的控制條件:
i=1 這是初始條件,將變量i置為1锤窑;
i<=10000 這是判斷條件璧针,滿足時就繼續(xù)循環(huán),不滿足就退出循環(huán)渊啰;
i++ 這是每次循環(huán)后的遞增條件探橱,由于每次循環(huán)后變量i都會加1,因此它終將在若干次循環(huán)后不滿足判斷條件i<=10000而退出循環(huán)绘证。
avaScript的默認對象表示方式{}可以視為其他語言中的Map或Dictionary的數(shù)據(jù)結(jié)構(gòu)隧膏,即一組鍵值對。
但是JavaScript的對象有個小問題迈窟,就是鍵必須是字符串私植。但實際上Number或者其他數(shù)據(jù)類型作為鍵也是非常合理的忌栅。
為了解決這個問題车酣,最新的ES6規(guī)范引入了新的數(shù)據(jù)類型Map。要測試你的瀏覽器是否支持ES6規(guī)范索绪,請執(zhí)行以下代碼湖员,如果瀏覽器報ReferenceError錯誤,那么你需要換一個支持ES6的瀏覽器:
'use strict';
var m = new Map();
var s = new Set();
console.log('你的瀏覽器支持Map和Set瑞驱!');
?Run
Map
Map是一組鍵值對的結(jié)構(gòu)娘摔,具有極快的查找速度。
舉個例子唤反,假設要根據(jù)同學的名字查找對應的成績凳寺,如果用Array實現(xiàn),需要兩個Array:
varnames = ['Michael','Bob','Tracy'];varscores = [95,75,85];
給定一個名字彤侍,要查找對應的成績肠缨,就先要在names中找到對應的位置,再從scores取出對應的成績盏阶,Array越長晒奕,耗時越長。
如果用Map實現(xiàn),只需要一個“名字”-“成績”的對照表脑慧,直接根據(jù)名字查找成績魄眉,無論這個表有多大,查找速度都不會變慢闷袒。用JavaScript寫一個Map如下:
varm =newMap([['Michael',95], ['Bob',75], ['Tracy',85]]);m.get('Michael');// 95
初始化Map需要一個二維數(shù)組坑律,或者直接初始化一個空Map。Map具有以下方法:
varm =newMap();// 空Mapm.set('Adam',67);// 添加新的key-valuem.set('Bob',59);m.has('Adam');// 是否存在key 'Adam': truem.get('Adam');// 67m.delete('Adam');// 刪除key 'Adam'm.get('Adam');// undefined
由于一個key只能對應一個value囊骤,所以脾歇,多次對一個key放入value,后面的值會把前面的值沖掉:
var m = new Map();m.set('Adam',67);m.set('Adam',88);m.get('Adam'); // 88
Set
Set和Map類似淘捡,也是一組key的集合藕各,但不存儲value。由于key不能重復焦除,所以激况,在Set中,沒有重復的key膘魄。
要創(chuàng)建一個Set乌逐,需要提供一個Array作為輸入,或者直接創(chuàng)建一個空Set:
vars1 =newSet();// 空Setvars2 =newSet([1,2,3]);// 含1, 2, 3
重復元素在Set中自動被過濾:
var s = newSet([1,2,3,3,'3']);s; //Set{1,2,3,"3"}
注意數(shù)字3和字符串'3'是不同的元素创葡。
通過add(key)方法可以添加元素到Set中浙踢,可以重復添加,但不會有效果:
s.add(4);s; //Set{1,2,3,4}s.add(4);s; // 仍然是Set{1,2,3,4}
通過delete(key)方法可以刪除元素:
var s = newSet([1,2,3]);s; //Set{1,2,3}s.delete(3);s; //Set{1,2}
遍歷Array可以采用下標循環(huán)灿渴,遍歷Map和Set就無法使用下標洛波。為了統(tǒng)一集合類型,ES6標準引入了新的iterable類型骚露,Array蹬挤、Map和Set都屬于iterable類型。
具有iterable類型的集合可以通過新的for ... of循環(huán)來遍歷棘幸。
for ... of循環(huán)是ES6引入的新的語法焰扳,請測試你的瀏覽器是否支持:
'use strict';
var a = [1, 2, 3];
for (var x of a) {
}
console.log('你的瀏覽器支持for ... of');
?Run
用for ... of循環(huán)遍歷集合,用法如下:
vara = ['A','B','C'];vars =newSet(['A','B','C']);varm =newMap([[1,'x'], [2,'y'], [3,'z']]);for(varx of a) {// 遍歷Arrayconsole.log(x);}for(varx of s) {// 遍歷Setconsole.log(x);}for(varx of m) {// 遍歷Mapconsole.log(x[0] +'='+ x[1]);}
你可能會有疑問误续,for ... of循環(huán)和for ... in循環(huán)有何區(qū)別吨悍?
for ... in循環(huán)由于歷史遺留問題,它遍歷的實際上是對象的屬性名稱蹋嵌。一個Array數(shù)組實際上也是一個對象育瓜,它的每個元素的索引被視為一個屬性。
當我們手動給Array對象添加了額外的屬性后欣尼,for ... in循環(huán)將帶來意想不到的意外效果:
vara = ['A','B','C'];a.name ='Hello';for(varxina) {? ? console.log(x);// '0', '1', '2', 'name'}
for ... in循環(huán)將把name包括在內(nèi)爆雹,但Array的length屬性卻不包括在內(nèi)停蕉。
for ... of循環(huán)則完全修復了這些問題,它只循環(huán)集合本身的元素:
vara = ['A','B','C'];a.name ='Hello';for(varx of a) {? ? console.log(x);// 'A', 'B', 'C'}
這就是為什么要引入新的for ... of循環(huán)钙态。
然而慧起,更好的方式是直接使用iterable內(nèi)置的forEach方法,它接收一個函數(shù)册倒,每次迭代就自動回調(diào)該函數(shù)蚓挤。以Array為例:
'use strict';
var a = ['A', 'B', 'C'];
?Run
注意,forEach()方法是ES5.1標準引入的驻子,你需要測試瀏覽器是否支持灿意。
Set與Array類似,但Set沒有索引崇呵,因此回調(diào)函數(shù)的前兩個參數(shù)都是元素本身:
vars =newSet(['A','B','C']);s.forEach(function(element, sameElement, set){console.log(element);});
Map的回調(diào)函數(shù)參數(shù)依次為value缤剧、key和map本身:
varm =newMap([[1,'x'], [2,'y'], [3,'z']]);m.forEach(function(value, key, map){console.log(value);});
如果對某些參數(shù)不感興趣,由于JavaScript的函數(shù)調(diào)用不要求參數(shù)必須一致域慷,因此可以忽略它們荒辕。例如,只需要獲得Array的element:
vara = ['A','B','C'];a.forEach(function(element){console.log(element);});
定義函數(shù)
在JavaScript中犹褒,定義函數(shù)的方式如下:
functionabs(x){if(x >=0) {returnx;? ? }else{return-x;? ? }}
上述abs()函數(shù)的定義如下:
function指出這是一個函數(shù)定義抵窒;
abs是函數(shù)的名稱;
(x)括號內(nèi)列出函數(shù)的參數(shù)叠骑,多個參數(shù)以,分隔李皇;
{ ... }之間的代碼是函數(shù)體,可以包含若干語句宙枷,甚至可以沒有任何語句掉房。
請注意,函數(shù)體內(nèi)部的語句在執(zhí)行時朦拖,一旦執(zhí)行到return時圃阳,函數(shù)就執(zhí)行完畢厌衔,并將結(jié)果返回璧帝。因此,函數(shù)內(nèi)部通過條件判斷和循環(huán)可以實現(xiàn)非常復雜的邏輯富寿。
如果沒有return語句睬隶,函數(shù)執(zhí)行完畢后也會返回結(jié)果,只是結(jié)果為undefined页徐。
由于JavaScript的函數(shù)也是一個對象苏潜,上述定義的abs()函數(shù)實際上是一個函數(shù)對象,而函數(shù)名abs可以視為指向該函數(shù)的變量变勇。
因此恤左,第二種定義函數(shù)的方式如下:
varabs =function(x){if(x >=0) {returnx;? ? }else{return-x;? ? }};
在這種方式下贴唇,function (x) { ... }是一個匿名函數(shù),它沒有函數(shù)名飞袋。但是戳气,這個匿名函數(shù)賦值給了變量abs,所以巧鸭,通過變量abs就可以調(diào)用該函數(shù)瓶您。
上述兩種定義完全等價,注意第二種方式按照完整語法需要在函數(shù)體末尾加一個;纲仍,表示賦值語句結(jié)束呀袱。
調(diào)用函數(shù)
調(diào)用函數(shù)時,按順序傳入?yún)?shù)即可:
abs(10);//返回10abs(-9);//返回9
由于JavaScript允許傳入任意個參數(shù)而不影響調(diào)用郑叠,因此傳入的參數(shù)比定義的參數(shù)多也沒有問題夜赵,雖然函數(shù)內(nèi)部并不需要這些參數(shù):
abs(10,'blablabla');// 返回10abs(-9,'haha','hehe',null);// 返回9
傳入的參數(shù)比定義的少也沒有問題:
abs();//返回NaN
此時abs(x)函數(shù)的參數(shù)x將收到undefined,計算結(jié)果為NaN乡革。
要避免收到undefined油吭,可以對參數(shù)進行檢查:
functionabs(x){if(typeofx !=='number') {throw'Not a number';? ? }if(x >=0) {returnx;? ? }else{return-x;? ? }}
arguments
JavaScript還有一個免費贈送的關鍵字arguments,它只在函數(shù)內(nèi)部起作用署拟,并且永遠指向當前函數(shù)的調(diào)用者傳入的所有參數(shù)婉宰。arguments類似Array但它不是一個Array:
'use strict'
?Run
利用arguments,你可以獲得調(diào)用者傳入的所有參數(shù)推穷。也就是說心包,即使函數(shù)不定義任何參數(shù),還是可以拿到參數(shù)的值:
functionabs(){if(arguments.length ===0) {return0;? ? }varx = arguments[0];returnx >=0? x : -x;}abs();// 0abs(10);// 10abs(-9);// 9
實際上arguments最常用于判斷傳入?yún)?shù)的個數(shù)馒铃。你可能會看到這樣的寫法:
// foo(a[, b], c)// 接收2~3個參數(shù)蟹腾,b是可選參數(shù),如果只傳2個參數(shù)区宇,b默認為null:functionfoo(a, b, c){if(arguments.length ===2) {// 實際拿到的參數(shù)是a和b娃殖,c為undefinedc = b;// 把b賦給cb =null;// b變?yōu)槟J值}// ...}
要把中間的參數(shù)b變?yōu)椤翱蛇x”參數(shù),就只能通過arguments判斷议谷,然后重新調(diào)整參數(shù)并賦值炉爆。
rest參數(shù)
由于JavaScript函數(shù)允許接收任意個參數(shù),于是我們就不得不用arguments來獲取所有參數(shù):
functionfoo(a, b){vari, rest = [];if(arguments.length >2) {for(i =2; i
為了獲取除了已定義參數(shù)a卧晓、b之外的參數(shù)芬首,我們不得不用arguments,并且循環(huán)要從索引2開始以便排除前兩個參數(shù)逼裆,這種寫法很別扭郁稍,只是為了獲得額外的rest參數(shù),有沒有更好的方法胜宇?
ES6標準引入了rest參數(shù)耀怜,上面的函數(shù)可以改寫為:
functionfoo(a, b, ...rest){console.log('a = '+ a);? ? console.log('b = '+ b);? ? console.log(rest);}foo(1,2,3,4,5);// 結(jié)果:// a = 1// b = 2// Array [ 3, 4, 5 ]foo(1);// 結(jié)果:// a = 1// b = undefined// Array []
rest參數(shù)只能寫在最后恢着,前面用...標識,從運行結(jié)果可知财破,傳入的參數(shù)先綁定a然评、b,多余的參數(shù)以數(shù)組形式交給變量rest狈究,所以碗淌,不再需要arguments我們就獲取了全部參數(shù)。
如果傳入的參數(shù)連正常定義的參數(shù)都沒填滿抖锥,也不要緊亿眠,rest參數(shù)會接收一個空數(shù)組(注意不是undefined)。
因為rest參數(shù)是ES6新標準磅废,所以你需要測試一下瀏覽器是否支持纳像。請用rest參數(shù)編寫一個sum()函數(shù),接收任意個參數(shù)并返回它們的和:
'use strict';
// 測試:
var i, args = [];
for (i=1; i<=100; i++) {
? ? args.push(i);
}
if (sum() !== 0) {
? ? console.log('測試失敗: sum() = ' + sum());
} else if (sum(1) !== 1) {
? ? console.log('測試失敗: sum(1) = ' + sum(1));
} else if (sum(2, 3) !== 5) {
? ? console.log('測試失敗: sum(2, 3) = ' + sum(2, 3));
} else if (sum.apply(null, args) !== 5050) {
? ? console.log('測試失敗: sum(1, 2, 3, ..., 100) = ' + sum.apply(null, args));
} else {
? ? console.log('測試通過!');
}
?Run
小心你的return語句
前面我們講到了JavaScript引擎有一個在行末自動添加分號的機制拯勉,這可能讓你栽到return語句的一個大坑:
functionfoo(){return{ name:'foo'};}foo();// { name: 'foo' }
如果把return語句拆成兩行:
functionfoo(){return{ name:'foo'};}foo();// undefined
要小心了竟趾,由于JavaScript引擎在行末自動添加分號的機制,上面的代碼實際上變成了:
functionfoo(){return;// 自動添加了分號宫峦,相當于return undefined;{ name:'foo'};// 這行語句已經(jīng)沒法執(zhí)行到了}
所以正確的多行寫法是:
functionfoo(){return{// 這里不會自動加分號岔帽,因為{表示語句尚未結(jié)束name:'foo'};}
練習
定義一個計算圓面積的函數(shù)area_of_circle(),它有兩個參數(shù):
r: 表示圓的半徑导绷;
pi: 表示π的值犀勒,如果不傳,則默認3.14
'use strict';
function area_of_circle(r, pi) {
}
// 測試:
if (area_of_circle(2) === 12.56 && area_of_circle(2, 3.1416) === 12.5664) {
? ? console.log('測試通過');
} else {
? ? console.log('測試失敗');
}
?Run
在JavaScript中妥曲,用var申明的變量實際上是有作用域的贾费。
如果一個變量在函數(shù)體內(nèi)部申明,則該變量的作用域為整個函數(shù)體檐盟,在函數(shù)體外不可引用該變量:
'use strict';functionfoo(){varx =1;? ? x = x +1;}x = x +2;// ReferenceError! 無法在函數(shù)體外引用變量x
如果兩個不同的函數(shù)各自申明了同一個變量褂萧,那么該變量只在各自的函數(shù)體內(nèi)起作用。換句話說葵萎,不同函數(shù)內(nèi)部的同名變量互相獨立导犹,互不影響:
'use strict';functionfoo(){varx =1;? ? x = x +1;}functionbar(){varx ='A';? ? x = x +'B';}
由于JavaScript的函數(shù)可以嵌套,此時陌宿,內(nèi)部函數(shù)可以訪問外部函數(shù)定義的變量锡足,反過來則不行:
'use strict';functionfoo(){varx =1;functionbar(){vary = x +1;// bar可以訪問foo的變量x!}varz = y +1;// ReferenceError! foo不可以訪問bar的變量y!}
如果內(nèi)部函數(shù)和外部函數(shù)的變量名重名怎么辦?來測試一下:
'use strict';
?Run
這說明JavaScript的函數(shù)在查找變量時從自身函數(shù)定義開始壳坪,從“內(nèi)”向“外”查找。如果內(nèi)部函數(shù)定義了與外部函數(shù)重名的變量掰烟,則內(nèi)部函數(shù)的變量將“屏蔽”外部函數(shù)的變量爽蝴。
變量提升
JavaScript的函數(shù)定義有個特點沐批,它會先掃描整個函數(shù)體的語句,把所有申明的變量“提升”到函數(shù)頂部:
'use strict';functionfoo(){varx ='Hello, '+ y;? ? console.log(x);vary ='Bob';}foo();
雖然是strict模式蝎亚,但語句var x = 'Hello, ' + y;并不報錯九孩,原因是變量y在稍后申明了。但是console.log顯示Hello, undefined发框,說明變量y的值為undefined躺彬。這正是因為JavaScript引擎自動提升了變量y的聲明,但不會提升變量y的賦值梅惯。
對于上述foo()函數(shù)宪拥,JavaScript引擎看到的代碼相當于:
functionfoo(){vary;// 提升變量y的申明,此時y為undefinedvarx ='Hello, '+ y;? ? console.log(x);? ? y ='Bob';}
由于JavaScript的這一怪異的“特性”铣减,我們在函數(shù)內(nèi)部定義變量時她君,請嚴格遵守“在函數(shù)內(nèi)部首先申明所有變量”這一規(guī)則。最常見的做法是用一個var申明函數(shù)內(nèi)部用到的所有變量:
functionfoo(){varx =1,// x初始化為1y = x +1,// y初始化為2z, i;// z和i為undefined// 其他語句:for(i=0; i<100; i++) {? ? ? ? ...? ? }}
全局作用域
不在任何函數(shù)內(nèi)定義的變量就具有全局作用域葫哗。實際上缔刹,JavaScript默認有一個全局對象window,全局作用域的變量實際上被綁定到window的一個屬性:
'use strict';varcourse ='Learn JavaScript';alert(course);// 'Learn JavaScript'alert(window.course);// 'Learn JavaScript'
因此劣针,直接訪問全局變量course和訪問window.course是完全一樣的校镐。
你可能猜到了,由于函數(shù)定義有兩種方式捺典,以變量方式var foo = function () {}定義的函數(shù)實際上也是一個全局變量灭翔,因此,頂層函數(shù)的定義也被視為一個全局變量辣苏,并綁定到window對象:
'use strict';functionfoo(){alert('foo');}foo();// 直接調(diào)用foo()window.foo();// 通過window.foo()調(diào)用
進一步大膽地猜測肝箱,我們每次直接調(diào)用的alert()函數(shù)其實也是window的一個變量:
'use strict';
window.alert('調(diào)用window.alert()');
// 把alert保存到另一個變量:
var old_alert = window.alert;
// 給alert賦一個新函數(shù):
window.alert = function () {}
// 恢復alert:
window.alert = old_alert;
alert('又可以用alert()了!');
?Run
這說明JavaScript實際上只有一個全局作用域。任何變量(函數(shù)也視為變量)稀蟋,如果沒有在當前函數(shù)作用域中找到煌张,就會繼續(xù)往上查找,最后如果在全局作用域中也沒有找到退客,則報ReferenceError錯誤骏融。
名字空間
全局變量會綁定到window上,不同的JavaScript文件如果使用了相同的全局變量萌狂,或者定義了相同名字的頂層函數(shù)档玻,都會造成命名沖突,并且很難被發(fā)現(xiàn)茫藏。
減少沖突的一個方法是把自己的所有變量和函數(shù)全部綁定到一個全局變量中误趴。例如:
// 唯一的全局變量MYAPP:varMYAPP = {};// 其他變量:MYAPP.name ='myapp';MYAPP.version =1.0;// 其他函數(shù):MYAPP.foo =function(){return'foo';};
把自己的代碼全部放入唯一的名字空間MYAPP中,會大大減少全局變量沖突的可能务傲。
許多著名的JavaScript庫都是這么干的:jQuery凉当,YUI枣申,underscore等等。
局部作用域
由于JavaScript的變量作用域?qū)嶋H上是函數(shù)內(nèi)部看杭,我們在for循環(huán)等語句塊中是無法定義具有局部作用域的變量的:
'use strict';functionfoo(){for(vari=0; i<100; i++) {//}? ? i +=100;// 仍然可以引用變量i}
為了解決塊級作用域忠藤,ES6引入了新的關鍵字let,用let替代var可以申明一個塊級作用域的變量:
'use strict';functionfoo(){varsum =0;for(leti=0; i<100; i++) {? ? ? ? sum += i;? ? }// SyntaxError:i +=1;}
常量
由于var和let申明的是變量楼雹,如果要申明一個常量模孩,在ES6之前是不行的,我們通常用全部大寫的變量來表示“這是一個常量贮缅,不要修改它的值”:
varPI =3.14;
ES6標準引入了新的關鍵字const來定義常量榨咐,const與let都具有塊級作用域:
'use strict';constPI =3.14;PI =3;// 某些瀏覽器不報錯,但是無效果携悯!PI;// 3.14
解構(gòu)賦值
從ES6開始祭芦,JavaScript引入了解構(gòu)賦值,可以同時對一組變量進行賦值憔鬼。
什么是解構(gòu)賦值龟劲?我們先看看傳統(tǒng)的做法,如何把一個數(shù)組的元素分別賦值給幾個變量:
vararray= ['hello','JavaScript','ES6'];varx =array[0];vary =array[1];varz =array[2];
現(xiàn)在轴或,在ES6中昌跌,可以使用解構(gòu)賦值,直接對多個變量同時賦值:
'use strict';
// 如果瀏覽器支持解構(gòu)賦值就不會報錯:
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
?Run
注意照雁,對數(shù)組元素進行解構(gòu)賦值時蚕愤,多個變量要用[...]括起來。
如果數(shù)組本身還有嵌套饺蚊,也可以通過下面的形式進行解構(gòu)賦值萍诱,注意嵌套層次和位置要保持一致:
let[x, [y, z]] = ['hello', ['JavaScript','ES6']];x;// 'hello'y;// 'JavaScript'z;// 'ES6'
解構(gòu)賦值還可以忽略某些元素:
let[, , z] = ['hello','JavaScript','ES6'];// 忽略前兩個元素,只對z賦值第三個元素z;// 'ES6'
如果需要從一個對象中取出若干屬性污呼,也可以使用解構(gòu)賦值裕坊,便于快速獲取對象的指定屬性:
'use strict';
var person = {
? ? name: '小明',
? ? age: 20,
? ? gender: 'male',
? ? passport: 'G-12345678',
? ? school: 'No.4 middle school'
};
var {name, age, passport} = person;
?Run
對一個對象進行解構(gòu)賦值時,同樣可以直接對嵌套的對象屬性進行賦值燕酷,只要保證對應的層次是一致的:
varperson = {? ? name:'小明',? ? age:20,? ? gender:'male',? ? passport:'G-12345678',? ? school:'No.4 middle school',? ? address: {? ? ? ? city:'Beijing',? ? ? ? street:'No.1 Road',? ? ? ? zipcode:'100001'}};var{name, address: {city, zip}} = person;name;// '小明'city;// 'Beijing'zip;// undefined, 因為屬性名是zipcode而不是zip// 注意: address不是變量籍凝,而是為了讓city和zip獲得嵌套的address對象的屬性:address;// Uncaught ReferenceError: address is not defined
使用解構(gòu)賦值對對象屬性進行賦值時,如果對應的屬性不存在苗缩,變量將被賦值為undefined饵蒂,這和引用一個不存在的屬性獲得undefined是一致的。如果要使用的變量名和屬性名不一致酱讶,可以用下面的語法獲韧硕ⅰ:
varperson = {? ? name:'小明',? ? age:20,? ? gender:'male',? ? passport:'G-12345678',? ? school:'No.4 middle school'};// 把passport屬性賦值給變量id:let{name, passport:id} = person;name;// '小明'id;// 'G-12345678'// 注意: passport不是變量,而是為了讓變量id獲得passport屬性:passport;// Uncaught ReferenceError: passport is not defined
解構(gòu)賦值還可以使用默認值,這樣就避免了不存在的屬性返回undefined的問題:
varperson = {? ? name:'小明',? ? age:20,? ? gender:'male',? ? passport:'G-12345678'};// 如果person對象沒有single屬性得问,默認賦值為true:var{name, single=true} = person;name;// '小明'single;// true
有些時候囤攀,如果變量已經(jīng)被聲明了软免,再次賦值的時候宫纬,正確的寫法也會報語法錯誤:
// 聲明變量:varx, y;// 解構(gòu)賦值:{x, y} = { name:'小明', x:100, y:200};// 語法錯誤: Uncaught SyntaxError: Unexpected token =
這是因為JavaScript引擎把{開頭的語句當作了塊處理,于是=不再合法膏萧。解決方法是用小括號括起來:
({x, y} = { name: '小明', x: 100, y: 200});
使用場景
解構(gòu)賦值在很多時候可以大大簡化代碼漓骚。例如,交換兩個變量x和y的值榛泛,可以這么寫蝌蹂,不再需要臨時變量:
varx=1, y=2;[x, y] = [y, x]
快速獲取當前頁面的域名和路徑:
var{hostname:domain, pathname:path} = location;
如果一個函數(shù)接收一個對象作為參數(shù),那么曹锨,可以使用解構(gòu)直接把對象的屬性綁定到變量中孤个。例如,下面的函數(shù)可以快速創(chuàng)建一個Date對象:
functionbuildDate({year, month, day, hour=0, minute=0, second=0}){returnnewDate(year +'-'+ month +'-'+ day +' '+ hour +':'+ minute +':'+ second);}
它的方便之處在于傳入的對象只需要year沛简、month和day這三個屬性:
buildDate({year:2017,month:1,day:1});//SunJan01201700:00:00GMT+0800(CST)
也可以傳入hour齐鲤、minute和second屬性:
buildDate({year:2017,month:1,day:1,hour:20,minute:15});//SunJan01201720:15:00GMT+0800(CST)
使用解構(gòu)賦值可以減少代碼量,但是椒楣,需要在支持ES6解構(gòu)賦值特性的現(xiàn)代瀏覽器中才能正常運行给郊。目前支持解構(gòu)賦值的瀏覽器包括Chrome,F(xiàn)irefox捧灰,Edge等淆九。
在一個對象中綁定函數(shù),稱為這個對象的方法毛俏。
在JavaScript中炭庙,對象的定義是這樣的:
varxiaoming = {? ? name:'小明',? ? birth:1990};
但是,如果我們給xiaoming綁定一個函數(shù)煌寇,就可以做更多的事情焕蹄。比如,寫個age()方法唧席,返回xiaoming的年齡:
varxiaoming = {? ? name:'小明',? ? birth:1990,? ? age:function(){vary =newDate().getFullYear();returny -this.birth;? ? }};xiaoming.age;// function xiaoming.age()xiaoming.age();// 今年調(diào)用是25,明年調(diào)用就變成26了
綁定到對象上的函數(shù)稱為方法擦盾,和普通函數(shù)也沒啥區(qū)別,但是它在內(nèi)部使用了一個this關鍵字淌哟,這個東東是什么迹卢?
在一個方法內(nèi)部,this是一個特殊變量徒仓,它始終指向當前對象腐碱,也就是xiaoming這個變量。所以,this.birth可以拿到xiaoming的birth屬性症见。
讓我們拆開寫:
functiongetAge(){vary =newDate().getFullYear();returny -this.birth;}varxiaoming = {? ? name:'小明',? ? birth:1990,? ? age: getAge};xiaoming.age();// 25, 正常結(jié)果getAge();// NaN
單獨調(diào)用函數(shù)getAge()怎么返回了NaN喂走?請注意,我們已經(jīng)進入到了JavaScript的一個大坑里谋作。
JavaScript的函數(shù)內(nèi)部如果調(diào)用了this芋肠,那么這個this到底指向誰?
答案是遵蚜,視情況而定帖池!
如果以對象的方法形式調(diào)用,比如xiaoming.age()吭净,該函數(shù)的this指向被調(diào)用的對象睡汹,也就是xiaoming,這是符合我們預期的寂殉。
如果單獨調(diào)用函數(shù)囚巴,比如getAge(),此時友扰,該函數(shù)的this指向全局對象彤叉,也就是window。
坑爹盎烂省姆坚!
更坑爹的是,如果這么寫:
varfn = xiaoming.age;// 先拿到xiaoming的age函數(shù)fn();// NaN
也是不行的实愚!要保證this指向正確兼呵,必須用obj.xxx()的形式調(diào)用!
由于這是一個巨大的設計錯誤腊敲,要想糾正可沒那么簡單击喂。ECMA決定,在strict模式下讓函數(shù)的this指向undefined碰辅,因此懂昂,在strict模式下,你會得到一個錯誤:
'use strict';varxiaoming = {? ? name:'小明',? ? birth:1990,? ? age:function(){vary =newDate().getFullYear();returny -this.birth;? ? }};varfn = xiaoming.age;fn();// Uncaught TypeError: Cannot read property 'birth' of undefined
這個決定只是讓錯誤及時暴露出來没宾,并沒有解決this應該指向的正確位置凌彬。
有些時候,喜歡重構(gòu)的你把方法重構(gòu)了一下:
'use strict';varxiaoming = {? ? name:'小明',? ? birth:1990,? ? age:function(){functiongetAgeFromBirth(){vary =newDate().getFullYear();returny -this.birth;? ? ? ? }returngetAgeFromBirth();? ? }};xiaoming.age();// Uncaught TypeError: Cannot read property 'birth' of undefined
結(jié)果又報錯了循衰!原因是this指針只在age方法的函數(shù)內(nèi)指向xiaoming吉懊,在函數(shù)內(nèi)部定義的函數(shù)嘶卧,this又指向undefined了=烹埂(在非strict模式下火俄,它重新指向全局對象window!)
修復的辦法也不是沒有,我們用一個that變量首先捕獲this:
'use strict';varxiaoming = {? ? name:'小明',? ? birth:1990,? ? age:function(){varthat =this;// 在方法內(nèi)部一開始就捕獲thisfunctiongetAgeFromBirth(){vary =newDate().getFullYear();returny - that.birth;// 用that而不是this}returngetAgeFromBirth();? ? }};xiaoming.age();// 25
用var that = this;先鱼,你就可以放心地在方法內(nèi)部定義其他函數(shù)俭正,而不是把所有語句都堆到一個方法中。
apply
雖然在一個獨立的函數(shù)調(diào)用中焙畔,根據(jù)是否是strict模式掸读,this指向undefined或window,不過闹蒜,我們還是可以控制this的指向的寺枉!
要指定函數(shù)的this指向哪個對象抑淫,可以用函數(shù)本身的apply方法绷落,它接收兩個參數(shù),第一個參數(shù)就是需要綁定的this變量始苇,第二個參數(shù)是Array砌烁,表示函數(shù)本身的參數(shù)。
用apply修復getAge()調(diào)用:
functiongetAge(){vary =newDate().getFullYear();returny -this.birth;}varxiaoming = {? ? name:'小明',? ? birth:1990,? ? age: getAge};xiaoming.age();// 25getAge.apply(xiaoming, []);// 25, this指向xiaoming, 參數(shù)為空
另一個與apply()類似的方法是call()催式,唯一區(qū)別是:
apply()把參數(shù)打包成Array再傳入函喉;
call()把參數(shù)按順序傳入。
比如調(diào)用Math.max(3, 5, 4)荣月,分別用apply()和call()實現(xiàn)如下:
Math.max.apply(null, [3,5,4]);// 5Math.max.call(null,3,5,4);// 5
對普通函數(shù)調(diào)用管呵,我們通常把this綁定為null。
裝飾器
利用apply()哺窄,我們還可以動態(tài)改變函數(shù)的行為捐下。
JavaScript的所有對象都是動態(tài)的,即使內(nèi)置的函數(shù)萌业,我們也可以重新指向新的函數(shù)坷襟。
現(xiàn)在假定我們想統(tǒng)計一下代碼一共調(diào)用了多少次parseInt(),可以把所有的調(diào)用都找出來生年,然后手動加上count += 1婴程,不過這樣做太傻了。最佳方案是用我們自己的函數(shù)替換掉默認的parseInt():
'use strict';
var count = 0;
var oldParseInt = parseInt; // 保存原函數(shù)
window.parseInt = function () {
? ? count += 1;
? ? return oldParseInt.apply(null, arguments); // 調(diào)用原函數(shù)
};
?Run