@JS之循環javascript
create by db on 2019-5-13 09:45:24
Recently revised in 2019-5-14 14:47:50html
Hello 小夥伴們,若是以爲本文還不錯,麻煩點個贊或者給個 star,大家的贊和 star 是我前進的動力!GitHub 地址前端
查閱網上諸多資料,並結合本身的學習經驗,寫下這篇學習筆記,以記錄本身的學習心得。現分享給你們,以供參考。java
I hear and I fogorget.node
I see and I remember.git
I do and I understand.github
ES6到來了,你還在用最簡單的for循環嗎?數組
參考文獻:瀏覽器
在講循環的以前,咱們先了解一下循環結構的執行步驟:數據結構
let num = 1;
while (num<=10){//二、判斷循環條件;
console.log(num);//三、執行循環體操做;
num++;//四、更新循環變量;
}
複製代碼
注:
while循環()中的表達式,運算結果能夠是各類類型,可是最終都會轉爲真假,轉換規則以下。
let num = 10;
do{
console.log(num);//10 9 8 7 6 5 4 3 2 1 0
num--;
}while(num>=0);
console.log(num);//-1
複製代碼
注:
while循環特色:先判斷後執行;
do-while循環特色:先執行再判斷,即便初始條件不成立,do-while循環至少執行一次;
for循環
;
分割,for循環三個表達式均可以省略,可是兩個;
缺一不可。&& ||
鏈接,第一三部分用,
分割;下面先來看看你們最多見的一種寫法:
const arr = [1, 2, 3];
for(let i = 0; i < arr.length; i++) { console.log(arr[i]); } 複製代碼
當數組長度在循環過程當中不會改變時,咱們應將數組長度用變量存儲起來,這樣會得到更好的效率,下面是改進的寫法:
const arr = [1, 2, 3];
for(let i = 0, len = arr.length; i < len; i++) {
console.log(arr[i]);
}
複製代碼
for循環的3個條件都是能夠省略的,若是沒有退出循環的判斷條件,就必須使用break語句退出循環,不然就是死循環:
let x = 0;
for (;;) { // 將無限循環下去
if (x > 100) {
break; // 經過if判斷來退出循環
}
x ++;
}
複製代碼
for-in 循環主要用於遍歷對象
for(keys in zhangsan){}
keys
表示obj對象的每個鍵值對的鍵!!全部循環中,須要使用obj[keys]
來取到每個值!!!因此,可使用hasOwnProperty
判斷一個屬性是否是對象自身上的屬性。
obj.hasOwnProperty(keys)==true
表示這個屬性是對象的成員屬性,而不是原先屬性for-in 循環遍歷的是對象的屬性,而不是數組的索引。所以, for-in 遍歷的對象便不侷限於數組,還能夠遍歷對象。例子以下:
const person = {
fname: "san",
lname: "zhang",
age: 99
};
let info;
for(info in person) {
console.log("person[" + info + "] = " + person[info]);
}
複製代碼
結果以下:
person[fname] = san
person[lname] = zhang
person[age] = 99
複製代碼
須要注意的是, for-in 遍歷屬性的順序並不肯定,即輸出的結果順序與屬性在對象中的順序無關,也與屬性的字母順序無關,與其餘任何順序也無關。
Array 在 Javascript 中是一個對象, Array 的索引是屬性名。
事實上, Javascript 中的 「array」 有些誤導性, Javascript 中的 Array 並不像大部分其餘語言的數組。
實際上, Array 的索引也不是 Number 類型,而是 String
類型的。咱們能夠正確使用如 arr[0] 的寫法的緣由是語言能夠自動將 Number 類型的 0 轉換成 String 類型的 「0″ 。
因此,在 Javascript 中歷來就沒有 Array 的索引,而只有相似 「0″ 、 「1″ 等等的屬性。有趣的是,每一個 Array 對象都有一個 length 的屬性,致使其表現地更像其餘語言的數組。但爲何在遍歷 Array 對象的時候沒有輸出 length 這一條屬性呢?那是由於 for-in 只能遍歷「可枚舉的屬性」, length 屬於不可枚舉屬性,實際上, Array 對象還有許多其餘不可枚舉的屬性。
如今,咱們再回過頭來看看用 for-in 來循環數組的例子,咱們修改一下前面遍歷數組的例子:
const arr = [1, 2, 3];
arr.name = "Hello world";
let index;
for(index in arr) {
console.log("arr[" + index + "] = " + arr[index]);
}
複製代碼
運行結果是:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[name] = Hello world
複製代碼
咱們看到 for-in 循環訪問了咱們新增的 「name」 屬性,由於 for-in 遍歷了對象的全部屬性,而不只僅是「索引」。
同時須要注意的是,此處輸出的索引值,即 「0″、 「1″、 「2″不是Number
類型的,而是String
類型的,由於其就是做爲屬性輸出,而不是索引。
那是否是說不在咱們的 Array 對象中添加新的屬性,咱們就能夠只輸出數組中的內容了呢?答案是否認的。由於 for-in 不只僅遍歷 array 自身的屬性,其還遍歷 array 原型鏈上的全部可枚舉的屬性。下面咱們看個例子:
Array.prototype.fatherName = "Father";
const arr = [1, 2, 3];
arr.name = "Hello world";
let index;
for(index in arr) {
console.log("arr[" + index + "] = " + arr[index]);
}
複製代碼
運行結果是:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[name] = Hello world
arr[fatherName] = Father
複製代碼
寫到這裏,咱們能夠發現 for-in 並不適合用來遍歷 Array 中的元素,其更適合遍歷對象中的屬性,這也是其被創造出來的初衷。卻有一種狀況例外,就是稀疏數組。考慮下面的例子:
let key;
const arr = [];
arr[0] = "a";
arr[100] = "b";
arr[10000] = "c";
for(key in arr) {
if(arr.hasOwnProperty(key) &&
/^0$|^[1-9]\d*$/.test(key) &&
key <= 4294967294
) {
console.log(arr[key]);
}
}
複製代碼
for-in 只會遍歷存在的實體,上面的例子中, for-in 遍歷了3次(遍歷屬性分別爲」0″、 「100″、 「10000″的元素,普通 for 循環則會遍歷 10001 次)。因此,只要處理得當, for-in 在遍歷 Array 中元素也能發揮巨大做用。
爲了不重複勞動,咱們能夠包裝一下上面的代碼:
function arrayHasOwnIndex(array, prop) {
return array.hasOwnProperty(prop) &&
/^0$|^[1-9]\d*$/.test(prop) &&
prop <= 4294967294; // 2^32 - 2
}
複製代碼
使用示例以下:
for (let key in arr) {
if (arrayHasOwnIndex(arr, key)) {
console.log(arr[key]);
}
}
複製代碼
正如上面所說,每次迭代操做會同時搜索實例或者原型屬性, for-in 循環的每次迭代都會產生更多開銷,所以要比其餘循環類型慢,通常速度爲其餘類型循環的 1/7。所以,除非明確須要迭代一個屬性數量未知的對象,不然應避免使用 for-in 循環。若是須要遍歷一個數量有限的已知屬性列表,使用其餘循環會更快,好比下面的例子:
const obj = {
"prop1": "value1",
"prop2": "value2"
};
const props = ["prop1", "prop2"];
for(let i = 0; i < props.length; i++) {
console.log(obj[props[i]]);
}
複製代碼
上面代碼中,將對象的屬性都存入一個數組中,相對於 for-in 查找每個屬性,該代碼只關注給定的屬性,節省了循環的開銷和時間。
ES6 借鑑 C++、Java、C# 和 Python 語言,引入了for...of循環,做爲遍歷全部數據結構的統一的方法。
一個數據結構只要部署了Symbol.iterator屬性,就被視爲具備iterator接口,就能夠用for...of循環遍歷它的成員。也就是說,for...of循環內部調用的是數據結構的Symbol.iterator方法。
先來看個例子:
const arr = ['a', 'b', 'c'];
for(let data of arr) {
console.log(data);
}
複製代碼
運行結果是:
a
b
c
複製代碼
爲何要引進 for-of?
要回答這個問題,咱們先來看看ES6以前的 3 種 for 循環有什麼缺陷:
因此,鑑於以上種種缺陷,咱們須要改進原先的 for 循環。但 ES6 不會破壞你已經寫好的 JS 代碼。目前,成千上萬的 Web 網站依賴 for-in 循環,其中一些網站甚至將其用於數組遍歷。若是想經過修正 for-in 循環增長數組遍歷支持會讓這一切變得更加混亂,所以,標準委員會在 ES6 中增長了一種新的循環語法來解決目前的問題,即 for-of 。
那 for-of 到底能夠幹什麼呢?
跟 forEach 相比,能夠正確響應 break, continue, return。
總結一下,for-of 循環有如下幾個特徵:
但須要注意的是,for-of循環不支持普通對象,但若是你想迭代一個對象的屬性,你能夠用 for-in 循環(這也是它的本職工做)。
map方法將數組的全部成員依次傳入參數函數,而後把每一次的執行結果組成一個新數組返回。
注意:是返回一個新數組,而不會改變原數組。
let numbers = [1, 2, 3];
numbers.map(function (n) {
return n + 1;
});
// [2, 3, 4]
numbers // [1, 2, 3]
複製代碼
map方法接受一個函數做爲參數。該函數調用時,map方法向它傳入三個參數:當前成員、當前位置和數組自己。
[1, 2, 3].map(function(elem, index, arr) {
return elem * index;
});
// [0, 2, 6]
複製代碼
此外,map()循環還能夠接受第二個參數,用來綁定回調函數內部的this變量,將回調函數內部的this對象,指向第二個參數,間接操做這個參數(通常是數組)。
let arr = ['a', 'b', 'c'];
[1, 2].map(function (e) {
return this[e];
}, arr)
// ['b', 'c']
複製代碼
上面代碼經過map方法的第二個參數,將回調函數內部的this對象,指向arr數組。間接操做了數組arr; forEach一樣具備這個功能。
在 ES5 中,引入了新的循環,即 forEach 循環。
forEach方法與map方法很類似,也是對數組的全部成員依次執行參數函數。可是,forEach方法不返回值,只用來操做數據。也就是說,若是數組遍歷的目的是爲了獲得返回值,那麼使用map方法,不然使用forEach方法。
const arr = [1, 2, 3];
arr.forEach((data) => {
console.log(data);
});
複製代碼
運行結果:
1
2
3
複製代碼
forEach 方法爲數組中含有有效值的每一項執行一次 callback
函數,那些已刪除(使用 delete 方法等狀況)或者從未賦值的項將被跳過(不包括那些值爲 undefined 或 null 的項)。
callback
函數會被依次傳入三個參數:
須要注意的是,forEach 遍歷的範圍在第一次調用 callback 前就會肯定。調用forEach 後添加到數組中的項不會被 callback 訪問到。若是已經存在的值被改變,則傳遞給 callback 的值是 forEach 遍歷到他們那一刻的值。已刪除的項不會被遍歷到。
const arr = [];
arr[0] = "a";
arr[3] = "b";
arr[10] = "c";
arr.name = "Hello world";
arr.forEach((daelta, index, array) => {
console.log(data, index, array);
});
複製代碼
運行結果:
a 0 ["a", 3: "b", 10: "c", name: "Hello world"]
b 3 ["a", 3: "b", 10: "c", name: "Hello world"]
c 10 ["a", 3: "b", 10: "c", name: "Hello world"]
複製代碼
這裏的index
是 Number
類型,而且也不會像 for-in 同樣遍歷原型鏈上的屬性。
因此,使用 forEach 時,咱們不須要專門地聲明 index 和遍歷的元素,由於這些都做爲回調函數的參數。
另外,forEach 將會遍歷數組中的全部元素,可是 ES5 定義了一些其餘有用的方法,下面是一部分:
filter
方法用於過濾數組成員,知足條件的成員組成一個新數組返回。
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3);
}) // [4, 5]
// 上面代碼將大於3的數組成員,做爲一個新數組返回。
let arr = [0, 1, 'a', false];
arr.filter(Boolean) // [1, "a"]
複製代碼
filter方法的參數函數也能夠接受三個參數:當前成員,當前位置和整個數組。
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
return index % 2 === 0;
}); // [1, 3, 5]
複製代碼
此外,filter方法也能夠接受第二個參數,用來綁定參數函數內部的this變量。
let obj = { MAX: 3 }; let myFilter = function (item) {
if (item > this.MAX) return true;
};
let arr = [2, 8, 3, 4, 1, 3, 2, 9];
arr.filter(myFilter, obj) // [8, 4, 9]
複製代碼
上面代碼中,過濾器myFilter內部有this變量,它能夠被filter方法的第二個參數obj綁定,返回大於3的成員。
ES6爲Array增長了find(),findIndex函數。
它們都是一個查找回調函數,用來循環查找,找到第一個符合條件的元素就會中止循環。
查找函數有三個參數。
value
:每一次迭代查找的數組元素。index
:每一次迭代查找的數組元素索引。arr
:被查找的數組。語法:
[1, 2, 3, 4].find((value, index, arr) => {
})
複製代碼
find()
函數用來查找目標元素,找到就返回該元素,找不到返回undefined
。
const arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
var ret1 = arr1.find((value, index, arr) => {
return value > 4
})
var ret2 = arr1.find((value, index, arr) => {
return value > 14
})
console.log('%s', ret1) // 5
console.log('%s', ret2) // undefined
複製代碼
findIndex()
函數也是查找目標元素,找到就返回元素的位置,找不到就返回-1
。
var ret3 = arr1.findIndex((value, index, arr) => {
return value > 4
})
var ret4 = arr1.findIndex((value, index, arr) => {
return value > 14
})
console.log('%s', ret3) // 4
console.log('%s', ret4) // -1
複製代碼
這兩個方法相似「斷言」(assert),返回一個布爾值,表示判斷數組成員是否符合某種條件。
some方法是隻要一個成員的返回值是true,則整個some方法的返回值就是true,不然返回false。
let arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
return elem >= 3;
});
// true
複製代碼
而every方法則相反,全部成員的返回值都是true,整個every方法才返回true,不然返回false。
兩相比較,some()只要有一個是true,便返回true;而every()只要有一個是false,便返回false.
let arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) {
return elem >= 3;
});
// false
複製代碼
這兩個方法在實際開發中,大有可用之處。好比在斷定用戶是否勾選了不可操做的數據,或者是否勾選了一條能夠操做的數據可使用這兩個方法遍歷循環數組。
reduce方法和reduceRight方法依次處理數組的每一個成員,最終累計爲一個值。
它們的差異是,reduce是從左到右處理(從第一個成員到最後一個成員),reduceRight則是從右到左(從最後一個成員到第一個成員),其餘徹底同樣。
[1, 2, 3, 4, 5].reduce(function (a, b) {
console.log(a, b);
return a + b;
})
// 1 2
// 3 3
// 6 4
// 10 5
//最後結果:15
複製代碼
reduce方法和reduceRight方法的第一個參數都是一個函數。該函數接受如下四個參數。
若是要對累積變量指定初值,能夠把它放在reduce方法和reduceRight方法的第二個參數。
[1, 2, 3, 4, 5].reduce(function (a, b) {
return a + b;
}, 10);
// 25
複製代碼
上面的第二個參數至關於設定了默認值,處理空數組時尤爲有用,可避免一些空指針異常。
因爲這兩個方法會遍歷數組,因此實際上還能夠用來作一些遍歷相關的操做。好比,找出字符長度最長的數組成員。
function findLongest(entries) {
return entries.reduce(function (longest, entry) {
return entry.length > longest.length ? entry : longest;
}, '');
}
findLongest(['aaa', 'bb', 'c']) // "aaa"
複製代碼
上面代碼中,reduce的參數函數會將字符長度較長的那個數組成員,做爲累積值。這致使遍歷全部成員以後,累積值就是字符長度最長的那個成員。
Object.keys()
方法的參數是一個對象,返回一個數組。該數組的成員都是該對象自身的(而不是繼承的)全部屬性名,且只返回可枚舉的屬性。
let obj = {
p1: 123,
p2: 456
};
Object.keys(obj) // ["p1", "p2"]
複製代碼
Object.getOwnPropertyNames()
方法與Object.keys
相似,也是接受一個對象做爲參數,返回一個數組,包含了該對象自身的全部屬性名。但它能返回不可枚舉的屬性。
let a = ['Hello', 'World'];
Object.keys(a) // ["0", "1"]
Object.getOwnPropertyNames(a) // ["0", "1", "length"]
複製代碼
上面代碼中,數組的length
屬性是不可枚舉的屬性,因此只出如今Object.getOwnPropertyNames()方法的返回結果中。
因爲 JavaScript 沒有提供計算對象屬性個數的方法,因此能夠用這兩個方法代替。
let obj = {
p1: 123,
p2: 456
};
Object.keys(obj).length // 2
複製代碼
直接跳出當前的循環,從當前循環外面開始執行,忽略循環體中任何其餘語句和循環條件測試。
它只能跳出一層循環,若是你的循環是嵌套循環,那麼你須要按照你嵌套的層次,逐步使用break來跳出。
function myBreak() {
for(let i = 0; i < 5; i++) {
if(i == 3) {
break;
}
console.log(i);
}
}
myBreak();
複製代碼
輸出:
0
1
2
複製代碼
注:
只能在循環體內和switch語句體內使用break語句。
當break出如今循環體中的switch語句體內時,其做用只是跳出該switch語句體。
當break出如今循環體中,但並不在switch語句體內時,則在執行break後,跳出本層循環體。
在循環結構中,應用break語句使流程跳出本層循環體,從而提早結束本層循環
終止當前的一次循環過程,其不跳出循環,而是繼續往下判斷循環條件執行語句。
只能結束循環中的一次過程,但不能終止循環繼續進行。
function myContinue() {
for(let i = 0; i < 5; i++) {
if(i == 3) {
continue;
}
console.log(i);
}
}
myContinue();
複製代碼
輸出:
0
1
2
4
複製代碼
注:
continue語句的通常形式爲:continue;
其做用是結束本次循環,即跳過本次循環體中餘下還沒有執行的語句,接着再一次進行循環的條件斷定。
注意:執行continue語句並無使整個循環終止。在while和do-while循環中,continue語句使得流程直接跳到循環控制條件的測試部分 ,而後決定循環是否繼續進行。
對與for循環,continue以後執行的語句,是循環變量更新語句i++;
5.對於while、do-while循環,continue以後執行的語句,是循環條件判斷;所以,使用這兩個循環時,必須將continue放到i++以後使用,不然,continue將跳過i++進入死循環。
從當前的方法中退出,返回到該調用的方法的語句處,繼續執行。
function myReturn() {
for(let i = 0; i < 5; i++) {
if(i == 3) {
return i;
}
console.log(i);
}
}
let s = myReturn();
console.log("s: " + s);
複製代碼
輸出:
0
1
2
s: 3
複製代碼
注:
return 從當前的方法中退出,返回到該調用的方法的語句處,繼續執行
return 返回一個值給調用該方法的語句,返回值的數據類型必須與方法的聲明中的返回值的類型一致,可使用強制類型轉換來是數據類型一致
return 當方法說明中用void聲明返回類型爲空時,應使用這種格式,不返回任何值。
循環方法 | 語法 | 傳入參數 | 返回值 | 應用場景 | 使用頻率 | 特色 | 對空位的處理 |
---|---|---|---|---|---|---|---|
while | while (循環條件 ){循環體 } |
key | 無 | 一般用在循環次數不肯定的時候 | 不經常使用 | 先判斷,再執行 | 不會忽略空位,標記undefined |
do-while | do{循環體 }while(循環條件 ); |
key | 無 | 循環至少要執行一次 | 不經常使用 | 先執行,再判斷,至少執行一次 | 不會忽略空位,標記undefined |
for | for(聲明循環變量 ;判斷循環條件 ;更新循環變量 ;){循環體 } |
key | 無 | 肯定循環次數以及對象遍歷的時候使用 | 經常使用 | 已知循環的初始和結束條件時很是有用 | 不會忽略空位,標記undefined |
for-in | for( let key in obj ){循環體 } |
key | 無 | 遍歷對象及稀疏數組 | 經常使用 | 循環會遍歷一個object全部的可枚舉屬性。最好不要用,可能會遍歷原型鏈上的屬性 | 會忽略空位 |
for-of | for(let value of arr ) {循環體 } |
value | 無 | 遍歷數組 | 經常使用 | 最簡潔、最直接的遍歷數組元素的語法 | 不會忽略空位,標記undefined |
forEach | arr .forEach((data , index , array ) => { 循環體 }) |
函數,其有三個參數:當前成員、當前位置和數組自己。 | 無 | 爲一些相同的元素,綁定事件處理器:如刪除所選,累加等 | 經常使用 | 調用數組的每一個元素,將元素傳給回調函數;沒有返回一個新數組&沒有返回值;不能正確響應 break, continue, return。 | 會忽略空位 |
map | arr .map(function(elem , index , array ) { return elem * index; }) |
函數,同上 | 一個新數組 | 在遍歷出的數據須要處理的時候,如處理數據每一項,或者取到對象中某些屬性並返回:如將A數組中的值雙倍返回B數組;將A數組中對象的某個屬性返回到B數組 | 經常使用 | 獲取到對象中某些屬性來返回;返回一個新數組,而不會改變原數組 | 會忽略空位 |
filter | arr .filter(function (elem , index , arr ) { return index % 2 === 0; }) |
函數,同上 | 一個新數組 | 用於過濾數組成員,知足條件的成員組成一個新數組返回:如獲取A數組中指定類型的對象返回到B數組中;過濾掉不符合條件的對象;根據數組A的id值,過濾掉B數組中不符合條件的對象 | 經常使用 | 過濾循環,返回一個新數組,而不會改變原數組;條件課使用&& ;匹配成功即爲true 並返回值,不成功即爲false 且返回值 |
會忽略空位 |
find | arr .find(function (elem , index , arr ) { return elem === 0; }) |
函數,同上 | 符合條件的元素 | 用於查找數組中符合條件的元素;根據指定條件數組中符合條件的對象;在list中找到對應id,傳入接口,獲取相應的數據 | 經常使用 | 循環查找,返回符合條件的元素,找到第一個符合條件的元素即中止執行並返回該元素,沒找到就返回undefind |
|
findIndex | arr .findIndex(function (elem , index , arr ) { return elem === 0; }) |
函數,同上 | 符合條件的元素的位置 | 用於查找數組中符合條件的元素的位置;查看元素是否存在 | 不經常使用 | 找到就返回元素的位置,找不到就返回-1 |
|
some(知足) & every(不知足) | arr .some(function (elem , index , arr ) { return elem >= 3; }) |
函數,同上 | 布爾值,即true 或者false |
用於統計數組中的元素是否知足某個條件:如判斷系統是否可用;註冊頁面中,判斷input的長度是否大於0 | 不經常使用 | 符合就單活true,不符合就返回false;知足條件即中止 | |
reduce(從左往右) & reduceRight(從右往左) | arr .reduce(function (a , b ) { return a + b; },10) |
函數,其有四個參數:累積變量 ,默認爲數組的第一個成員;當前變量 ,默認爲數組的第二個成員;當前位置 (從0開始);原數組 |
依次處理數組的每一個成員,最終累計爲一個值 | 用於計算全部值的和;將數組找那個的某些屬性抽離另外一個數組;判斷字符串中的括號是否對稱 | 不經常使用 | 累加 |
循環是讓計算機作重複任務的有效的方法,有些時候,若是代碼寫得有問題,會讓程序陷入「死循環」,也就是永遠循環下去。JavaScript的死循環會讓瀏覽器沒法正常顯示或執行當前頁面的邏輯,有的瀏覽器會直接掛掉,有的瀏覽器會在一段時間後提示你強行終止JavaScript的執行,所以,要特別注意死循環的問題。
做爲一隻前端菜鳥,本篇文章旨在記錄本身的學習心得,若有不足,還請多多指教,謝謝你們。
路漫漫其修遠兮,與諸君共勉。
後記:Hello 小夥伴們,若是以爲本文還不錯,記得點個贊或者給個 star,大家的贊和 star 是我編寫更多更豐富文章的動力!GitHub 地址
db 的文檔庫 由 http://www.javashuo.com/tag/db 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議進行許可。
基於github.com/danygitgit上的做品創做。
本許可協議受權以外的使用權限能夠從 creativecommons.org/licenses/by… 處得到。