公司做的局域网网站怎么登陆,百度推广 网站要备案吗,phpmysql网站开发实例,销售的产品是帮别人做网站我这里先不说和C之间的性能差距#xff0c;而是展开说JavaScript递归优化问题#xff0c;有人问我为什么不说性能差距#xff0c;我#xff1a;#xff1f;#xff1f;#xff1f;这个问题就跟问地球为什么是圆的一样(明摆着)传统的递归函数#xff0c;比如:function a…我这里先不说和C之间的性能差距而是展开说JavaScript递归优化问题有人问我为什么不说性能差距我这个问题就跟问地球为什么是圆的一样(明摆着)传统的递归函数比如:function a(){return a()}这是一个经典的递归在函数a内部调用自身调用栈的机制如下:每调用一个函数解释器就会把该函数添加进调用栈并开始执行.正在调用栈中执行的函数还调用了其它函数那么新函数也将会被添加进调用栈一旦这个函数被调用便会立即执行.当前函数执行完毕后解释器将其清出调用栈继续执行当前执行环境下的剩余的代码.当分配的调用栈空间被占满时会引发“堆栈溢出”错误.这里我们做一个测试尝试运行这个递归函数找出调用栈深度:let index 0function a(){index 1console.log(index)return a()}执行结果如下:// 省略多行输出 12559 12560 12561 12562 12563 Uncaught RangeError: Maximum call stack size exceededat console.X.t. [as log] (init.js:1)这里可以看出当递归深度达到12563的时候调用栈爆掉了平时使用是没有问题的但是对于某些特殊或者极端情况你又有相似需求的情况下怎么样突破这个调用栈限制做到永不爆栈呢这里需要一个比较hack的写法, 我们对上面的递归函数进行一下改造:let index 0async function a(){await undefinedindex 1console.log(index)return await a()}我们来执行一下看一下结果:// 省略多行输出 100590 100591 100592// 省略多行输出事实上这个递归函数永远不会停止它会一直执行下去也没有爆栈这是一个神奇的优化可以让你写出非常大深度的递归而不会出现问题这个优化的关键就是:async function() { await undefined }首先将递归函数改为async函数然后在内部最好第一行 await undefined这个操作的原理就是1 async创建微任务队列然后执行器执行当前队列.2此时遇到await undefined其实这个写法等同于await (async () {})和await Promise.resolve(setTimeout)这几种写法效果等同用unedfined只是为了在实现同样效果的情况下更简洁既然已经等同了那就从这三个写法分析起.3此时执行器发现第一个任务完全没有等待马上完成了但是执行器发现后面的任务是需要等待的并不会马上完成.4这时候执行器为了microtask(也就是协程)调度的合理优化不会让这个微任务队列始终占有这个execution而是会把当前微任务队列转移到别的execution去执行(您几位走得慢请去那边空闲的地方走).5转移execution带来的操作就是因为没办法直接转移调用栈所以会先将当前调用栈入堆然后把任务队列转移到别的execution.6然后队列里面接下来的任务全部都是使用新创建的execution去执行.这个操作的本意就是为了让当前栈入堆而且这个写法在C#和Kotlin里面是完全通用的因为这3个语言的异步方案都是基本类似而这个写法来自Rust群一位群友的发现当时我看到这种写法的时候也表示了惊奇然后对于递归大面积使用这种写法目前没有发现什么问题.这两天发现有人星风雪月对执行机制有很深的成见所以我这里使用Node.JS的async hooks做了一下异步执行的调试这是测试代码:const async_hooks require(async_hooks)let index 0let print_buffer /*** async hooks会追踪async调用,* 而console.log使用异步输出,* 所以这里使用同步方法模拟console*/function println(log) {print_buffer log \n}/* 创建钩子 */async_hooks.createHook({init(asyncId, type, triggerAsyncId) {const eid async_hooks.executionAsyncId()println(init: *********************************)println(init: triggerAsyncId triggerAsyncId)println(init: executionAsyncId eid)println(init: asyncId asyncId)println(init: type type)}}).enable()/*********** 测试区 **********/async function A() {/* 为了观察方便只执行2次 */println(A: runing)index 1if (index 2) return undefined// 有优化递归 await undefinedreturn await A()}/*********** 测试区 **********/A().then(() {console.log(print_buffer)})执行之后的输出:init: *********************************init: triggerAsyncId 1init: executionAsyncId 1init: asyncId 2init: type PROMISEA: runinginit: *********************************init: triggerAsyncId 2init: executionAsyncId 1init: asyncId 3init: type PROMISEinit: *********************************init: triggerAsyncId 3init: executionAsyncId 1init: asyncId 4init: type PROMISEinit: *********************************init: triggerAsyncId 2init: executionAsyncId 1init: asyncId 5init: type PROMISEinit: *********************************init: triggerAsyncId 4init: executionAsyncId 4init: asyncId 6init: type PROMISEA: runinginit: *********************************init: triggerAsyncId 6init: executionAsyncId 4init: asyncId 7init: type PROMISE这里看executionAsyncId标志这个是当前执行器的ID这里可以看到当经过await undefined之后, 执行器从1变成了4说明这里发生了execution转移下面我修改一下代码变成不优化的写法:// 无优化递归// await undefinedreturn await A()我这里将await undefined删除再来执行看看会是什么情况:init: *********************************init: triggerAsyncId 1init: executionAsyncId 1init: asyncId 2init: type PROMISEA: runinginit: *********************************init: triggerAsyncId 1init: executionAsyncId 1init: asyncId 3init: type PROMISEA: runinginit: *********************************init: triggerAsyncId 3init: executionAsyncId 1init: asyncId 4init: type PROMISEinit: *********************************init: triggerAsyncId 2init: executionAsyncId 1init: asyncId 5init: type PROMISE再看executionAsyncId这里始终是使用1去执行所以没有转移execution执行这里就很能说明问题了await undefined可以转移执行器执行让当前栈入堆这样可以使调用栈不会溢出达到深递归优化的目的.对于评论区朋友说明这种方式会阻塞macrotask所以不推荐这种写法我这里并不表示完全反对意见我这里来说一下我自己的看法异步任务归属microtask而其他事件回调归属macrotaskmicrotask的优先级本身就比macrotask要高所以肯定是microtask先执行然后才轮到macrotask像setTimeout(0)这种本身就是属于macrotask肯定要等到microtask执行完成之后才能执行不过这确实会带来一个问题就是对已经运行的macrotask产生时间分辨率精度影响比如定时器偏移定时器不会精准得按时间分片执行任务所以这种写法见仁见智你如果需要精确macrotask执行的场景还是慎用.