将依据自身痛点学习,计划对原生JavaScript写一个系统,本文为第一篇,感兴趣的同窗能够关注我的公众号:ZeroToOneMe,或者github博客,将持续输出。前端
JavaScript
中能够实现遍历的数据类型主要是对象,其中包括普通对象与数组。实现遍历的方式有不少,本文梳理下JavaScript
中能实现遍历的方式。java
语句以任意顺序遍历一个对象自有的、继承的、可枚举的、非Symbol
的属性。对于每一个不一样的属性,语句都会被执行。git
语法:github
for (variable in object) { xxx }
参数:express
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
遍历,可能会存在如下几个问题:数据结构
在必定程度上来看,使用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 }
语句在可迭代对象(包括 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
循环的全部缺陷。 返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用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 }
用于建立一个循环,它包含了三个可选的表达式,三个可选的表达式包围在圆括号中并由分号分隔, 后跟一个在循环中执行的语句(一般是一个块语句)。
<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 }
按升序为数组中含有效值的每一项执行一次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()
),以后的元素将被跳过。
“过滤”,为数组中的每一个元素调用一次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]
建立一个新数组,其结果是该数组中的每一个元素都调用一个提供的函数后返回的结果。其基本用法与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没有变
对累加器和数组中的每一个元素(从左到右)应用一个函数,最终合并为一个值。
<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/>
笔者如今仍是一个前端新人,对遍历的实现方式不太清楚,借此梳理的机会,熟悉相关的实现,文章若有不正确的地方欢迎各位大佬指正,也但愿有幸看到文章的同窗也有收获,一块儿成长!
——本文首发于我的公众号———最后,欢迎你们关注个人公众号,一块儿学习交流。