This question is actually a roommate interview some time ago. I think similar to reduce to achieve map, with XXX to achieve YYY topics are actually quite interesting, study the ability to be integrated. But this might make more sense. For example, we often use setTimeout to implement a countdown. Here’s what I think about it.

Simple version

First, we’ll use setTimeout to implement a simple version of setInterval.

SetInterval needs to be called over and over again, which brings us to recursive calls themselves:

const mySetInterval = (cb, time) = > {
  const fn = (a)= > {
    cb() // Execute the incoming callback function
    setTimeout((a)= > {
      fn() // Call yourself recursively
    }, time)
  }
  setTimeout(fn, time)
}
Copy the code

Let’s write some code to test it:

mySetInterval((a)= > {
  console.log(new Date()},1000)
Copy the code

Well, that’s fine, it does what we want… Wait a minute. How do we stop? The total can not be implemented to leave it…

The realization of the clearInterval

If you use setInterval, you probably know that clearInterval exists (how else would you stop interval?).

ClearInterval is used as clearInterval(ID). This ID is the return value of setInterval, which clears the specified timer.

const id = setInterval((a)= > {
  // ...
}, 1000)
// ...
clearInterval(id)
Copy the code

But have you thought about how clearInterval works? To answer that question, we need to implement the return value of mySetInterval.

Return value of mySetInterval

Back to our simple version of mySetInterval:

const mySetInterval = (cb, time) = > {
  const fn = (a)= > {
    cb() // Execute the incoming callback function
    setTimeout((a)= > {
      fn() // Call yourself recursively
    }, time)
  }
  setTimeout(fn, time)
}
Copy the code

Now its return value is undefined because it is not specified. So the first thing we’re going to do is return an ID.

Return setTimeout(fn, time). Since we know that setTimeout also returns an ID, the initial idea is to implement our myClearInterval with the ID returned by setTimeout and then call clearTimeout(ID).

As follows:

const mySetInterval = (cb, time) = > {
  const fn = (a)= > {
    cb() // Execute the incoming callback function
    setTimeout((a)= > { // The second, the third...
      fn() // Call yourself recursively
    }, time)
  }
  return setTimeout(fn, time) // The first setTimeout
}

const id = mySetInterval((a)= > {
  console.log(new Date()},1000)

setTimeout((a)= > { // Clear timer after 2 seconds
  clearTimeout(id)
}, 2000)
Copy the code

This is clearly not going to work. The id returned by mySetInterval is the id of the first setTimeout. However, when you clearTimeout 2 seconds later, the id of the second setTimeout, the third setTimeout, and so on for recursive execution is no longer the first id. Therefore, it cannot be cleared at this time.

So we need to save the new ID every time we execute setTimeout. How to save? We should think of closures:

const mySetInterval = (cb, time) = > {
  let timeId
  const fn = (a)= > {
    cb() // Execute the incoming callback function
    timeId = setTimeout((a)= > { // Closure updates timeId
      fn() // Call yourself recursively
    }, time)
  }
  timeId = setTimeout(fn, time) // The first setTimeout
  return timeId
}
Copy the code

It’s nice that at this point we’ve been able to update the timeId. The problem is that executing mySetInterval returns an id that is not the latest timeId. Because timeId is only updated inside fn, its update is not known externally. Is there any way to let the outside world know about timeId updates?

Yes, the answer is to use global variables.

let timeId // Global variables
const mySetInterval = (cb, time) = > {
  const fn = (a)= > {
    cb() // Execute the incoming callback function
    timeId = setTimeout((a)= > { // Closure updates timeId
      fn() // Call yourself recursively
    }, time)
  }
  timeId = setTimeout(fn, time) // The first setTimeout
  return timeId
}
Copy the code

But there is a problem with this. Since timeId is of type Number, when we use it like this:

const id = mySetInterval((a)= > { // where id is type Number, it is a copy of the value, not a reference
  console.log(new Date()},1000)

setTimeout((a)= > { // Clear timer after 2 seconds
  clearTimeout(id)
}, 2000)
Copy the code

Since id is of type Number, we get a copy of the value of the global variable timeId rather than a reference, so the above code is still invalid. But we can already clear timers with the global timeId variable:

setTimeout((a)= > { // Clear timer after 2 seconds
  clearTimeout(timeId) // Global variable timeId
}, 2000)
Copy the code

TimeId is a variable of type Number, so only one mySetInterval ID can exist globally at a time. You can’t clear multiple mySetInterval timers.

So we need a type that supports multiple timeids and allows the id returned by mySetInterval to be used by our myClearInterval. As you can imagine, we’re going to do it with a global Object.

Modify the code as follows:

let timeMap = {}
let id = 0 // Simply implement unique id
const mySetInterval = (cb, time) = > {
  let timeId = id // Assign timeId to id
  id++ // id Specifies a unique ID
  let fn = (a)= > {
    cb()
    timeMap[timeId] = setTimeout((a)= > {
      fn()
    }, time)
  }
  timeMap[timeId] = setTimeout(fn, time)
  return timeId / / return timeId is
}
Copy the code

Our mySetInterval still returns an ID value. Except that the ID value is the content of a key in the global variable timeMap.

Instead of updating the timeId each time we update the setTimeout ID, we update the value in timeMap[timeId].

The timeId obtained by calling mySetInterval is unchanged, but the real setTimeout ID obtained by timeMap[timeId] is always updated.

In addition, to ensure the uniqueness of timeId, I simply use an incremented global variable ID to ensure uniqueness.

Now that you have the ID value, all that’s left is the implementation of myClearInterval.

MyClearInterval implementation

Since the timeId returned by our mySetInterval is not the id returned by a true setTimeout, we cannot simply clear the timer with clearTimeout(timeId).

However, the principle is also very similar, we just need to get the real ID:

const myClearInterval = (id) = > {
  clearTimeout(timeMap[id]) // Run timeMap[id] to get the real ID
  delete timeMap[id]
}
Copy the code

Test it out:

No problem ~

So far we have simply implemented setInterval and clearInterval using setTimeout and clearTimeout. Of course, this article is talking about simple implementation, after all, there are still a few things left undone, such as setTimeout args parameter, Node and browser side setTimeout difference, etc. Is just a primer, the focus is on how to achieve step by step. Thanks for reading ~