갑자기 JS으로 HTML에 도형그리기(반복문에 비동기?)

KIP·2022년 6월 15일
0

HTML/CSS의 지식을 다시 견고히 다지겠다고 마음먹은 후 CSS의 position쪽 공부를 하다가 갑자기 도형을 그리고 싶어졌다.

사각형 5개 그리기 => 클로저 마무리(..??)

도형을 하나 그려본다.
//style 
  #wrapper{
        border: 1px solid black;
        width: 100px;
        height: 100px;
        position: absolute;
    }
//html
<div id="wrapper">
         <div id="wrapper"  onclick=onClick()>
       
    </div>

멋있는 사각형이 완성되었다. ㅋ

absolute를 배우고 있었으므로 내가 그리고자 하는 도형은 곂곂이 쌓인 사각형 5개를 그려보는 것.

이렇게 더 멋있는 사각형이 되었다. ㅋ
사실 삼항연산자 너무 대강 썼는데 핵심은 이게 아니니..

간단한 설명을 한 뒤 핵심으로 들어가겠다.

사각형 그린 구조(코드의 순서대로 따라오길)

onClick = () =>{
	for(let i = 1 ; i< 5; i++){
	    	
            setTimeout(function(){
                    const wrapper =document.getElementById('wrapper')
                    const createDiv = document.createElement('div')
                    const color= 
                    ['red','blue','black','pink','red','blue','black','pink']
                    createDiv.style= `border: 1px solid black;
                    background-color: 
                    ${color[Math.floor(Math.random()*10)] ?
                    color[Math.floor(Math.random()*10)]: color[0] };
                    width: 100px;
                    height: 100px;
                    position: absolute;
                    margin:${i*20}px`
                    wrapper.appendChild(createDiv)
            },1000 * i)
	}
}

for문을 생성 (사각형 몇 개 그릴래?) -> 5개

그 후 setTimeout을 적용해, 한번에 5개가 짠 하고 나타나지 않고, 1초,2초....뒤에 하나씩 나오게끔 구현(중요한 내용이라 밑에 다시 다룰예정)

저 사각형의 부모요소에 접근 후, div element생성.

div의 스타일에 대한 접근 -> 크기, 높이, 같은 정적인 요소들 생성 + margin과 color에는 동적인 접근

  • margin: 가장 기본적인 i(블록 스코프)에 대한 값으로 20px씩 늘림.
  • color: 기본적인 컬러들을 지정한 후, random함수(얘는 0~1까지만 숫자를 만든다)에 정수반환하는 floor(5.95면 5를 반환)을 섞어,
    삼항연산자(값이 있니? 있으면 그대로 써: 아니면 이거 써)로 지정.

대충 사각형 다 만들었고, 이제 appendChild 말 그대로 자식을 넣겠다.=> wrapper안에 createDiv를 넣음. (div>div 구조)

이렇게 실행을 해보면 1,2... 5초동안 5개의 사각형이 그려진다.

문제가 뭔데?

처음 이 사각형을 만들 때, for문 안에 setTimeout을 쓴다는 것에 대해 별 생각이 없다가 꽤나 난항을 겪을뻔 했다. 분명 내가 알고있는 사실인데..

var/const&let, for, setTimeout

이것에 대한 설명은 너무나 많은 곳에서 했기 때문에, 간단하게 코드로만 작성

  // var i = 1;
         for(i=1; i<6; i++){
            console.log(i)
         	setTimeout(function(){
             console.log(i)
             document.write(i);
         	}, 1000* i);
         }
    console.log(i)

함수레벨의 스코프인 var는 최종적으로 1이 아닌 6이 출력된다는 사실을 알고있다.
그러면 저 비동기함수인 setTimeout에서는 어떻게 동작될까?
https://velog.io/@kip/JS-ConfEUWhat-the-heck-is-the-event-loop-part.2
(나름 잘 정리된 나의 글..)
for문은 분명 stack에 쌓일거고 비동기에는 setTimeout이 동작 중일 것이다.

우리가 상상한 계획은 123456이다. 그런데 HTML에서는 666666을 그린다.
여차하면 666666을 위해서 만들었다고 뻥치자

그럼 어떻게 할래?

방법이 여러가지가 있다.

  • 그냥 for문 초기화문에 let을 쓰고 전역에서 var를 지운다. 깔끔하게ㅎ
    (공부를 위해서는 이런 우회법으로 돌아가면 하나를 놓칠 가능성이 있다.)

  • 클로저
    두두두둥장.

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 
클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지
(Lexical scoping)를 먼저 이해해야 한다.

진짜 간단한 함수 하나로 보자. (모질라에서 긁어왔다)

 function showHelp(help) {
      document.getElementById('help').innerHTML = help;
    }  
    
 function setupHelp() {
      var helpText = [
          {'id': 'email', 'help': 'Your e-mail address'},
          {'id': 'name', 'help': 'Your full name'},
          {'id': 'age', 'help': 'Your age (you must be over 16)'}
        ];
      
 for (var i = 0; i < helpText.length; i++) {
        var item = helpText[i];
         document.getElementById(item.id).onfocus = function() {
          showHelp(item.help);
        }
      }

반복문을 heplText의 길이(3)만큼 돌린다. -> var item = helpText[0->1->2]가 된다. (자동)
onfocus는 내가 마우스를 클릭할 때 실행된다. (수동)
여기서부터 뭔가 이상하다. 그럼 내가 클릭할 때면? var item = helpText[2] 가 값으로 들어간다.

우리가 생각한건..

  • E-mail 클릭-> 음 your e-mail address가 뜨겠구만? -> 아냐 돌아가. you must be over 16
  • Name 클릭 -> 음.. your full name이 뜨겠구만? -> 어림도 없어 you must be over 16

해결책(나에게 let이 없다는 가정을 하자 때는 ES2015 전..)

더 많은 클로저?

 function showHelp(help) {
      document.getElementById('help').innerHTML = help;
    }  
    
 function setupHelp() {
      var helpText = [
          {'id': 'email', 'help': 'Your e-mail address'},
          {'id': 'name', 'help': 'Your full name'},
          {'id': 'age', 'help': 'Your age (you must be over 16)'}
        ];
        
  function makeHelpCallback(help) {   <----이녀석의 추가
      return func![](https://velog.velcdn.com/images/kip/post/e69b6d98-db27-41eb-8abd-2758ed977d24/image.png)
ion() {
        showHelp(help);
      };
    } 
  
 for (var i = 0; i < helpText.length; i++) {
        var item = helpText[i];
         document.getElementById(item.id).onfocus = 
         makeHelpCallback(item.help)  <-- cb
        }
      }

즉시 실행함수?

  function showHelp(help) {
      document.getElementById('help').innerHTML = help;
    }

    function setupHelp() {
      var helpText = [
          {'id': 'email', 'help': 'Your e-mail address'},
          {'id': 'name', 'help': 'Your full name'},
          {'id': 'age', 'help': 'Your age (you must be over 16)'}
        ];

      for (var i = 0; i < helpText.length; i++) {
        (function() {
           var item = helpText[i];
           document.getElementById(item.id).onfocus = function() {
             showHelp(item.help);
           }
        })(); // Immediate event listener attachment with the current value of item (preserved until iteration).
      }
    }

0개의 댓글