溫故js系列(8)-詳解代碼中的流程控制

前端學習:教程&開發模塊化/規範化/工程化/優化&工具/調試&值得關注的博客/Git&面試-前端資源彙總javascript

歡迎提issues斧正:流程控制前端

JavaScript-流程控制

JavaScript是單線程的,一個語句一個語句的執行。語句是執行過程當中的流程、限定與約定,形式上能夠是單行語句,或者由一對大括號"{}"括起來的複合語句,複合語句總體能夠做爲一個單行語句處理。那麼,代碼中,流程控制就顯得格外重要了。JavaScript也規定了一些語句和一些關鍵字用於流程控制。java

if語句

if (條件表達式) {語句}

if(2 > 1){
    console.log('xzavier win');
}

javascript會判斷括號裏的條件表達式的值。若是值爲truthy類型的值,也就是真,則執行後面的一條語句,不然不執行。這個語句無可厚非,使用率也是極高的,有時候會使用短鏈接來替代:git

2 > 1 && console.log('xzavier win');

想了解此運算符以及更多運算符參考: 運算符詳解程序員

關於判斷參見本系列文章:代碼中的那些判斷github

if (條件表達式) {語句;} else {語句;}

if爲真值(Boolean轉換),則執行if裏的代碼,不然執行else裏的代碼。面試

if (2 > 1) {
    console.log('xzavier win'); 
} else {
    console.log('xzavier fail'); 
}

這個語句的使用也無需多說,有時候會使用三目運算符代替:segmentfault

2 > 3 ? console.log('xzavier win') : console.log('xzavier fail');

有時候設計到賦值的if...else,也可使用短鏈接。同參考上面一篇文章。數組

if (條件表達式) {語句;} else if (條件表達式) {語句;} ... else {語句;}

if (1 > 2) {
    console.log('xzavier win'); 
} else if(3 > 2){
    console.log('xzavier win2'); 
} else {
    console.log('xzavier fail'); 
}

JavaScript實際上是沒有else if的,else if算是一種封裝。看這裏的js判斷:緩存

var a = 3, b = 2;
a > b // true
a > b // false
a = b // false

額(⊙o⊙)…這不是在逗我嗎?不,我只是用最簡單的判斷說一下js的判斷:

a >= b // true  
//其實它是先判斷了 a < b 爲false 再用「非正即反」 來返回 a >= b 的值

不信?看這個:

var a = {};  // 你能夠添加任意屬性 {x:3};
var b = {};  // 你能夠添加任意屬性 {z:2};;
a < b;    // false
a == b;   // false
a > b;    // false

對象的比較,能夠參見本系列判斷相關的文章。
看上面沒問題是吧,可是用 >= 就不同了:

a <= b;    // true
a >= b;    // true

我...因此,也根據語言規範,先對 <= 的另外一面求值,再「非正即反」。

那麼回到else if呢,上面的代碼其實最終是這樣的:

if (1 > 2) {
    console.log('xzavier win'); 
} else {
     if(3 > 2){
        console.log('xzavier win2'); 
    } else {
        console.log('xzavier fail'); 
    }
}

把它拆分爲多個if...else... 這只是個探究,由於else if司空見慣,代碼又少,語義很好,因此使用得多。因此,盡情的使用吧,個人意思是別避開寫else...if...,不是寫一大堆else...if...,若是有一堆else...if...,那仍是用下面的switch吧

switch語句

switch 語句是多重條件判斷,用於多個值相等的比較。

var xzavier = 'boy';
switch (xzavier) {
    case 'girl' :
        console.log('xzavier is a girl');
        break;
    case 'boy' :
        console.log('xzavier is a boy');
        break;
    case 'man' :
        console.log('xzavier is a man');
        break;
    default : 
        console.log('time error');
}

if和switch之間能夠轉換,當條件過多時,使用switch可讓代碼更清晰,更好看。

這裏說一下switch,它對括號裏的語句求值一次,而後將返回值與每一個case表達式進行匹配。若是找到一個匹配,就會開始執行那個匹配的case裏的代碼,直到遇到一個break或者直到switch塊末尾。
因此,寫好break,很重要:

var xzavier = 'boy';
switch (xzavier) {
    case 'girl' :
        console.log('xzavier is a girl');
        break;
    case 'boy' :
        console.log('xzavier is a boy');
    case 'man' :
        console.log('xzavier is a man');
        break;
    default : 
        console.log('time error');
}
VM293:7 xzavier is a boy
VM293:9 xzavier is a man

沒有break掉,你的業務代碼可能就出現bug.使用switch就須要好好的考慮業務場景,該break的地方切勿忘記。

另外,表達式的返回值和每個case表達式之間的匹配判斷使用的是全等運算符===

var xzavier = '1';
switch (xzavier) {
    case 1 :
        console.log('xzavier is a girl');
        break;
    case 2 :
        console.log('xzavier is a boy');
    case 3 :
        console.log('xzavier is a man');
        break;
    default : 
        console.log('type error');
}
// type error

還有一點就是 default 非必需,位置也能夠不固定。由於js老是先去匹配表達式的返回值和每個case表達式,最終沒有找到匹配的case,就會尋找default,找到了則執行default裏的語句。沒找到則執行到switch塊末尾。

do...while語句

do...while 語句是一種先運行,後判斷的循環語句。也就是說,無論條件是否知足,至少先運行一次循環體。

var xzavier = 1; 
do {
    console.log(xzavier);
    xzavier++;
} while (xzavier <= 10); 
// 1 2 3 4 5 6 7 8 9 10

var xzavier = 1; 
do {
    console.log(xzavier);
    xzavier++;
} while (xzavier <= 0);//先運行一次,再判斷
// 1

while語句

while 語句是一種先判斷,後運行的循環語句。

var xzavier = 1; 
while (xzavier <= 10) {  //先判斷,再運行
    console.log(xzavier);
    xzavier++;
}
// 1 2 3 4 5 6 7 8 9 10

var xzavier = 1; 
while (xzavier <= 0) {  //先判斷,再運行
    console.log(xzavier); // 不會執行
    xzavier++;
}

for語句

for 語句也是一種先判斷,後運行的循環語句。但它具備在執行循環以前初始變量和定義循環後要執行代碼的能力。

for (var i = 0; i <= 10 ; i++) { 
    console.log(i); 
}

第1步: 聲明變量var i = 1;
第2步: 判斷i <= 10
第3步: console.log(i);
第4步: i++
第5步: 重複2-5,直到判斷爲false

這裏咱們常常會遇到這樣的寫法:

for (var i = 0; i <= data.length ; i++) {

建議先緩存data.length 爲一個變量:

for (var i = 0, l = data.length; i <= l ; i++) {

這樣作的目的並非必定能提高程序的性能,由於length屬性是一個字典屬性,讀取它和讀取變量的複雜度都爲O(1)
這樣作主要是爲了防止在for循環中改變了data.length,具體使用還應視具體代碼而定。

固然,若是涉及到DOM,那麼緩存變量就必定能提高代碼性能了。

for (var i = 0; i <= $('.item').length ; i++) {

每循環一次都$('.item') 就相對耗費性能了,與DOM有關的讀取大多狀況都應先用變量緩存,特殊業務視狀況而定。

var ietm_l = $('.item').length;
for (var i = 0; i <= ietm_l ; i++) {

for...in語句

for...in 語句能夠用來枚舉對象的屬性。

var xzavier = { 
    'name' : 'xzavier', 
    'age' : 23,
    'job' : 'Jser',
    'width' : 100,
    'height' : 100,
    'border' : 10
};
for (var i in xzavier) { 
    console.log(i);
}
//name age job width height border
for (var i in xzavier) { 
    console.log(xzavier[i]);
}
//xzavier 23 Jser 100 100 10

for...in是爲遍歷對象屬性設計的,可是它能夠遍歷數組,我去,由於數組也是對象啊。 不過,for...in 循環遍歷的是對象的屬性,而不是數組的索引。咱們看一下打印的結果就知道了:

var arr = [1,2,3];
for(var i in arr) {
    console.log(typeof(i));
}
// string string string

而經典的for語句遍歷是數組的索引:

var arr = [1,2,3];
for(var i = 0; i < arr.length; i++) {
    console.log(typeof(i));
}
// number number number

咱們用for...in遍歷數組時,取arr[i]時是咱們指望獲得的結果:

var arr = [1,2,3];
for(var i in arr) {
    console.log(i + '--' + arr[i]);
}
// 0--1 1--2 2--3

可是:

var arr = [1,2,3];
arr.name = 'xzavier';
for(var i in arr) {
    console.log(i + '--' + arr[i]);
}
VM230:4 0--1
VM230:4 1--2
VM230:4 2--3
VM230:4 name--xzavier

它訪問了數組新增的 "name" 屬性,由於 for-in 遍歷了對象的全部屬性。

甚至包括原型鏈上的全部可枚舉的屬性:

var arr = [1,2,3];
    arr.name = 'xzavier';Array.prototype.oname = 'xzavier.chris'
    for(var i in arr) {
        console.log(i + '--' + arr[i]);
    }
VM236:4 0--1
VM236:4 1--2
VM236:4 2--3
VM236:4 name--xzavier
VM236:4 oname--xzavier.chris

顯然,咱們習慣的數組遍歷的結果是隻有1,2,3 這樣的結果的。

因此,綜合來講:
for...in 循環遍歷的是對象的屬性,而不是數組的索引。正如例子,輸出的索引值 "0"、 "1"、 "2"不是 Number 類型的,而是 String 類型的,由於是對象的屬性都是string類型。

這樣看來,for...in是不適合遍歷數組的。確實,for...in原本就是爲遍歷對象屬性設計的。

不過,在對於比較特殊的數組,for...in或許有用:

var arr = Array(1000), count = 0;
arr[100] = 100;
arr[200] = 200;
arr[300] = 300;
for(var i in arr) {
    count += 1;
    console.log(i + '--' + arr[i]);
}
console.log(count);
VM242:7 100--100
VM242:7 200--200
VM242:7 300--300
VM242:9 3   // 只循環了3次額

而for循環:

var arr = Array(1000), count = 0;
arr[100] = 100;
arr[200] = 200;
arr[300] = 300;
for(var i = 0; i < arr.length; i++) {
    count += 1;
    console.log(i + '--' + arr[i]);
}
console.log(count);
// count 1000  循環了這麼屢次
// 打印出100 200 300 和一堆的undefined

so :
for...in 只會遍歷對象中存在的實體,而for 循環則會遍歷 1000 次。若是你的業務恰好有這樣的須要,那麼合適的使用for..in遍歷數組將會發揮更奇妙的做用。
可是通常來看,這樣用到的機會不多,上面的指定索引是一種,另外你使用了delete去刪除數組元素以後(非特殊,不要用delete去刪除),也是能夠這樣遍歷的:

var arr = [1,2,3];
delete arr[1]
for(var i in arr) {
    console.log(i + '--' + arr[i]);
}
VM246:4 0--1
VM246:4 2--3

可是,通常狀況誰容許你用delete去刪除數組元素呢。關於數組能夠參考個人另外一篇對數組詳解的文章。

forEach循環

for...in是爲遍歷對象屬性設計的,固然也能夠遍歷數組。後來,ES5也爲素組設計了一個forEach方法來遍歷。
forEach方法爲數組中含有有效值的每一項執行一次 callback。callback有三個參數:
callback(數組當前項的值,數組當前項的索引,數組對象自己)

var arr = [1,2,3,4,5,'xzavier',7];
arr.forEach(function (value, index, arr) {
    console.log(value);
});
// 1 2 3 4 5 xzavier 7

注意:

1.使用forEach循環的時候,須要注意,forEach 遍歷的範圍在第一次調用 callback 前就會肯定。調用forEach 後添加到數組中的項不會被 callback 訪問到。

在數組後面添加:

var arr = [1,2,3,4,5,'xzavier',7];
arr.forEach(function (value, index, arr) {
    if (value == 3) {
        arr.push(8);
    }
    console.log(value);
});
// 1 2 3 4 5 xzavier 7

在數組前面添加:

var arr = [1,2,3,4,5,'xzavier',7];
arr.forEach(function (value, index, arr) {
    if (value == 3) {
        arr.unshift(0);
    }
    console.log(index + '--' +value);
});
VM316:6 0--1
VM316:6 1--2
VM316:6 2--3
VM316:6 3--3
VM316:6 4--3
VM316:6 5--3
VM316:6 6--3

什麼狀況?由於你在數組前添加1項後,遍歷下一項的值,發現值仍是3,因此後面的遍歷一直都走到if語句裏了。
可是數組的遍歷範圍是不可變的。

2.若是已經存在的值被改變,則傳遞給 callback 的值是 forEach 遍歷到他們那一刻的值。

var arr = [1,2,3,4,5,'xzavier',7];
arr.forEach(function (value, index, arr) {
    arr[5] = 'xx';
    console.log(value);
});

3.已刪除的項不會被遍歷到(使用delete方法等狀況,或者直接置爲undefined)。

var arr = [1,2,3,4,5,'xzavier',7];
arr.forEach(function (value, index, arr) {
    if (value == 3) {
        delete arr[5];;
    }
    console.log(value);
});
// 1 2 3 4 5 7

可是使用別的方法刪除就就會出錯哦:

var arr = [1,2,3,4,5,'xzavier',7];
arr.forEach(function (value, index, arr) {
    if (value == 3) {
        arr.shift(0);
    }
    console.log(value);
});
// 1 2 3 5 xzavier 7

第四個值不在了,由於forEach記住了最開始的length,可是shift卻改變了length。其餘的方法同理。
而delete並無把數組項刪掉,只是把值置爲了undefined,因此length未變。

4.不能使用break或continue中斷或跳出循環,報錯。不過能夠return false 跳出當前循環,達到必定效果:

var arr = [1,2,3,4,5,'xzavier',7];
arr.forEach(function (value, index, arr) {
    if (value == 3) {
        return false;
    }
    console.log(value);
});
// 1 2 4 5 xzavier 7

for...of語句

for...in循環,只能得到對象的鍵名,不能直接獲取鍵值,而且遍歷數組不友好。
forEach中又不能使用break語句中斷循環,也不能使用return語句返回到外層函數。

ES6提供for...of循環,容許遍歷得到鍵值。若是要經過for...of循環,獲取數組的索引,能夠藉助數組實例的entries方法和keys方法。它還能夠正確響應break、continue和return語句。for-of 還能夠遍歷 Map 和 Set (ES6 中新增數據集合類型),以及其餘可迭代對象。

var arr = ['a', 'b', 'c', 'd'];   
for (let i in arr) {
      console.log(i); // 0 1 2 3
}
for (let i of arr) {
      console.log(i); // a b c d
}
for (var i of arr) {
    if(i == 'c'){
        break;
    }
      console.log(i); // a b
}

let str = 'xzavier';
for (let i of str) {
      console.log(i); // x z a v i e r
}

break和continue語句

break 和continue 語句用於在循環中精確地控制代碼的執行。其中,break 語句會當即退出循環,強制繼續執行循環體後面的語句。而continue 語句退出當前循環,繼續後面的循環。

for (var i = 1; i <= 10; i++) {
    if (i == 5) break; //若是i等於5,就退出循環
    console.log(i); //1 2 3 4
}
for (var i = 1; i <= 10; i++) {
    if (i == 5) continue; //若是i等於5,就退出當前循環
    console.log(i); // 1 2 3 4 6 7 8 9 10
}

標記跳轉

咱們程序員應該都知道有個字段叫:goto,可讓你的程序跳到指定的地方執行。因爲goto遭到非議太多,使用這類編碼形式會使你的代碼難以理解和維護,基本不建議使用,若是JavaScript也有goto語句,那麼在上面continue的基礎上咱們還可讓程序的執行跳轉到指定的代碼中的那個位置。不過,幸虧,JavaScript沒有這個標識,因此,咱們的世界很歡暢。不過,你要是想這樣作,咱們仍是有相似的實現的:

xzavier: for (var i = 1; i <= 10; i++) {
    if (i == 5) {
        continue xzavier; // 一層循環,與continue無異
    }
    console.log(i);
}
// 1 2 3 4 6 7 8 9 10

在多層循環的時候做用就區別出來了:

xzavier: for (var i = 1; i <= 10; i++) {
    for( var j = 1; j <= 10; j++) {
        if (i == j) {
            continue xzavier;
        }
        console.log(i + '>' + j);
    }
}
VM123:6 2>1
VM123:6 3>1
VM123:6 3>2
VM123:6 4>1
VM123:6 4>2
VM123:6 4>3
VM123:6 5>1
VM123:6 5>2
VM123:6 5>3
VM123:6 5>4
VM123:6 6>1
VM123:6 6>2
VM123:6 6>3
VM123:6 6>4
VM123:6 6>5
VM123:6 7>1
VM123:6 7>2
VM123:6 7>3
VM123:6 7>4
VM123:6 7>5
VM123:6 7>6
VM123:6 8>1
VM123:6 8>2
VM123:6 8>3
VM123:6 8>4
VM123:6 8>5
VM123:6 8>6
VM123:6 8>7
VM123:6 9>1
VM123:6 9>2
VM123:6 9>3
VM123:6 9>4
VM123:6 9>5
VM123:6 9>6
VM123:6 9>7
VM123:6 9>8
VM123:6 10>1
VM123:6 10>2
VM123:6 10>3
VM123:6 10>4
VM123:6 10>5
VM123:6 10>6
VM123:6 10>7
VM123:6 10>8
VM123:6 10>9

continue xzavier表示跳到標記爲xzavier的循環,並繼續下一次迭代。若是不使用這個標識符的話只會在內層循環中跳出當次循環並繼續下一次循環。

固然,咱們也不建議使用這樣的寫法,並無那麼好用。舉例很隨意,並不是要實現一個這樣的業務。若是要實現相似的功能,用continue換個寫法就OK了。這裏主要說明下咱們仍是有這樣的寫法存在的,用不用看你心情,看你的場景,以及你的團隊同不一樣意。

with語句

with語句的做用是將代碼的做用域設置到一個特定的對象中。當代碼運行到with語句時,執行上下文的做用域鏈臨時被改變了。一個新的可變對象被建立,它包含了參數指定的對象的全部屬性。這個對象將被推入做用域鏈的頭部,這意味着函數的全部局部變量如今處於第二個做用域鏈對象中,所以訪問代價更高了。若是,再挖深一點,JavaScript 引擎在編譯階段遇到with字段都不能好好的幹活兒了,由於它不知道這個with最後會怎麼改變做用域,本想把變量做用域都安置好都不行了。因此,通常不建議使用。

相關文章
相關標籤/搜索