前言
前段时间朋友给我发了一个面试题,是这样的:
写一个 mySetInterVal(fn, a, b),每次间隔 a,a+b,a+2b,...,a+nb 的时间,然后写一个 myClear,停止上面的 mySetInterVal
细看这个题目,它就是一个无限循环的递增计时器。
题目分析:
- 每次间隔
a+nb时间调用fn,那就要使用setTimeout来做定时 - 如何
a+nb时间执行fn,a和b是传入的确定的值,n是递增的,所以要递归调用定时器
题目解析完成,要做的事情就简单了。
方法不止一种,本文会介绍两个实现方法。
常规方法(ES5 函数)
function mySetInterval(fn,a,b) {
// let timerId = null
let tag = {
timerId: 0
}
let n = 0
function innerFuction() {
tag.timerId = setTimeout(function(){
fn()
n++
console.log("时间间隔:" + (a+n*b))
innerFuction()
}, a + n*b)
}
innerFuction()
return tag
}
// 清除定时器
function myClear(timerId) {
clearTimeout(timerId)
}
/*调用函数-如何使用*/
// 要传入的fn函数
function output() {
console.log('递增计时器')
}
// 调用mySetInterval函数
const tag = mySetInterval(output,100,200)
console.log(tag)
const dom = document.getElementById("demo");
dom.onclick = function () {
myClear(tag.timerId);
};
此方法中有1处写法值得注意:
为何使用tag对象来存储timerId,而不直接定义一个timerId?
这是因为我们每次循环调用setTimeout,他都会生成一个新的定时器编号,并且这个编号是数字类型。如果我们直接定义一个timerId,我们在初始化mySetInterval函数时,返回的就是第一次的定时器编号,在后面想要清除时,这个编号其实已经无效了,因为已经生成了新的定时器编号。
从上图中我们就可以清楚地看到,为何无法停止定时器了。
那为什么使用tag.timerId就可以清除了呢?
这是因为对象是引用类型,初始化mySetInterval函数时,返回的tag是一个对象,它是一个引用类型,其实在内存中存储的只是引用地址。当我们在调用clearTimeout时,传递的是tag.timerId,那么它就需要去tag这个对象中获取timerId,就会拿到最新的值,从而把最新的定时器清除,之后也就不会再调用定时器了。
ES6的class方法
这个方法写起来就比常规方法要简单多了。
const MySetInterval = class {
_timer = null;
_num = 0;
constructor(fn, a, b) {
this.fn = fn;
this.a = a;
this.b = b;
}
start() {
this._timer = setTimeout(() => {
this._num++;
this.fn();
console.log('间隔时间:' + (this.a + this._num * this.b))
this.start();
}, this.a + this._num * this.b);
}
myClear() {
clearTimeout(this._timer);
this._timer = null;
}
};
如何调用
function output() {
console.log("sleep");
}
const sleep = new MySetInterval(output, 100, 200);
sleep.start();
const dom = document.getElementById("demo");
dom.onclick = function () {
sleep.myClear();
};
总结
这道手写面试题,它其实想考察的就是我们的js基础。如:
- 引用类型与作用域
- 递归
- 定时器