• 深入JS 中三类循环原理和性能


    for 循环和 while 循环的性能对比

    1. let arr = new Array(9999999).fill(1)
    2. console.time('for-let')
    3. for(let i = 0; i< arr.length; i++){}
    4. console.timeEnd('for-let')
    5. var j = 0
    6. console.time('for-var')
    7. for(j = 0; j< arr.length; j++){}
    8. console.timeEnd('for-var')
    9. console.time('while')
    10. let i = 0
    11. while(i< arr.length){
    12. i ++
    13. }
    14. console.timeEnd('while')

    Google中运行结果

    思考 不同声明下性能差异产生的原因

    • 基于var声明的时候,FOR和WHILE性能差不多「不确定循环次数的情况下使用WHILE」  
    • 基于let声明的时候,由于 for 中块级作用域的影响,内存得到释放,运行的运行的速度会更快一些。FOR循环性能更好「原理:没有创造全局不释放的变量」  

     foreach

    1. let arrs = new Array(999999);
    2. console.time('for');
    3. for (let i = 0; i < arrs.length; i++) {
    4. };
    5. console.timeEnd('for');
    6. console.time('forEach');
    7. arrs.forEach((arr) => {
    8. });
    9. console.timeEnd('forEach');

    1. // let arrs = new Array(999999);
    2. let arrs = new Array(999999).fill(1);
    3. console.time('for');
    4. for (let i = 0; i < arrs.length; i++) {
    5. };
    6. console.timeEnd('for');
    7. console.time('forEach');
    8. arrs.forEach((arr) => {
    9. });
    10. console.timeEnd('forEach');

     

    上面两个示例中同样的数据量却有很大的差异这是为什么呢?

    fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。

    在第二个示例中

    arrs = new Array(999999).fill(1);

     另外在mdn中对于foreach有这样一段描述

     所有我猜测差异是因为第一个示例中的foreach没有处理数组中未初始的值,而第二个示例中foreach因为fill()给数组填充了值,所有才会有这么大的差异,个人猜想如有错误恳请指正

    实际使用中需要操作的数组大部分情况都是有值的,可以认为forEach相比for 性能更差

    但是对于foreach我们更关注的是他的函数式编程

    for循环属于命令式编程(how如何去做 看中的是过程,循环多少轮多少次是可以控制的,还可以改变步长改变循环次数,还可以通过break、continue去结束循环)

    forEach属于函数式编程,用起来比for方便,性能比for更差些(what更关注结果,把执行过程封装起来,内部实现封装,自己控制不了循环次数,无法管控过程 不可以中断结束循环。所以会把循环都走一遍)

    而在数据量小的时候性能差异不大

    1. let arrs = new Array(9999).fill(1);
    2. console.time('for');
    3. for (let i = 0; i < arrs.length; i++) {
    4. };
    5. console.timeEnd('for');
    6. console.time('forEach');
    7. arrs.forEach((arr) => {
    8. });
    9. console.timeEnd('forEach');
    10. </script>

     思考 什么情况适合使用foreach进行循环遍历呢

    forEach属于函数式编程,数据量小的时候 使用forEach循环遍历操作数据更方便

    数据量比较大量级时为提升性能,我倾向于使用for循环自己在for循环中操作数据

    思考:在 forEach 中怎么中断循环

    return 无法跳出循环

    foreach使用return 只是中止本次继续执行,而不是终止循环,有点像for循环里面的continue,然后forEach 里面的return是没有返回值的效果的

    可以自己throw new Error("end");再try catch捕获异常

     除了抛出异常以外,没有办法中止或跳出 forEach() 循环。如果你需要中止或跳出循环,forEach() 方法不是应当使用的工具。

    实现 forEach

    看v8引擎中js源码 使用的是for循环实现

    v8/array.js at eff24bef5c0f071008bdd4bcee3a86384e90c90b · v8/v8 · GitHub

    1. Array.prototype.myForEach = function (callback, context) {
    2. let i = 0,
    3. than = this,
    4. len = this.length;
    5. context = context ? window : context;//forEach每循环一次把传进去的回调函数执行一次 还有
    6. //第二个参数是每一回调执行内部this的指向 不传的情况下默认window
    7. for (; i < len; i++) {
    8. typeof callback === 'function' ? callback.call(context, than[i], i, than) : null
    9. }
    10. }

     for in 循环

    for in 的循环性能循环很差。性能差的原因是因为:迭代当前对象中所有可枚举的属性(私有属性大部分是可枚举的,公有属性【出现在所属类的原型上的】,也有部分是可枚举的),查找机制一定会搞到原型链上去

    1. let obj = new Array(9999).fill(1);
    2. console.time('for-in');
    3. for (let key in obj) {
    4. if (!obj.hasOwnProperty(key)) break;// 阻止获取原型链上的公有属性 fn 减少循环提示性能
    5. // console.log(key);
    6. }
    7. console.timeEnd('for-in');

    缺点

    1、遍历顺序以数字优先,由小到大遍历

    2、无法遍历symbol属性

    3、可以遍历到公有中的可枚举属性 可以使用 hasOwnProperty来阻止遍历公有属性。

    思考 怎么获取 Symbol 属性

    (Object.keys()可以拿到所有非symbol属性 Object.getOwnPropertySymbols(拿到所有symbol属性。拼接拿到所有)

    1. let obj = {
    2. name: 'zhufeng',
    3. age: 12,
    4. [Symbol('AA')]: 100,
    5. 0: 200,
    6. 1: 300
    7. };
    8. let keys = Object.keys(obj);
    9. if (typeof Symbol !== "undefined") keys = keys.concat(Object.getOwnPropertySymbols(obj));
    10. keys.forEach(key => {
    11. console.log('属性名:', key);
    12. console.log('属性值:', obj[key]);
    13. {
    14. )

    for of 循环 

    1. let arr = new Array(999999).fill(1)
    2. console.time('forOf')
    3. for(const value of arr){}
    4. console.timeEnd('forOf')

     for of 循环的原理是按照是否有迭代器规范来循环的,所有带有 Symbol.iterator 的都是实现了迭代器规范,比如数组一部分类数组,Set,Map...对象没有实现 Symbol.iterator 规范,所以不能使用for of循环。

    • 使用 for of 循环,首先会先执行 Symbol.iterator 属性对应的函数且返回一个对象
    • 对象内包含一个函数 next() 循环一次执行一次 next()next() 中又返回一个对象
    • 这个对象内包含两个值分别是 done:代表循环是否结束,true 代表结束;value:代表每次返回的值
    1. let arr = [1,2,3,4]
    2. arr[Symbol.iterator] = function () {
    3. let self = this,
    4. index = 0;
    5. return {
    6. next() {
    7. if(index > self.length-1){
    8. return {
    9. done: true,
    10. value: undefined
    11. }
    12. }
    13. return {
    14. done: false,
    15. value: self[index++]
    16. }
    17. }
    18. }
    19. }
    20. console.time('forOf');
    21. for (const val of arr) {
    22. console.log(val);
    23. }
    24. console.timeEnd('forOf');

     思考,如何让普通的类数组可以使用 for of 循环

    给类数组对象添加Symbol.iterator接口规范就可以了。

    类数组被需具备和数组类试的结果属性名从0, 1, 2...开始,且必须具备length 属性 

    1. //  类数组对象「默认不具备迭代器规范」
    2. let obj = {
    3.     0: 200,
    4.     1: 300,
    5.     2: 400,
    6.     length: 3
    7. };
    8. obj[Symbol.iterator] = Array.prototype[Symbol.iterator];
    9. for (let val of obj) {
    10.     console.log(val);
    11. }

    参考文章

    JS中三类循环对比及性能分析 - 掘金

    以V8中js源码为例了解GitHub查看代码功能 - 兴趣使然的Geek - 博客园

  • 相关阅读:
    C++进阶篇5-哈希
    java 企业工程管理系统软件源码 自主研发 工程行业适用
    solvePnP的使用及物理意义
    RedisConnectionException: Unable to connect to 127.0.0.1:6379
    沃尔玛平台入驻条件,沃尔玛平台可以做哪些产品——站斧浏览器
    算法笔记(一)
    Scala 基础 (五):面向对象(上篇)
    多线程,进程
    ppt忘记保存怎么恢复?
    202.快乐数
  • 原文地址:https://blog.csdn.net/qq_60587956/article/details/127452480