JavaScript2

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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抱婉,一起剝皮案震驚了整個濱河市档叔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒸绩,老刑警劉巖衙四,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異侵贵,居然都是意外死亡届搁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卡睦,“玉大人宴胧,你說我怎么就攤上這事”矶停” “怎么了恕齐?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瞬逊。 經(jīng)常有香客問我显歧,道長,這世上最難降的妖魔是什么确镊? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任士骤,我火速辦了婚禮,結(jié)果婚禮上蕾域,老公的妹妹穿的比我還像新娘拷肌。我一直安慰自己,他們只是感情好旨巷,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布巨缘。 她就那樣靜靜地躺著,像睡著了一般采呐。 火紅的嫁衣襯著肌膚如雪若锁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天斧吐,我揣著相機與錄音又固,去河邊找鬼。 笑死会通,一個胖子當著我的面吹牛口予,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涕侈,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼沪停,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了裳涛?” 一聲冷哼從身側(cè)響起木张,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎端三,沒想到半個月后舷礼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡郊闯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年妻献,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛛株。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡育拨,死狀恐怖谨履,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情熬丧,我是刑警寧澤笋粟,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站析蝴,受9級特大地震影響害捕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜闷畸,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一尝盼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腾啥,春花似錦东涡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽组贺。三九已至凸舵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間失尖,已是汗流浹背啊奄。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掀潮,地道東北人菇夸。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像仪吧,于是被迫代替她去往敵國和親庄新。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 開篇語 最近筆者考完了所有的考試薯鼠,只需要做課程設計就可以等著滾回家睡大覺了择诈。所以要開始著手傷心團隊的項目事宜了。最...
    張照博閱讀 926評論 0 21
  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,294評論 0 10
  • 1出皇、Math對象 Math.PI圓周率π Math.ceil()向上取整返回的是大于或等于函數(shù)參數(shù)羞芍,并且與之最接近...
    Lizzy95閱讀 357評論 0 0
  • pyspark.sql模塊 模塊上下文 Spark SQL和DataFrames的重要類: pyspark.sql...
    mpro閱讀 9,446評論 0 13
  • 通過源碼呈現(xiàn) Spark Streaming 的底層機制。 1. 初始化與接收數(shù)據(jù) Spark Streaming...
    三萬_chenbing閱讀 683評論 0 0