Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
liangbus opened this issue Nov 30, 2019 · 2 comments
Open

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

liangbus opened this issue Nov 30, 2019 · 2 comments

Comments

@liangbus
Copy link
Owner

liangbus commented Nov 30, 2019

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
Copy link
Owner Author

liangbus commented Dec 1, 2019

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
Copy link

YuetTong commented Mar 6, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants