Open sisterAn opened 4 years ago
第一种实现会导致内存溢出😂
第一种方式可以实现,但是它是在同步实现,大量的循环会导致内存和CPU飙升。
于是我尝试了另一种方式requestIdleCallback
,一个实验中的API来实现,虽然实现了,但是具体可控也太远了,setTImeout
最小400 ms,我这个没统计多少毫秒,但是调用了1320次。。。然后才得到结果。
它并不是固定1320次,它是不稳定的,可能CPU就停不下来了
function setTimeOut(cb, delay, startTime) {
const start = startTime || +new Date()
console.count('setTimeOut');
// 如果CPU空闲
window.requestIdleCallback(() => {
console.count('requestIdleCallback');
const now = +new Date();
if (now - start >= delay) {
cb.call(this);
return;
}
// 否则重新轮询
setTimeOut(cb, delay, startTime);
});
}
function hello() {
console.log('hello');
}
setTimeOut(hello, 1000);
输出结果
setTimeOut :1320
requestIdleCallback :1320
hello
function setTimeout(fn, time) { let flag = true; const start = new Date(); while (flag) { const end = new Date() if(end - start > time) { flag = false } } return fn() }
用while 会导致 js 死锁,其他任务无法执行,不可取
function setTimeout(fn, time) { let flag = true; const start = new Date(); while (flag) { const end = new Date() if(end - start > time) { flag = false } } return fn() }
这个代码是要干啥???主线程被搞死了,还玩个gg,面试官看到这样的代码只能说拜拜了。
支持多个, 支持清除的实现..
const _timerId = {};
let _id = 0;
function mySetTimeout(fn, timeout, ...args) {
// 'use strict'
const start = new Date().getTime();
const cid = _id++;
const loop = () => {
const time = new Date().getTime();
if (time >= start + timeout) {
fn.apply(this, args);
delete _timerId[cid];
} else {
_timerId[cid] = requestAnimationFrame(loop)
}
}
_timerId[cid] = requestAnimationFrame(loop);
return cid;
}
function myClearTimeout(timer) {
cancelAnimationFrame(_timerId[timer])
delete _timerId[timer]
}
function showName(){
console.log("Hello")
}
mySetTimeout(showName, 1000);
const mySetTimeout = (cb, t) => { let _date = new Date(); let _has = 0; while (_has - _date < t) { _has = new Date(); } cb(); }; 暴力一点,嘻嘻嘻
window.requestAnimationFrame 这个方法每次返回的值都不一样吧,怎么在外部去关闭定时器,也没有实现第四点返回 timerID
function setTimeout(fn, time) { let flag = true; const start = new Date(); while (flag) { const end = new Date() if(end - start > time) { flag = false } } return fn() }
这个代码是要干啥???主线程被搞死了,还玩个gg,面试官看到这样的代码只能说拜拜了。
我想应该是表达的要 const end,start = Date.now()
setTimeout
方法,就是一个定时器,用来指定某个函数在多少毫秒之后执行。它会返回一个整数,表示定时器的编号,同时你还可以通过该编号来取消这个定时器:setTimeout
的第一个参数是一个将被延迟执行的函数,setTimeout
的第二个参数是延时(多少毫秒)。如果使用
setTimeout
延迟的函数需要携带参数,我们可以把参数放在setTimeout
里(放在已知的两个参数后)来中转参数给需要延迟执行的函数。alert('test')
,此法不建议使用)为延迟毫秒数
,可选的,默认值为 0setTimeout
的返回值是一个数字,这个成为timeoutID
,可以用于取消该定时器手写一个简单 setTimeout 函数
感谢 @coolliyong 的提醒,这里调整一下
setTimeout 在浏览器中的实现
浏览器渲染进程中所有运行在主线程上的任务都需要先添加到消息队列,然后事件循环系统再按照顺序执行消息队列中的任务。
在 Chrome 中除了正常使用的消息队列之外,还有另外一个消息队列,这个队列中维护了需要延迟执行的任务列表,包括了定时器和 Chromium 内部一些需要延迟执行的任务。所以当通过 JavaScript 创建一个定时器时,渲染进程会将该定时器的回调任务添加到延迟队列中。
源码中延迟执行队列的定义如下所示:
当通过
JavaScript
调用setTimeout
设置回调函数的时候,渲染进程将会创建一个回调任务,包含了回调函数showName
、当前发起时间、延迟执行时间,其模拟代码如下所示:创建好回调任务之后,再将该任务添加到延迟执行队列中,代码如下所示:
现在通过定时器发起的任务就被保存到延迟队列中了,那接下来我们再来看看消息循环系统是怎么触发延迟队列的。
从上面代码可以看出来,我们添加了一个
ProcessDelayTask
函数,该函数是专门用来处理延迟执行任务的。这里我们要重点关注它的执行时机,在上段代码中,处理完消息队列中的一个任务之后,就开始执行ProcessDelayTask
函数。ProcessDelayTask
函数会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务。等到期的任务执行完成之后,再继续下一个循环过程。通过这样的方式,一个完整的定时器就实现了。设置一个定时器,
JavaScript
引擎会返回一个定时器的ID
。那通常情况下,当一个定时器的任务还没有被执行的时候,也是可以取消的,具体方法是调用clearTimeout
函数,并传入需要取消的定时器的ID
。如下面代码所示:clearTimeout(timer_id)
其实浏览器内部实现取消定时器的操作也是非常简单的,就是直接从delayed_incoming_queue
延迟队列中,通过ID
查找到对应的任务,然后再将其从队列中删除掉就可以了。来源:浏览器工程与实践(极客时间)
setTimeout 在 nodejs 中的实现
setTimeout
是在系统启动的时候挂载的全局函数。代码在timer.js
。我们先看一下
setTimeout
函数的代码。其中
Timeout
函数在lib/internal/timer.js
里定义。由代码可知,首先创建一个保存相关信息的对象,然后执行
active
函数。从上面的代码可知,
active
一个定时器实际上是把新建的timeout
对象挂载到一个哈希队列里。我们看一下这时候的内存视图。当我们创建一个
timerList
的是时候,就会关联一个底层的定时器,执行setTimeout
时传进来的时间是一样的,都会在一条队列中进行管理,该队列对应一个定时器,当定时器超时的时候,就会在该队列中找出超时节点。下面我们看一下new TimeWraper
的时候发生了什么。其实就是初始化了一个
libuv
的uv_timer_t
结构体。然后接着start
函数做了什么操作。就是启动了刚才初始化的定时器。并且设置了超时回调函数是
OnTimeout
。这时候,就等定时器超时,然后执行OnTimeout
函数。所以我们继续看该函数的代码。OnTimeout
函数继续调kOnTimeout
,但是该变量在time_wrapper.c
中是一个整形,这是怎么回事呢?这时候需要回lib/timer.js
里找答案。由上可知,
TimeWrapper.c
里的kOnTimeout
字段已经被改写成一个函数,所以底层的定时器超时时会执行上面的代码,即从定时器队列中找到超时节点执行,直到遇到第一个未超时的节点,然后重新设置超时时间。再次启动定时器。来源:nodejs之setTimeout源码解析