媒介
将根据自身痛点进修,设计对原生JavaScript写一个体系,本文为第一篇,感兴趣的同砚能够关注个人民众号:ZeroToOneMe,或许github博客,将延续输出。
JavaScript
中能够完成遍历的数据范例主假如对象,个中包括一般对象与数组。完成遍历的体式格局有许多,本文梳理下JavaScript
中能完成遍历的体式格局。
对象&&数组
for…in
语句以恣意递次遍历一个对象自有的、继续的、可罗列的、非Symbol
的属性。关于每一个差别的属性,语句都邑被实行。
语法:
for (variable in object) {
xxx
}
参数:
variable:在每次迭代时,将差别的属性名分配给变量。<br/>
object:被迭代罗列其属性的对象。
从定义上来看,for...in
语句遍历对象和组数都是能够的,然则此体式格局运用在数组能够会带来一些我们并不想看到的东西,看下面的实例一:
// 实例一
Array.prototype.name = 'fe'
let arr = [10, 20, 30, 40, 50]
arr.str = 'hello'
for(let index in arr) {
console.log(index) // 0, 1, 2, 3, 4, str, name
}
上面的实例一输出效果能够看出,直接获得不是数组元素值,而是数组的索引值(键名),同时另有其自定义属性以及其原型链上的属性和要领‘str’,‘name’两项,数组自身也是对象。
纵然运用getOwnPropertyNames()
或实行hasOwnProperty()
来肯定某属性是不是是对象自身的属性能够防止原型链上的属性和要领不输出,但照样不能防止自定义的属性输出。实例二:
// 实例二
Array.prototype.name = 'fe'
let arr = [10, 20, 30, 40, 50]
arr.str = 'hello'
for(let index in arr) {
if (arr.hasOwnProperty(index)) {
console.log(index) // 0, 1, 2, 3, 4, str
}
}
数组运用for...in
遍历,能够会存在以下几个题目:
- 获得的index索引值为字符串,不能直接做多少运算,须要先做数据范例转换处置惩罚;
- 能够以恣意递次做遍历的,即遍历递次能够不是根据数组内部递次;
- 会遍历数组一切可罗列的属性,包括原型。
在肯定水平上来看,运用for...in
遍历数组是一个很蹩脚的挑选,不引荐运用for...in
语句遍历数组,对开辟履历短缺的新人不友好,比较轻易踩到坑,会碰到许多意想不到的题目。遍历数组更多的引荐是for...of
与foreach
这两种体式格局,下面也会细致的梳理这两种体式格局。
注重点:
for...in
比较合适遍历一般对象,遍历获得的效果是对象的键名,其递次也是无序的,无关递次。也能够经由历程break
或许return false
中缀遍历。- 在迭代历程中最好不要在对象上举行增添、修正或许删除属性的操纵,除非是对当前正在被接见的属性。
// 实例三
let obj = {
name: 'fe',
age: 18
}
for(let key in obj) {
console.log(key) // name age
}
for…of
语句在可迭代对象(包括 Array
,Map
,Set
,String
,TypedArray
,arguments
对象,NodeList
对象等)上建立一个迭代轮回,挪用自定义迭代钩子,并为每一个差别属性的值实行语句。该语句是ES6新增遍历体式格局,功用照样比较壮大的。<br/>
<span>语法:</span>
for (variable of iterable) {
xxx
}
参数:
variable:在每次迭代中,将差别属性的值分配给变量。<br/>
iterable:一个具有可罗列属性而且能够迭代的对象。
// 实例四
let arr = [10, 20, 30, 40, 50]
arr.str = 'hello'
for(let value of arr) {
console.log(value) // 10 20 30 40 50
}
for...of
语句经常使用在遍历数组,从实例四看出,for...of
能够直接获得数组索引中的值,然则不会将其实例属性的值返回。缘由在于for...of
轮回挪用遍历器接口,数组的遍历器接口只返回具有数字索引的属性,所以for...of
轮回不会返回数组arr
的str
属性。
话说我们日常平凡的开辟中经常使用for..of
遍历数组,从上面的定义来看,for...of
不仅仅能遍历数组,也能够遍历String
(字符串)、Map
、Set
等数据结构,是由于这几个数据结构默许内置了遍历器接口,Iterator接口
,即Symbol.iterator
要领。
也就是说有了遍历器接口,数据结构就能够用for...of
轮回遍历。遍历器(Iterator
)是一种接口,为种种差别的数据结构供应一致的接见机制。任何数据结构只需布置 Iterator 接口
,就能够完成遍历操纵。Iterator接口
与for...of
都是ES6提出的特性,Iterator接口
也就重要供for...of
消耗。
// 实例五
let arr = [10, 20, 30, 40, 50]
arr.str = 'hello'
let item = arr[Symbol.iterator]() // 遍历器对象
console.log(item.next()) // { value: 10, done: false }
console.log(item.next()) // { value: 20, done: false }
console.log(item.next()) // { value: 30, done: false }
console.log(item.next()) // { value: 40, done: false }
console.log(item.next()) // { value: 50, done: false }
console.log(item.next()) // { value: undefined, done: true }
实例五是Iterator
的遍历历程,经由历程手动挪用其对象的next()
要领完成信息猎取。默许的Iterator接口
布置在数据结构的Symbol.iterator
属性。关于原生布置Iterator接口
的数据结构,不必本身写遍历器天生函数,for...of
轮回会自动遍历。能够参考实例四。
// 实例六
let str = 'abc'
let item = str[Symbol.iterator]()
console.log(item.next()) // { value: 'a', done: false }
console.log(item.next()) // { value: 'b', done: false }
console.log(item.next()) // { value: 'c', done: false }
console.log(item.next()) // { value: undefined, done: true }
for (let item of str) {
console.log(item) // a b c
}
实例六为字符串的遍历演示,另有其他几种数据结构(Map
,Set
,TypedArray
,arguments
对象,NodeList
对象)原生布置了Iterator接口
,能够直接运用for...of
完成遍历,在此不在供应代码实例,能够自行测试。
for..of
不能遍历对象,由于对象默许没有布置Iterator接口
,没 有默许布置Iterator接口
缘由在于对象属性是无序的,哪一个属性先遍历,哪一个属性后遍历没法肯定。本质上,遍历器是一种线性处置惩罚,关于任何非线性的数据结构,布置遍历器接口,就即是布置一种线性转换。也能够手动为对象布置Iterator接口
,看下面的实例七。
// 实例七
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this
let index = 0
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true }
}
}
};
}
}
for (let item of obj) {
console.log(item) // hello world
}
总结下for..of
的几个特性:
- 不仅能遍历数组,同时也能遍历布置了遍历器接口
Iterator接口
的数据结构; - 相对比较简约直接遍历数组的体式格局;
- 避开了
for-in
轮回的一切缺点。
对象
Object.keys()
返回一个由一个给定对象的自身可罗列属性构成的数组,数组中属性名的分列递次和运用for...in
轮回遍历该对象时返回的递次一致。
<span>语法:</span>
Object.keys(obj)
参数:
obj:要返回其罗列自身属性的对象
返回一个一切元素为字符串的数组,其元素来自于从给定的object
上面可直接罗列的属性。这些属性的递次与手动遍历该对象属性时的一致。
// 实例八
let obj = {
name: 'fe',
age: 18
}
let result = Object.keys(obj)
console.log(result) // [ 'name', 'age' ]
注重点:
- 在ES5里,假如此要领的参数不是对象(而是一个原始值),那末它会抛出
TypeError
。在ES6中,非对象的参数将被强迫转换为一个对象。
consoloe.log(Object.keys('foo')) // TypeError: "foo" is not an object (ES5)
consoloe.log(Object.keys('foo')) // ["0", "1", "2"] (ES6)
对象也能够经由历程和Object.keys(obj)
搭配,运用for...of
来遍历一般对象的属性。
// 实例九
let obj = {
name: 'fe',
age: 18
}
for(let key of Object.keys(obj)) {
console.log(obj[key]) // fe 18
}
数组
for轮回
用于建立一个轮回,它包括了三个可选的表达式,三个可选的表达式包围在圆括号中并由分号分开, 后跟一个在轮回中实行的语句(通常是一个块语句)。
<span>语法:</span>
for ([initialization]; [condition]; [final-expression]) {
statement
}
参数:
- initialization:一个表达式 (包括赋值语句) 或许变量声明;<br/>
- condition:一个前提表达式被用于肯定每一次轮回是不是能被实行;<br/>
- final-expression:每次轮回的末了都要实行的表达式;<br/>
- statement:只需condition的效果为true就会被实行的语句。<br/>`
// 实例十
let arr = [1, 2, 3]
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]) // 1 2 3
}
for轮回
为编程言语中最原始的一种遍历体式格局,涵盖了险些一切的编程言语,在JavaScript中其只能遍历数组,不能遍历对象。实例十是for轮回
最通例的写法。我们不注重的话,也比较轻易带来本能够防止的几个“题目”,我们须要注重:
-
在for轮回
中运用var
或许未声明的变量,变量作用于全局,会来不必要的贫苦,运用let
声明变量天生块级作用域,或许构成闭包; - 在轮回最先以变量的情势缓存下数组长度,能够获得更好的效力,若在轮回内部有能够转变数组长度, 请务必慎重处置惩罚, 防止数组越界。
// 实例十一
let arr = [1, 2, 3]
for (let i = 0, len = arr.length; i < len; i++) {
console.log(arr[i]) // 1 2 3
}
forEach
按升序为数组中含有效值的每一项实行一次callback
函数,那些已删除或许未初始化的项将被跳过。
<span>语法:</span>
array.forEach(function(currentValue, index, arr), thisValue)
参数:
- currentValue(必选):数组当前项的值<br/>
- index(可选):数组当前项的索引<br/>
- arr(可选):数组对象自身<br/>
- thisValue(可选):当实行回调函数时用作
this
的值(参考对象),默许值为undefined
<br/>
注重点:
- 没有办法中断或许跳出
forEach
轮回,除了抛出一个非常。- 只能用
return
退出本次回调,举行下一次回调,并老是返回undefined
值,纵然你return
了一个值。forEach
被挪用时,不直接转变挪用它的对象,然则对象能够会被callback
转变。
罕见划定规矩(一样适用于filter
、map
要领):
- 关于空数组是不会实行回调函数的。
- 遍历的范围在第一次挪用
callback
前就会肯定。 - 为每一个数组元素实行
callback
函数。 - 函数在实行时,原数组中新增添的元素将不会被
callback
接见到。 - 假如已存在的值被转变,则传递给
callback
的值是遍历到他们那一刻的值。 - 被删除的元素将不会被接见到。
// 实例十二
let arr = [1, 2, 3, , 5] // 倒数第二个元素为空,不会遍历
let result = arr.forEach((item, index, array) => {
arr[0] = '修正元素值'
console.log(item) // 1 2 3 5
})
console.log(arr) // ["修正元素值", 2, 3, , 5]
// 实例十三
let arr = [1, 2, 3, , 5] // 倒数第二个元素为空,不会遍历
let result = arr.forEach((item, index, array) => {
arr[1] = '修正元素值'
console.log(item) // 1 修正元素值 3 5
})
console.log(arr) // [1, '修正元素值', 3, , 5]
实例十二和实例十三都对数组元素值做了修正,但是实例十二中item
值没有变化,实例十三中item
值变化了,为啥???
实例十二在实行forEach
时,修正了arr[0]
的值,数组arr
的值发生了转变,然后控制台输出item
的值没有变,缘由是仅仅是修正了arr[0]
的值,item
的值没有变化,控制台输出的item
的值照旧照样之前forEach
保留的item
的值。
实例十三在实行forEach
时,修正了arr[1]
的值,控制台输出item
与数组arr
的值均发生变化,在第一次遍历的时刻,数组arr
的第二个元素值已变化了,在第二次遍历的时刻,forEach
回调函数的参数值发生了变化,即控制台输出item
值发生变化。
假如已存在的值被转变,则传递给callback
的值是forEach
遍历到他们那一刻的值。
// 实例十四
let arr = [1, 2, 3, , 5] // 倒数第二个元素为空,不会遍历
let result = arr.forEach((item, index, array) => {
arr.push('增添到尾端,不会被遍历') // 挪用 forEach 后增添到数组中的项不会被 callback 接见到
console.log(item) // 1 2 3 5
return item // return只能完毕本次回调 会实行下次回调
console.log('不会实行,由于return 会实行下一次轮回回调')
})
console.log(result) // 纵然return了一个值,也照样返回undefined
// 实例十五
let arr = [1, 2, 3, , 5] // 倒数第二个元素为空,不会遍历
let result = arr.forEach((item, index, array) => {
if (item === 2) {
arr.shift()
}
console.log(item) // 1 2 5
})
已删除的项不会被遍历到。假如已接见的元素在迭代时被删除了(比方运用 shift()
),以后的元素将被跳过。
filter
“过滤”,为数组中的每一个元素挪用一次callback
函数,并运用一切使得callback
返回true
或等价于true
的值的元素建立一个新数组。
<span>语法:</span>
let newArray = arr.filter(function(currentValue, index, arr), thisValue)
参数:
- currentValue(必选):数组当前项的值;<br/>
- index(可选):数组当前项的索引;<br/>
- arr(可选):数组对象自身;<br/>
- thisValue(可选):当实行回调函数时用作
this
的值(参考对象),默许值为undefined
。注重点:
- 不修正挪用它的原数组自身,当然在
callback
实行时转变原数组另说;callback
函数返回值不肯定非假如Boolean
值,只假如弱即是== true/false
也没题目。
// 实例十六
let arr = [1, 2, 3, , 5] // 倒数第二个元素为空,不会遍历
let result = arr.filter(function(item, index, array) {
arr.push(6) // 在挪用 filter 以后被增添到数组中的元素不会被 filter 遍历到
arr[1] = 4 // 假如已存在的元素被转变了,则他们传入 callback 的值是 filter 遍历到它们那一刻的值
console.log(item) // 1 4 3 5
return item < 5 // 返回数组arr中小于5的元素
})
console.log(result) // [1, 4, 3]
// 实例十七
let arr = [0, 1, 2, 3, 4]
let result = arr.filter(function(item, index, array) {
return item // 做范例转换
})
console.log(result) // [1, 2, 3, 4]
map
建立一个新数组,其效果是该数组中的每一个元素都挪用一个供应的函数后返回的效果。其基础用法与forEach
相似,差别在于map的callback
须要有return
值。
<span>语法:</span>
let newArray = arr.map(function(currentValue, index, arr), thisValue)
参数:
- currentValue(必选):数组当前项的值
- index(可选):数组当前项的索引
- arr(可选):数组对象自身
- thisValue(可选):当实行回调函数时用作
this
的值(参考对象),默许值为undefined
注重点:
- 不修正挪用它的原数组自身,当然在
callback
实行时转变原数组另说。callback
函数只会在有值的索引上被挪用;那些历来没被赋过值或许运用delete
删除的索引则不会被挪用。
map
要领的重要作用实际上是对原数组映照发生新数组,看实例十八:
// 实例十八
let arr = [1, 2, 3]
let result = arr.map(function(item, index, array) {
return item * 2
})
console.log(result) // [2, 4, 6]
console.log(arr) // [1, 2, 3] // 原数组arr没有变
reduce
对累加器和数组中的每一个元素(从左到右)运用一个函数,终究合并为一个值。
<span>语法:</span>
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
参数:
- total(必选):累计器累计回调的返回值; 它是上一次挪用回调时返回的积累值,或initialValue
- currentValue(必选):数组中正在处置惩罚的元素
- currentIndex(可选):数组中正在处置惩罚的当前元素的索引 假如供应了initialValue,则肇端索引号为
0
,否则为1
- arr(可选):挪用
reduce()
的数组- initialValue(可选):作为第一次挪用
callback
函数时的第一个参数的值。 假如没有供应初始值,则将运用数组中的第一个元素。 在没有初始值的空数组上挪用reduce
将报错注重点:
- 假如没有供应initialValue,reduce 会从索引1的处所最先实行 callback 要领,跳过第一个索引。假如供应initialValue,从索引0最先。
<p>
回调函数第一次实行时:
</p>
- 假如挪用
reduce()
时供应了initialValue
,total
取值为initialValue
,currentValue
取数组中的第一个值; - 假如没有供应
initialValue
,那末total
取数组中的第一个值,currentValue
取数组中的第二个值; - 假如数组为空且没有供应
initialValue
,会抛出TypeError
; - 假如数组唯一一个元素(不管位置怎样)而且没有供应
initialValue
, 或许有供应initialValue
然则数组为空,那末此唯一值将被返回而且callback
不会被实行。
// 实例十九
let sum = [1, 2, 3].reduce(function (total, currentValue, currentIndex) {
return total + currentValue
})
console.log(sum) // 6
结语
本文梳理了对象和数组几种比较经常使用的遍历体式格局。数组的要领有许多,有部分内容本文没有涉及到,比方some
,every
,reduceRight
,find
,findIndex
等要领,感兴趣的同砚能够自行相识。<br/>
笔者如今照样一个前端新人,对遍历的完成体式格局不太清晰,借此梳理的时机,熟习相干的完成,文章若有不正确的处所迎接列位大佬斧正,也愿望有幸看到文章的同砚也有收成,一同生长!
——本文首发于个人民众号———
末了,迎接人人关注我的民众号,一同进修交换。