🔇 preface

In our daily life, going to and from work every day, the traffic lights accompany us most. So, in the following article, we will implement a traffic light manually. Look at the traffic lights you see every day. How does it work?

🔈 1. Demand Analysis – traffic lights

First, we want to implement a traffic light that can switch between different traffic states. As shown below:

Next, we’ll go through the various versions of the implementation.

🔉 2. Implementation version

1. Version 1: Rough version

The code is attached below:

HTML code:

<ul id="traffic" class="wait">
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>
Copy the code

The CSS code:

#traffic {
  display: flex;
  flex-direction: column;
}

#traffic li{
  list-style: none;
  width: 50px;
  height: 50px;
  background-color: gray;
  margin: 5px;
  border-radius: 50%;
}

#traffic.s1 li:nth-child(1) {
  background-color: #a00;
}

#traffic.s2 li:nth-child(2) {
  background-color: #aa0;
}

#traffic.s3 li:nth-child(3) {
  background-color: #0a0;
}

#traffic.s4 li:nth-child(4) {
  background-color: #a0a;
}

#traffic.s5 li:nth-child(5) {
  background-color: #0aa;
}
Copy the code

JS code:

const traffic = document.getElementById('traffic');

(function reset(){
  traffic.className = 's1';
  
  setTimeout(function(){
      traffic.className = 's2';
      setTimeout(function(){
        traffic.className = 's3';
        setTimeout(function(){
          traffic.className = 's4';
          setTimeout(function(){
            traffic.className = 's5';
            setTimeout(reset, 1000)},1000)},1000)},1000)},1000); }) ();Copy the code

The browser displays:

Ok, at this point, we have basically completed the basic form of the traffic light. However, does anyone feel that there is a bit of violence in the JS code above? First, it creates a closure ()(), and then, when switching states, it switches states through layer after layer of setTimeout(), which is very scary in terms of complexity and other levels.

So, we’re going to do version 2, which abstracts the data.

2. Version 2: Data Abstraction version

First attach the code:

HTML code:

<ul id="traffic" class="wait">
  <li></li>
  <li></li>
  <li></li>
</ul>
Copy the code

The CSS code:

#traffic {
  display: flex;
  flex-direction: column;
}

#traffic li {
  display: inline-block;
  width: 50px;
  height: 50px;
  background-color: gray;
  margin: 5px;
  border-radius: 50%;
}

#traffic.stop li:nth-child(1) {
  background-color: #a00;
}

#traffic.wait li:nth-child(2) {
  background-color: #aa0;
}

#traffic.pass li:nth-child(3) {
  background-color: #0a0;
}
Copy the code

JS code:

const traffic = document.getElementById('traffic');

const stateList = [
  {state: 'wait'.last: 1000},
  {state: 'stop'.last: 3000},
  {state: 'pass'.last: 3000},];function start(traffic, stateList){
  function applyState(stateIdx) {
    const {state, last} = stateList[stateIdx];
    traffic.className = state;
    setTimeout(() = > {
      applyState((stateIdx + 1) % stateList.length);
    }, last)
  }
  applyState(0);
}

start(traffic, stateList);
Copy the code

The browser displays:

In the above code, we abstract the wait, stop, and pass states by first receiving a set of states and then performing some instruction operations based on the set of states, such as switching states by changing the class of an element.

We can think of it as encapsulating the data in the state to make the code more extensible.

However, data abstraction is not enough, so let’s move on to the third version, process abstraction.

Version 3: Process Abstract version

First attach the code:

HTML code:

<ul id="traffic" class="wait">
  <li></li>
  <li></li>
  <li></li>
</ul>
Copy the code

The CSS code:

#traffic {
  display: flex;
  flex-direction: column;
}

#traffic li{
  display: inline-block;
  width: 50px;
  height: 50px;
  background-color: gray;
  margin: 5px;
  border-radius: 50%;
}

#traffic.stop li:nth-child(1) {
  background-color: #a00;
}

#traffic.wait li:nth-child(2) {
  background-color: #aa0;
}

#traffic.pass li:nth-child(3) {
  background-color: #0a0;
}
Copy the code

JS code:

const traffic = document.getElementById('traffic');

function wait(ms) {
  return new Promise(resolve= > setTimeout(resolve, ms));
}

function poll(. fnList){
  let stateIndex = 0;
  
  return async function(. args){
    let fn = fnList[stateIndex++ % fnList.length];
    return await fn.apply(this, args); }}async function setState(state, ms){
  traffic.className = state;
  await wait(ms);
}

let trafficStatePoll = poll(setState.bind(null.'wait'.1000),
                            setState.bind(null.'stop'.3000),
                            setState.bind(null.'pass'.3000));

(async function() {
  // noprotect
  while(1) {
    await trafficStatePoll();
  }
}());
Copy the code

The browser displays:

In the process abstraction above, the switching state above is a typical polling operation. So, we can abstract a polling method, which abstracts our start method into a polling method, and then inside that polling method, we give it a function list, which is a list of operation functions.

After that, we fetch the current state operand from fnList, and finally execute that function.

In this case, we implemented process abstraction. That’s actually an asynchronous problem for this example, so we can solve it with promises, async, await, etc. Check it out below.

Version 4: Imperative and declarative

First attach the code:

HTML code:

<ul id="traffic" class="wait">
  <li></li>
  <li></li>
  <li></li>
</ul>
Copy the code

The CSS code:

#traffic {
  display: flex;
  flex-direction: column;
}

#traffic li{
  display: inline-block;
  width: 50px;
  height: 50px;
  background-color: gray;
  margin: 5px;
  border-radius: 50%;
}

#traffic.stop li:nth-child(1) {
  background-color: #a00;
}

#traffic.wait li:nth-child(2) {
  background-color: #aa0;
}

#traffic.pass li:nth-child(3) {
  background-color: #0a0;
}
Copy the code

JS code:

const traffic = document.getElementById('traffic');

// Wait operation, i.e. how long to wait
function wait(time){
  return new Promise(resolve= > setTimeout(resolve, time));
}

// Set the state
function setState(state){
  traffic.className = state;
}

async function start(){
  //noprotect
  while(1){
    setState('wait');
    await wait(1000);
    setState('stop');
    await wait(3000);
    setState('pass');
    await wait(3000);
  }
}

start();
Copy the code

The browser displays:

Traffic light switching can be regarded as a continuous cycle, in which it will first setState a state and then await it for a period of time. Then continue to setState another state, then await it for a while, and so on.

In the actual application, if we want to extend it, we can modify the while mode to operate.

🔊 3

Online addresses for the above four versions:

  • Version 1: The rough and easy version
  • Version 2: Data Abstraction edition
  • Version 3: Process Abstract edition
  • Version 4: Imperative and declarative

📢 4. Conclusion

Here, from shallow to deep four traffic light version of the realization of the end of the explanation!

In the past, I might have used brute force cracking and data abstraction more, but now I have learned more about procedural abstraction, imperative and declarative.

So, JS has a lot of high-level content worth us to explore and dig ~

If you think this article is helpful to you, you might as well like to support yo ~~😉

📣 previous recommendation

👉 HTML basics worth watching

👉 CSS is still just about writing layouts? Discover the wonders of CSS in 10 minutes!

👉 front end just cut diagram son? Learn about UI design for developers

👉 Follow the steps of moon Shadow big guy, let’s learn how to write good JS (1)

👉 Follow the steps of moon Shadow big guy to learn how to write good JS (2)

👉 Listen to the Little Red Book translator talk about front-end development from a Web perspective