JS中的循環和遍歷提供給我們一種簡單快速的方法去做一些重複的事情.學會在不同的場景中使用不同的方法能夠幫助我們有效的編寫各類代碼.在這裏整理了一些常見的方法,今天就讓我們大家一些來學習一波.
for
for循環有3個表達式
- 聲明循環變量
- 判斷循環條件
- 更新循環變量
其中,三個表達式都是可以省略的,但是兩個;
卻是不能省略的.
let arr = ['a','b','c']
for(let i = 0; i < arr.length; i++){
console.log(arr[i]) // a b c
}
我們有時候會使用一個臨時變量將數組的長度緩存下來,以免每次循環的時候都去獲取一遍
let arr = ['a','b','c']
for(let i = 0, len = arr.length; i < len; i++){
console.log(arr[i]) // a b c
}
這樣寫在某些瀏覽器中確實會有一定的性能提升.但是在基於V8引擎的Chrome瀏覽器中,上面兩種寫法性能基本一致,因爲瀏覽器會在底層自動幫我們優化.大致就是會將那些在循環體中不變的語句拿出來放到循環體的外部.僞代碼如下:
for(let i = 0; i < arr.length; i++){
total = a + b
arr[i] = i + total * 2
}
將上面的代碼編譯優化成下面的代碼:
total = a + b
total2 = total * 2
n = arr.length
for(let i = 0; i < n; i++){
arr[i] = i + total2
}
因此,大家在平時寫代碼的時候,是否存儲變量就視情況而定吧.
for循環可以循環字符串,數組,類數組,DOM節點等
// 遍歷類數組
(function(){
for(let i=0;i<arguments.length;i++){
console.log(arguments[i]) // a b
}
})('a','b')
JS中的循環除了for
循環外,其他的還有while
循環,do-while
循環,我們就不說了.
forEach
不會改變原數組,不會返回新數組
let arr = ['a','b','c']
let arr2 = arr.forEach((item, index, arr) => {
return item + '2'
})
console.log(arr) // ["a", "b", "c"]
console.log(arr2) // undefined
forEach還可以傳入第二個參數,用來綁定回調函數內部的this
變量
let arr = ['a','b','c']
let arr2 = ['d','e','f']
arr.forEach(function(item, index, arr){
console.log(this[index]) // d e f
}, arr2)
map
不會改變原數組,會返回新數組
let arr = [2,'a','b','c',undefined,null]
let arr2 = arr.map((item, index, arr) => {
item = item + '1'
return item + '2'
})
console.log(arr) // [2, "a", "b", "c", undefined, null]
console.log(arr2) // ["212", "a12", "b12", "c12", "undefined12", "null12"]
可以鏈式調用
let arr = [1,2,3]
let tmp = arr.map(item => item + 1).map(item => item + 1)
console.log(tmp) // [3, 4, 5]
map還可以傳入第二個參數,用來綁定回調函數內部的this
變量
let arr = [1,2]
let arr2 = [3,4]
let tmp = arr.map(function(item, index){
return this[index]
}, arr2)
console.log(tmp) // [3, 4]
filter
不會改變原數組,會返回新數組
let arr = [1,2,3,4,5,6]
let tmp = arr.filter(item => {
return item % 2 === 0
})
console.log(tmp) // [2, 4, 6]
filter還可以傳入第二個參數,用來綁定回調函數內部的this
變量
let arr = ['a','b','c']
let arr2 = ['aa','bb','cc']
let tmp = arr.filter(function(item, index){
return this[index] === 'bb'
}, arr2)
console.log(tmp) // ["b"]
還有一個通常用來移除數組中假值(undefined
, null
, false
, 0
, ''
, NaN
等)的操作
let arr = [1,undefined,2,null,3,false,0,'',4,NaN]
let tmp = arr.filter(Boolean)
console.log(tmp) // [1, 2, 3, 4]
for…in
可以用來遍歷字符串,數組,對象等.
let arr = ['a','b']
for(let key in arr){
console.log(arr[key]) // a b
}
遍歷對象的可枚舉屬性,不遍歷對象的不可枚舉屬性
let obj = Object.create(null,{
name:{
value:'zhangsan',
enumerable: true
},
// age 是不可枚舉屬性,不會被for...in循環遍歷
age:{
value:12,
enumerable: false
}
})
console.log(obj) // {name: "zhangsan", age: 12}
for(let p in obj){
console.log(p) // name
console.log(obj[p]) // zhangsan
}
遍歷原型對象上的屬性,若是不想要原型對象上的屬性怎麼辦? 使用hasOwnProperty()
方法來判斷過濾
class Person {
constructor(name){
this.name = name
}
}
// gender是原型對象上的屬性
Person.prototype.gender = 'male'
let obj = new Person('zhangsan')
for(let p in obj){
if(obj.hasOwnProperty(p)){
console.log(p) // name
}
console.log(p) // name gender
}
for...in
也會遍歷數組自身的屬性.由於歷史遺留問題,它遍歷的實際上是對象的屬性名.數組也是對象,其中每個元素的索引也被當作一個屬性.所以當我們給數組手動添加額外屬性的時候,它會被遍歷出來.
let arr = [1,2]
arr.name = 'array1'
for(let p in arr){
console.log(arr[p]) // 1 2 array1
}
for...in
循環會輸出name屬性.但是length
屬性卻不包括在內.for...of
循環則修復了這些問題,下面我們來看這個在ES6出來的傢伙.
for…of
遍歷具有Iterator
迭代器的對象,比如字符串,數組,類數組(arguments對象,DOM NodeList對象),Map,Set,Generator等,並且可以響應break
,continue
和return
語句.
遍歷一個Map類型的
let arr = [['name','zhangsan'],['age',12]]
let map = new Map(arr)
for(let [key, value] of map){
console.log(key) // name age
console.log(value) // zhangsan 12
}
使用continue
語句來控制流程,就像是在for
循環中使用一樣
let arr = [1,2,3,4,5,6]
for(let item of arr){
if(item % 2 === 0){
continue
}
console.log(item) // 1 3 5
}
既然講到了for...of
,那就有必要提一嘴Iterator
這個東西了.Iterator
是一個接口,爲不同的數據結構提供了統一訪問機制,主要用來給for...of
消費.我們先來看下面的代碼
let obj = {}
for(let item of obj){
console.log(item) // Uncaught TypeError: obj is not iterable
}
代碼爆紅了,提示說obj不是可以迭代的.這是因爲obj沒有內置Iterator
,無法供for...of
使用.但假如我們想要使用for...of
來遍歷obj對象要怎麼辦?
這裏提供了一個方法,就是對象的Symbol.iterator
屬性指向對象的默認迭代器方法.那麼只要我們給對象手動加上一個Symbol.iterator
屬性就可以了
let obj = {}
obj[Symbol.iterator] = function*(){
yield 'a'
yield 'b'
}
for(let item of obj){
console.log(item) // a b
}
可以看到,此時的obj對象已經可以被遍歷了,變得更像一個數組了.假如我們再在後面加上一句代碼,觀察結果.此時大家感覺是不是越來越像了.
console.log([...obj]) // ["a", "b"]
every
對數組中的每一項進行函數運算,只有每一項都返回true
,最後結果纔會返回true
.通俗的講就是大家都滿足要求才行.
let arr = [1,2,3]
let res = arr.every(item => item > 0)
console.log(res) // true
some
對數組中的每一項進行函數運算,只要有任一項返回true
,最後結果就會返回true
.通俗的講就是隻要有一個人滿足要求就行.
let arr = [1,2,3]
let res = arr.some(item => item > 2)
console.log(res) // true
every
和some
這倆方法都接受一個函數作爲參數,這個參數函數又接受三個參數,分別是數組當前成員,當前索引和整個數組.這個接受參數的形式和map
,forEach
,map
等方法基本一致.
find
返回符合函數測試條件的第一個元素,並停止查找後面的,若沒有符合的,則返回undefined
let arr = [1,2,3]
let res = arr.find(item => item > 1)
console.log(res) // 2
Object.keys
這一系列有好幾個兄弟,除了Object.keys()
以外還有Object.values()
和Object.entries()
等
使用上面幾個方法可以分別獲取對象的屬性名,屬性值,以及鍵值對.注意,Object.keys
返回的是該對象自身的屬性名,且只返回可枚舉屬性
let obj = {name:'zhangsan', age:12, job:'FE engineer'}
console.log(Object.keys(obj)) // ["name", "age", "job"]
console.log(Object.values(obj)) // ["zhangsan", 12, "FE engineer"]
console.log(Object.entries(obj)) // [["name", "zhangsan"],["age", 12],["job", "FE engineer"]]
我們來看下下面的代碼來驗證一下
// 原型對象上的屬性,在for...in中被遍歷到了,在Object.keys中找不到
Object.prototype.gender = 'male'
let obj = Object.create({}, {
name:{
value:'zhangsan',
enumerable:true
},
// age是不可枚舉屬性,在for...in和Object.keys中都沒有
age:{
value:12,
enumerable:false
}
})
console.log(Object.keys(obj)) // ["name"]
for(let p in obj){
console.log(p) // name gender
}
此處又可以衍生出一個判斷對象是否爲空對象的方法
if(Object.keys(obj).length){
// 不是空對象
}else{
// 是空對象
}
總結
forEach
本質上也是數組的循環,和for
循環語句差不多,但是語法簡單了.並且forEach
不會改變原數組,不會返回新數組.map
和filter
都不會改變原數組,都會返回新數組.也正是因爲能返回一個新數組,所以可以鏈式調用.不同之處在於map
是對原數組進行加工,進行一對一的關係映射,而filter
則是對原數組過濾,保留符合要求的數組成員.for...in
則會遍歷對象的可枚舉屬性,包括原型對象上的屬性.主要是爲遍歷對象而設計,不適用於遍歷數組.for...of
遍歷具有Iterator
迭代器的對象,避開了for...in
循環的所有缺陷,並且可以正確響應break
,continue
和return
語句等.every
和some
有點類似於斷言的感覺,返回布爾類型Object.keys()
返回一個給定對象的所有可枚舉屬性的字符串數組