liangbus / blogging

Blog to go
10 stars 0 forks source link

关于遍历相关的方法,你知道多少? #14

Open liangbus opened 4 years ago

liangbus commented 4 years ago

for, for-in, for-of

for

这个应该是学编程的时候,循环的初认识吧,最基础的 for 循环其结构为

for ([initialization]; [condition]; [final-expression])
   statement

其中初始化,条件语句,final-expression 都是可以省略的,但须要确保正确跳出循环,以免陷入死循环 statement 中可以使用 breakcontinue 关键字退出和跳过本次循环 要注意的是,在初始化阶段,应该尽量避免使用 var 去声明变量,由于 ES5 没有块级作用域,所以 var 会把声明上升,因而有可能会出现变量污染,比如很常见的一道题

for(var i = 0; i < 10; i++) {
    setTimeout(() => {
        console.log(i)
    }, 0)
}
// 输出 10 次 10

改成 let 就可以使该循环正常输出 0-9,let 实际上为 JavaScript 新增了块级作用域。 普通 for 循环由于初始化和条件语句都是由开发者自行决定,所以就没有太多使用限制

for-in

for (variable in object)
  statement

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

for-in 一般用来遍历对象,而非遍历数组(虽然它可以用来遍历数组),因为通常数组的遍历是需要升序的,但它不能保证这一点,并且它返回的 key 为字符串,若以 key 做一些计算,可能会引起一些不必要的麻烦,并且,假如有通过 prototype 拓展了 Array 的原型,如果没有设置该属性是否可枚举,则通过 for-in 循环时,会将其一并遍历,如下:

Array.prototype.foo = function() {
    console.log('I am a fool!!')
}
var arr = ['a','b', 3,4,5]
for(let k in arr){
    console.log(typeof k, k)
}
// 结果
string 0
string 1
string 2
string 3
string 4
string foo

ECMA Script 规范允许 for-in 循环以不同的顺序遍历对象的属性。
当数组的索引是非数字或数组是稀疏数组(数组的索引不是连续的)时它们则按照我写顺序枚举 —— 犀牛书

for...in 循环会像 [[Getter]] 一样从原型链上查找属性,所以只要其原型上有相应的可枚举属性,for...in 都能遍历到,如下

var o1 = {a: 1, isObject: true}
var o2 = Object.create(o1) // {}
o2.isArray = false
o2.b = 101
var o3 = Object.create(o2) // {}
for(let key in o3) {
    console.log(`${key} -> ${o3[key]}`)
}
// isArray -> false
// b -> 101
// a -> 1
// isObject -> true

continue 和 break 对 for-in 是无效的

for-of

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

for (variable of iterable) {
    //statements
}

相比 for-in, for-of 可以遍历更多的迭代器,诸如 Map, Set, arguments 对象, String(相比以前,就省去了 string 转数组这一步了),for-of 可以使用 continue, break, throw, return 等跳出循环

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];
iterable.foo = 'hello';

for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i in iterable) {
  // 检查该属性是否为自身定义的,而非来自继承
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}

for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}

for...of 还能遍历我们自定义的迭代器,只要其有 [Symbol.iterator] 属性

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是"可遍历的"(iterable)。

举个例子

var a = 1
var loop = {
    [Symbol.iterator]: function(){ return this },
    next: () => { 
        return {
            done: a > 100,
            value: a++ 
        }
    }
}
for(let v of loop){
    console.log(v)
}

for...of 会自动调用 next 函数然后读取其返回的 value 值,并且根据 done 属性决定是否继续遍历,上面的示例就是输出 1到100 的数字

liangbus commented 4 years ago

forEach, map, reduce

forEach

forEach() 方法对数组的每个元素执行一次提供的函数。返回值是 undefined forEach 不会改变原数组内容(当然可以在 callback 执行时改变原数组) forEach 遍历的范围在第一次调用 callback 前就会确定。调用 forEach 后添加到数组中的项不会被 callback 访问到。

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array 
}[, thisArg])

map

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。 map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组) map 方法处理数组元素的范围是在 callback 方法第一次调用之前就已经确定了。调用map方法之后追加的数组元素不会被callback访问。

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array 
}[, thisArg])

reduce

对 reduce 的理解,与其说是遍历,更不如说是一个累计器,因为通过它对一个数组做一些累计性的操作更常见 arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue]) 其返回值,就是累计最终的结果 需要注意的是,如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始。 比如二维数组的扁平化就可以通过 reduce 来简单实现

[[0, 1], [2, 3], [4, 5]].reduce(
 (acc, b) => {
    return acc.concat(b);
  },
  []
);
// [0, 1, 2, 3, 4, 5]
YuetTong commented 2 years ago

除非遍历的是对象,指针不改变,对象内容可以改变