这是我参与8月更文挑战的第14天,活动详情查看8月更文挑战
前言
本文将对比几种循环,比如,探索 forEach 的底层原理和 for of 循环的底层机制;运行代码,比较所耗费的时间。
命令式编程和函数式编程
我们先来了解下什么是命令式编程和函数式编程。
命令式编程
命令式编程的主要思想是关注计算机执行的步骤, 即一步一步告诉计算机先做什么再做什么。
如果要解决以下一个问题:
求一个list里所有值的和
命令式编程可能会这么做
let list = [1,2,3,4,5];
let total = 0;
for (let i =0 ;i<list.lenght;i++){
total += list[i]
}
console.log(total)
命令式编程,比较灵活,关注 how 如何做?初学者入门时,常用的编程方式和编程思想。
函数式编程
声明式编程是以数据结构的形式来表达程序执行的逻辑。 它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。
同样要实现“求一个list里所有值的和”,函数式编程,会怎么做?
let list = [1,2,3,4,5];
let total = list.reduce((sum,n)=>{
return sum + n
})
console.log(total)
函数式编程,使用方便,无法管控过程,性能上也有所消耗,是内部上实现封装,what 关注结果。
for 循环
for 循环是自己控制循环过程,明确知道循环多少次;而不确定循环次数的情况下使用 while。
情况1:基于 var 声明的时候,for 和 while 性能差不多。
我们可以在控制台运行下代码,看下具体结果
let arr = new Array(9999999).fill(0);
console.time('FOR~~');
for(var i = 0; i < arr.length; i++){}
console.timeEnd('FOR~~');
console.time('WHILE~~');
var i = 0;
while(i < arr.length) {
i++;
}
console.timeEnd('WHILE~~');
在我的电脑,运行结果:
情况2:基于 let 声明的时候,for 循环性能更好「原理:没有创造全局不释放的变量」
我们可以将上面的循环中的 var 声明改成 let 声明,再运行下代码看下。下面是测试结果。
forEach 底层原理
我们来尝试手写一下 forEach,理解下 forEach 实现逻辑。
里面涉及到 this 和 call 等相关内容,不熟悉的同学,可以查下相关资料学习下。
Array.prototype.forEach = function forEach(callback, context) {
// this => arr
let self = this,
i = 0,
len = self.length;
context = context == null ? window : context;
for(;i < len; i++) {
typeof callback === 'function' ? callback.call(context, self[i], i) : null;
}
}
for in 循环的 BUG 及解决方案
for in 性能很差,迭代当前对象中所有可枚举的属性「私有属性大部分是可枚举的,公有属性(出现在所属类的原型上的)也有部分是可枚举的」查找机制上一定会搞到原型链上去,按照原型链一级级查找很耗性能
for in 循环存在的问题:
- 问题一:遍历顺序以数字优先
- 问题二:无法遍历 Symbol 属性
- 问题三:可以遍历到公有中可枚举的(一般是自定义属性)
运行代码
Object.prototype.fn = function fn(){}
let obj = {
name: '追梦玩家',
age: 18,
[Symbol('sex')]: 'male',
0: 200,
1: 300
};
for(let key in obj) {
console.log(key);
}
我们看下截图对应的输出结果,正好都说明了 for in 循环存在的问题
那我们如何解决无法遍历 Symbol 属性呢?不慌,我们需要使用到 Object.getOwnPropertySymbols
let keys = Object.keys(obj);
if(typeof Symbol !== 'undefined') {
keys = keys.concat(Object.getOwnPropertySymbols(obj));
}
keys.forEach(key => {
console.log('属性名:', key);
console.log('属性值:', obj[key]);
})
那如何解决可以遍历到公有中可枚举的?其实就是考察我们的基础知识,使用 hasOwnProperty 方法即可。
for of 循环的底层机制
mdn 文档关于 for of 循环的描述
for...of语句在可迭代对象(包括Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
重点是可迭代对象,这些数据结构实现了迭代器规范,其实 for of 循环的原理就是按照迭代器规范遍历的。
迭代器 iterator 规范「具备 next 方法,每次执行返回一个对象,具备 value/done 属性」
我们来尝试模拟实现一下。
let arr = [10, 20, 30];
// 默认的
// arr[Symbol.iterator] = function () {
// 我们重写的方法
Array.prototype[Symbol.iterator] = function () {
let self = this,
index = 0;
return {
// 必须具备 next 方法,执行一次 next 方法,拿到结构中的某一项的值
// done: false value: 每一次获取的值
next () {
if (index > self.length - 1) {
return {
done: true,
value: undefined
}
}
return {
done: false,
value: self[index++]
}
}
}
}
关于几种循环的运行结果如图,耗费时间对比
从上图,我们可以看出使用 for 循环的性能更好,而且也值得注意的是,实际开发中,我们应该选择合适的循环方式来实现想要的效果。
文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你或者喜欢,欢迎点赞和关注。