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])
}
输出 →
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])
}
输出 →
注意:
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])
}
}
输出 →
_注:关于上面为什么要从原始 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))
输出 →
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测试:
ie测试:
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))
输出 →
1.3.2 注意是 es6 添加的新方法
Object.values() / Object.entries() 是es6添加的新方法,在旧版本中运行,会报错不存在此方法:
chrome测试:
ie测试:
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))
输出 →
简单理解: 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))
输出 →
注意:
其只会返回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))
输出 →
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))
输出 →
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])
}
输出 →
适用场景:
日常很少这么写,但这个有最高的自由度,写一些算法题的时候很常用;
2.1.2 for-in
let arr = ['a', 'b', 'c', 'd', 'e']
for (let i in arr) {
console.log(i, arr[i])
}
输出 →
注意: for-in的定义为:for...in语句以任意顺序遍历一个对象的除Symbol以外的可枚举属性。
- for in 遍历的是键名,对象的属性名都是字符串,所以出现一个现象:
for in 对数组来说也是在遍历键名(下标),因此取到的 i 是字符串,而不是数字; 相对,map等方法才是真的在输出位置的数字下标 i ;
- for...in本身是以任意顺序遍历的,不应该用于迭代一个关注索引顺序的 Array。
适用场景: 可用,但不是很推荐用于数组遍历;
2.1.3 for of
定义:for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
let arr = ['a', 'b', 'c', 'd', 'e']
for (let item of arr) {
console.log(item)
}
输出 →
注意: 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)
})
输出 →
注意: 除了抛出异常以外,没有办法中止或跳出 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)
输出 →
注意:
map() 中方法没有return值时,会建立一个undefined,保持和原数组长度一致;
const arr = ['A', 'B', 'C', 'D', 'E']
let arr2 = arr.map((item, i) => {
// return `第${i + 1}个值=${item}`
})
console.log(arr2)
输出 →
2.1.6 filter
定义:filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。 同forEach,无法中途结束退出;
const arr = [111, 222, 333, 444, 555]
let arr2 = arr.filter((item) => {
return item > 300
})
console.log(arr2)
输出 →
注意:
- filter() 中方法没有return值时,会返回空数组:
const arr = [111, 222, 333, 444, 555]
let arr2 = arr.filter((item) => {
// return item > 300
})
console.log(arr2)
输出 →
- filter() 可以直接传入类型作为过滤函数使用,如过滤虚值:
let arr = [1, 2, 'abc', '哈哈', undefined, null, 0, '']
console.log(arr)
console.log(arr.filter(Boolean))
输出 →
- 注意!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)
输出 →
更新:之前理解错误,实际上不是因为保持对原数组的引用,是因为和原数组保持了一样的指向,下例证明:
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)
输出 →
具体的指向、引用的关系理解,参考笔记 误区!引用是指向内存,不是链表那种关系!
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)
输出 →
注意:
- 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)
输出 →
- 不传默认值时,自动以第一个元素为默认值,从第二个元素开始遍历:
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)
输出 →
- 应用:
因为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)
输出 →
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)
输出 →
注意: 若收到一个空数组,此方法在一切情况下都会返回 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)
输出 →
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)
输出 →
注意: 同样和 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)
输出 →
注意,同 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)
输出 →
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)
输出 →
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)
输出 →
注意: 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)
输出 →
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
输出 →
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)
输出 →
同 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
输出 →
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值; | 找到目标自动跳出 |