数组 / 对象,常用遍历/定位检测方法总结

412 阅读11分钟

1. 对象

1.1 for-in

1.1.1 通常用其获取所有可枚举属性

for-in语句的定义为:for...in语句以任意顺序遍历一个对象的Symbol以外的可枚举属性

    let obj = {
      name: 'KV-2',
      size: '152mm',
      type: 'HT',
    }

    for (let i in obj) {
      console.log(i, obj[i])
    }

输出 → image.png

1.1.2 注意,可枚举范围包括其原型链

    let obj1 = Object.create({ name1: '我是爷爷' })

    let obj2 = Object.create(obj1)
    obj2.name2 = '我是爸爸'

    let obj3 = Object.create(obj2)
    obj3.name3 = '孙咋'

    console.log(obj3)

    for (let i in obj3) {
      console.log(i, obj3[i])
    }

输出 → image.png 注意: for in 目标的【可枚举属性】,包括了其本身,和其原型链上的所有【可枚举属性】,这也是在较为严格的eslint规则中,不推荐使用 for in 遍历对象的原因; ​

1.1.3 手动增加过滤

因为 for-in 语句会遍历其自身和其原型链上的所有非symbol的可枚举属性,那么增加键名是否属于自身即可成功过滤; 例如,使用 hasOwnProperty 方法,判断键名是否为此对象的私有键名:

    let obj1 = Object.create({ name1: '我是爷爷' })

    let obj2 = Object.create(obj1)
    obj2.name2 = '我是爸爸'

    let obj3 = Object.create(obj2)
    obj3.name3 = '孙咋'

    console.log(obj3)

    for (let i in obj3) {
      if (Object.hasOwnProperty.call(obj3, i)) {
        console.log(i, obj3[i])
      }
    }

输出 → image.png _注:关于上面为什么要从原始 Object 取 hasOwnProperty 方法,可以拓展到 eslint 为什么不建议类似 _ if (obj3.hasOwnProperty(i)) { 写法、hasOwnProperty 不受保护等问题,这里不做展开;

1.2 Object.keys()

1.2.1 简单使用

定义:Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。

    let obj1 = Object.create({ name1: '我是爷爷' })

    let obj2 = Object.create(obj1)
    obj2.name2 = '我是爸爸'

    let obj3 = Object.create(obj2)
    obj3.name3 = '孙咋'

    console.log(obj3)

    console.log(Object.keys(obj3))

输出 → image.png

1.2.2 注意其在es6前后的区别

在ES5里,如果此方法的参数不是对象(而是一个原始值),那么它会抛出 TypeError。 在ES2015中,非对象的参数将被强制转换为一个对象。

Object.keys("foo");
// TypeError: "foo" is not an object (ES5 code)

Object.keys("foo");
// ["0", "1", "2"]                   (ES2015 code)

chrome测试: image.png ie测试: image.png

1.3 Object.values() / Object.entries()

1.3.1 基本使用

Object.keys() 的兄弟方法,用法基本一致,同样,这三者都只会获取目标本身的可枚举属性; ​

Object.keys() 键名 Object.values() 键值 Object.entries() 键名-键值对

    let obj1 = Object.create({ name1: '我是爷爷' })
    let obj2 = Object.create(obj1)
    obj2.name2 = '我是爸爸'
    let obj3 = Object.create(obj2)
    obj3.name3 = '孙咋'

    console.log(obj3)

    console.log(Object.keys(obj3))
    console.log(Object.values(obj3))
    console.log(Object.entries(obj3))

输出 → image.png

1.3.2 注意是 es6 添加的新方法

Object.values() / Object.entries() 是es6添加的新方法,在旧版本中运行,会报错不存在此方法: ​

chrome测试: image.pngimage.png ie测试: image.png

1.4 针对不可枚举属性的获取

1.4.1 Object.getOwnPropertyNames

定义:**Object.getOwnPropertyNames()**方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。

    const obj3 = {
      name2: '我是爷爷',
    }
    const obj2 = Object.create(obj3)
    obj2.name2 = '我是爸爸'
    obj2[Symbol('imSymbol')] = '这是Symbol属性'
    Object.defineProperty(obj2, 'name2_black', {
      value: '爸爸的黑历史',
      configurable: true,
      enumerable: false,
    })

    console.log(obj2)
    console.log(Object.keys(obj2))
    console.log(Object.getOwnPropertyNames(obj2))

输出 → image.png

简单理解: Object.keys() 对象自身的可枚举属性(不包括symbol) Object.getOwnPropertyNames() 对象自身的可枚举/不可枚举属性(不包括symbol)

注:在 chrome 控制台中,浅色属性代表此为不可枚举属性;

1.5 针对 Symbol 属性的获取

1.5.1 Object.getOwnPropertySymbols()

定义:Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。 (只返回Symbol属性,其他属性一概不管;)

    const obj3 = {
      name2: '我是爷爷',
    }
    const obj2 = Object.create(obj3)
    obj2.name2 = '我是爸爸'
    obj2[Symbol('imSymbol')] = '这是Symbol属性'
    Object.defineProperty(obj2, 'name2_black', {
      value: '爸爸的黑历史',
      configurable: true,
      enumerable: false,
    })

    console.log(obj2)
    console.log(Object.keys(obj2))
    console.log(Object.getOwnPropertyNames(obj2))
    console.log(Object.getOwnPropertySymbols(obj2))

输出 → image.png 注意: 其只会返回Symol属性,无法获取其他属性键名;

1.6 获取本身的所有属性值

1.6.1 Reflect.ownKeys

定义:静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组。 (返回目标本身的所有属性,包括可枚举、不可枚举、Symbol;)

    const obj3 = {
      name2: '我是爷爷',
    }
    const obj2 = Object.create(obj3)
    obj2.name2 = '我是爸爸'
    obj2[Symbol('imSymbol')] = '这是Symbol属性'
    Object.defineProperty(obj2, 'name2_black', {
      value: '爸爸的黑历史',
      configurable: true,
      enumerable: false,
    })

    console.log(obj2)
    console.log(Object.keys(obj2))
    console.log(Object.getOwnPropertyNames(obj2))
    console.log(Object.getOwnPropertySymbols(obj2))
    console.log(Reflect.ownKeys(obj2))

输出 → image.png

1.7 小结

1.7.1 表格归纳

遍历方法范围可枚举不可枚举Symbol
for-in目标及其原型链
Object.keys()目标自身
Object.getOwnPropertyNames()目标自身
Object.getOwnPropertySymbols()目标自身
Reflect.ownKeys()目标自身

注:ES语言后续添加的新特性不会对以前的代码产生副作用,比如在ES2015之前就存在的 for in循环、Object.keys()和 Object.getOwnPropertyNames()是肯定不会返回 Symbol属性的。

1.7.2 实践

    const obj3 = {
      name3: '我是爷爷',
    }
    obj3[Symbol('imSymbol3')] = '这是Symbol属性3'
    Object.defineProperty(obj3, 'name3_black', {
      value: '爷爷的黑历史',
      configurable: true,
      enumerable: false,
    })

    const obj2 = Object.create(obj3)
    obj2.name2 = '我是爸爸'
    obj2[Symbol('imSymbol2')] = '这是Symbol属性2'
    Object.defineProperty(obj2, 'name2_black', {
      value: '爸爸的黑历史',
      configurable: true,
      enumerable: false,
    })

    console.log('obj2', obj2)

    let arr = []
    for (let i in obj2) {
      arr.push(i)
    }
    console.log('for-in', arr)
    console.log('Object.keys()', Object.keys(obj2))
    console.log('Object.getOwnPropertyNames()', Object.getOwnPropertyNames(obj2))
    console.log('Object.getOwnPropertySymbols()', Object.getOwnPropertySymbols(obj2))
    console.log('Reflect.ownKeys()', Reflect.ownKeys(obj2))

输出 → image.png

2. 数组

2.1 遍历方法

2.1.1 原始 for

    let arr = ['a', 'b', 'c', 'd', 'e']

    for (var i = 0; i < arr.length; i++) {
      console.log(i, arr[i])
    }

输出 → image.png 适用场景: 日常很少这么写,但这个有最高的自由度,写一些算法题的时候很常用; ​

2.1.2 for-in

    let arr = ['a', 'b', 'c', 'd', 'e']

    for (let i in arr) {
      console.log(i, arr[i])
    }

输出 → image.png

注意: for-in的定义为:for...in语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性。 ​

  1. for in 遍历的是键名,对象的属性名都是字符串,所以出现一个现象:

for in 对数组来说也是在遍历键名(下标),因此取到的 i 是字符串,而不是数字; 相对,map等方法才是真的在输出位置的数字下标 i ; ​

  1. for...in本身是以任意顺序遍历的,不应该用于迭代一个关注索引顺序的 Array。

适用场景: 可用,但不是很推荐用于数组遍历; ​

2.1.3 for of

定义:for...of语句可迭代对象(包括 ArrayMapSetStringTypedArrayarguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句

    let arr = ['a', 'b', 'c', 'd', 'e']

    for (let item of arr) {
      console.log(item)
    }

输出 → image.png

注意: for of 遍历的是目标的迭代器,只可用于含有迭代器的目标;(例如对象就没有迭代器) 因为获取的是迭代器给出的内容(),其 item 就是迭代器返回的value值,对数组来说就是其值而非下标; ​

适用场景: 所有包含迭代器的目标; ​

注:理解 for-of 实质,需要拓展到迭代器、生成器部分,这里不做展开;

2.1.4 forEach

定义:forEach() 方法对数组的每个元素执行一次给定的函数。

    const arr = ['A', 'B', 'C', 'D', 'E']

    arr.forEach((item, i, arr) => {
      console.log(item, i, arr)
    })

输出 → image.png

注意: 除了抛出异常以外,没有办法中止或跳出 forEach() 循环。如果你需要中止或跳出循环,forEach() 方法不是应当使用的工具。 若你需要提前终止循环(break / continue),你可以使用:

这些数组方法则可以对数组元素判断,以便确定是否需要继续遍历:

译者注:只要条件允许,也可以使用 filter() 提前过滤出需要遍历的部分,再用 forEach() 处理。 ​

2.1.5 map

定义:map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。 同forEach,无法中途结束退出;

    const arr = ['A', 'B', 'C', 'D', 'E']

    let arr2 = arr.map((item, i) => {
      return `第${i + 1}个值=${item}`
    })

    console.log(arr2)

输出 → image.png 注意: map() 中方法没有return值时,会建立一个undefined,保持和原数组长度一致

    const arr = ['A', 'B', 'C', 'D', 'E']

    let arr2 = arr.map((item, i) => {
      // return `第${i + 1}个值=${item}`
    })

    console.log(arr2)

输出 → image.png

2.1.6 filter

定义:filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。 同forEach,无法中途结束退出;

    const arr = [111, 222, 333, 444, 555]

    let arr2 = arr.filter((item) => {
      return item > 300
    })

    console.log(arr2)

输出 → image.png

注意:

  1. filter() 中方法没有return值时,会返回空数组
    const arr = [111, 222, 333, 444, 555]

    let arr2 = arr.filter((item) => {
      // return item > 300
    })

    console.log(arr2)

输出 → image.png

  1. filter() 可以直接传入类型作为过滤函数使用,如过滤虚值:
    let arr = [1, 2, 'abc', '哈哈', undefined, null, 0, '']
    console.log(arr)
    console.log(arr.filter(Boolean))

输出 → image.png

  1. 注意!filter虽然返回新数组,但其内部的数据,若为引用类型(数组对象等),依然保持对原数组的引用!
    let data = [
      { id: 1, name: '01' },
      {
        id: 2,
        name: '02',
        children: [
          { id: 1, name: '0201' },
          { id: 2, name: '0202' },
        ],
      },
    ]

    let data2 = data.filter((item) => true)
    data[1].name = '这是大号02'

    console.log(data)
    console.log(data2)

输出 → image.png 更新:之前理解错误,实际上不是因为保持对原数组的引用,是因为和原数组保持了一样的指向,下例证明:

    let data = [
      { id: 1, name: '01' },
      {
        id: 2,
        name: '02',
        children: [
          { id: 1, name: '0201' },
          { id: 2, name: '0202' },
        ],
      },
    ]

    let data2 = data.filter((item) => true)
    // data[1].name = '这是大号02'
    data[1] = {
      id: 3,
      name: '03',
      children: [
        { id: 1, name: '0301' },
        { id: 2, name: '0302' },
      ],
    }

    console.log(data)
    console.log(data2)

输出 → image.png 具体的指向、引用的关系理解,参考笔记 误区!引用是指向内存,不是链表那种关系!

2.1.7 reduce

定义:reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。 同forEach,无法中途结束退出;

    let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    let add = arr.reduce((sum, item) => {
      return sum + item
    }, 0)

    console.log(add)

输出 → image.png 注意:

  1. reduce() 中方法没有return值时,返回undefined
    let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    let add = arr.reduce((sum, item) => {
      // return sum + item
    }, 0)

    console.log(add)

输出 → image.png

  1. 不传默认值时,自动以第一个元素为默认值,从第二个元素开始遍历:
    let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    let add = arr.reduce((sum, item) => {
      console.log(`现在计算${item}`)
      return sum + item
    }, 0)

    console.log(add)

    let add2 = arr.reduce((sum, item) => {
      console.log(`现在计算${item}`)
      return sum + item
    })
    console.log(add2)

输出 → image.png

  1. 应用:

因为reduce实际每次的返回值都是sum,最终也是返回sum值,且sum可以支持任意类型数据,可以用reduce做一些简单应用:

  1. 模拟map
    let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    let arr1 = arr.map((item) => {
      return `值=${item}`
    })
    console.log(arr1)

    let arr2 = arr.reduce((sum, item) => {
      sum.push(`值=${item}`)
      return sum
    }, [])
    console.log(arr2)

输出 → image.png

  2. [利用reduce简化降维迭代](https://www.yuque.com/linxin-da4fs/gu1zm2/evkzua)
  2. ...

2.2 定位/检测 方法

2.2.1 every

定义:every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。 遇到一个 false 就退出并返回 false,全为 true 才返回true;

    const arr = ['A', 'B', 'C', 'D', 'E']

    let result = arr.every((item) => {
      return item !== 'G'
    })
    console.log(result)

    let result2 = arr.every((item) => {
      return item !== 'C'
    })
    console.log(result2)

输出 → image.png

注意: 若收到一个空数组,此方法在一切情况下都会返回 true; 若执行中检测到一个 false ,遍历会直接中断,并返回最终结果 false;

中断情况例如上面的判断 C :

    const arr = ['A', 'B', 'C', 'D', 'E']
    
		let result = arr.every((item, i) => {
      console.log(`开始判断-${i}-${item}`)
      return item !== 'G'
    })
    console.log('检查所有元素中,都不存在G=', result)

    let result2 = arr.every((item, i) => {
      console.log(`开始判断-${i}-${item}`)
      return item !== 'C'
    })
    console.log('检查所有元素中,都不存在C==', result2)

输出 → image.png

2.2.2 some

定义:和every相反,some() 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。 遇到一个 true 就退出并返回 true,全为 false 才返回 false;

比较 every 和 some

    const arr = ['A', 'B', 'C', 'D', 'E']

    let result = arr.some((item, i) => {
      console.log(`开始判断-${i}-${item}`)
      return item !== 'C'
    })
    console.log('some判断C=', result)

    let result2 = arr.every((item, i) => {
      console.log(`开始判断-${i}-${item}`)
      return item !== 'C'
    })
    console.log('every存在C=', result2)

输出 → image.png

注意: 同样和 every 相反,如果用一个空数组进行测试,在任何情况下它返回的都是false。 ​

2.2.3 find

定义: find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

    const arr = ['A', 'B', 'C', 'D', 'E']

    let firstFind = arr.find((item) => {
      return item === 'C'
    })
    console.log(firstFind)

    let firstFind2 = arr.find((item) => {
      return item === 'G'
    })
    console.log(firstFind2)

输出 → image.png

注意,同 every / some 类似,find 在找到首个符合条件目标后,就会退出方法

    const arr = ['A', 'B', 'C', 'D', 'E']

    let firstFind = arr.find((item, i) => {
      console.log(`开始判断-${i}-${item}`)
      return item === 'C'
    })
    console.log(firstFind)

    let firstFind2 = arr.find((item, i) => {
      console.log(`开始判断-${i}-${item}`)
      return item === 'G'
    })
    console.log(firstFind2)

输出 → image.png

2.2.4 findIndex

定义:findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。 用法和 find 一致,只是返回内容从值变为下标

    const arr = ['A', 'B', 'C', 'D', 'E']

    let firstFind = arr.findIndex((item, i) => {
      console.log(`开始判断-${i}-${item}`)
      return item === 'C'
    })
    console.log(firstFind)

    let firstFind2 = arr.findIndex((item, i) => {
      console.log(`开始判断-${i}-${item}`)
      return item === 'G'
    })
    console.log(firstFind2)

输出 → image.png

2.2.5 indexOf()

定义:**indexOf()**方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。 类似 findIndexOf ,区别在于传的不是判断函数,是直接传某个值比较相等;

    const arr = ['A', 'B', 'C', 'D', 'E']

    let firstFind = arr.indexOf('C')
    console.log(firstFind)

    let firstFind2 = arr.indexOf('G')
    console.log(firstFind2)

输出 → image.png

注意: indexOf 也会在找到首个后退出方法,测试如下:

    const arr = ['A', 'B', 'C', 'D', 'E']

    const arrPorxy = new Proxy(arr, {
      get (target, property) {
        console.log(`监听到属性${property}被get`)
        return Reflect.get(...arguments)
      },
    })

    let firstFind = arrPorxy.indexOf('C')
    console.log(firstFind)

    let firstFind2 = arrPorxy.indexOf('G')
    console.log(firstFind2)

输出 → image.png

indexOf 支持第二个参数: 正数时理解为开始搜索的下标,负数时理解为长度length+负数的下标; 第二个参数只影响取值范围,输出的下标位置依然是针对整个数组来说的; 例:

    const arr = ['A', 'B', 'C', 'D', 'E']

    console.log(arr.indexOf('C'))     // 范围 ABCDE   // 2
    console.log(arr.indexOf('C', 2))  // 范围 CDE     // 2
    console.log(arr.indexOf('C', 3))  // 范围 DE      // -1

    console.log(arr.indexOf('C', -1)) // 范围 E       // -1
    console.log(arr.indexOf('D', -1)) // 范围 E       // -1
    console.log(arr.indexOf('E', -1)) // 范围 E       // 4

    console.log(arr.indexOf('C', -2)) // 范围 DE      // -1
    console.log(arr.indexOf('D', -2)) // 范围 DE      // 3
    console.log(arr.indexOf('E', -2)) // 范围 DE      // 4

    console.log(arr.indexOf('C', -3)) // 范围 CDE     // 2

输出 → image.png

2.2.6 includes()

定义:includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。 基本和 indexOf 用法一致,返回结果变为一个Bool值; 逻辑也和 indexOf 类似,找到首个目标后就返回并退出方法;

    const arr = ['A', 'B', 'C', 'D', 'E']

    const arrPorxy = new Proxy(arr, {
      get (target, property) {
        console.log(`监听到属性${property}被get`)
        return Reflect.get(...arguments)
      },
    })

    let firstFind = arrPorxy.includes('C')
    console.log(firstFind)

    let firstFind2 = arrPorxy.includes('G')
    console.log(firstFind2)

输出 → image.png

同 indexOf ,includes 也支持第二个参数,规则相同 正数时理解为开始搜索的下标,负数时理解为长度length+负数的下标; 例:

    const arr = ['A', 'B', 'C', 'D', 'E']

    console.log(arr.includes('C'))     // 范围 ABCDE   // true
    console.log(arr.includes('C', 2))  // 范围 CDE     // true
    console.log(arr.includes('C', 3))  // 范围 DE      // false

    console.log(arr.includes('C', -1)) // 范围 E       // false
    console.log(arr.includes('D', -1)) // 范围 E       // false
    console.log(arr.includes('E', -1)) // 范围 E       // true

    console.log(arr.includes('C', -2)) // 范围 DE      // false
    console.log(arr.includes('D', -2)) // 范围 DE      // true
    console.log(arr.includes('E', -2)) // 范围 DE      // true

    console.log(arr.includes('C', -3)) // 范围 CDE     // true

输出 → image.png

2.3 小结

2.3.1 表格归纳

方法适用目标作用可否中止跳出
原始 for无限制无限制可中止跳出
for-in对象(数组)遍历可中止跳出
for-of有迭代器的对象(数组)遍历可中止跳出
forEach数组遍历执行传入函数不可跳出
map数组遍历执行传入函数,内部return组成新数组;不可跳出
filter数组遍历执行传入函数,内部return为ture的组成新数组;不可跳出
reduce数组遍历执行传入函数,内部return作为下轮的sum;不可跳出
every数组遍历,判断全部符合传入函数,回Bool值;碰到false自动跳出
some数组遍历,判断存在符合传入函数的内容,回Bool值;碰到true自动跳出
find数组遍历,返回第一个符合传入函数的值;找到目标自动跳出
findIndex数组遍历,返回第一个符合传入函数的下标;找到目标自动跳出
indexOf数组遍历,返回第一个和传入内容相同的下标;找到目标自动跳出
includes数组遍历,返回是否存在传入内容,回Bool值;找到目标自动跳出