데브코스 프론트엔드 5기/VanillaJS를 통한 자바스크립트 기본 역량 강화 1

231004 [Day12] VanillaJS를 통한 자바스크립트 기본 역량 강화 1 (1)

코딩하는 키티 2023. 10. 5. 12:03

강의를 듣기 전 JS 사전 퀴즈 7문제를 풀어보았다.
오류를 찾고 해결방법을 물어보는 문제와 출력값 구하는 문제들이 나왔는데 '이게 뭐가 문제지?'라는 생각과 한참 생각해야 답이 나오는 문제들이었다. 찍은 것도 있었다.. 여전히 기초와 기본이 부족한 것 같다. 틈틈이 기초 강의들을 들어야겠다는 생각이 들었다. 

 

JS 사전 퀴즈 문제 1

Q) 다음 코드의 실행결과는?

  1. 'cherry'가 출력된다.
  2. 빈 문자열이 출력된다.
  3. undefined가 출력된다.
  4. 오류가 발생한다. ✅
function Cat(name, age) {
  this.name = name;
  this.age = age;
}

const cat1 = Cat('cherry', 6);
console.log(cat1.name);
함수(Cat) 내에 함수를 return하는 코드가 없다. -> cat1에 undefined 값이 들어감 -> undefined.name을 찾을 수 없기에 오류

함수 안에 있는 this는 window를 가르키기에 window.name을 찍으면 cherry 가 나온다.

this가 window를 가르키는 이유
- 함수를 new 키워드를 통해 생성하지 않았기 때문이다.
- new를 사용하지 않고 함수를 선언할 시 함수 내부의 this는 window를 가르킨다.
- new를 사용하면 this는 새로운 Cat함수의 새로운 객체를 가르키게 된다.

 

this를 사용하여 객체지향 흉내내기 가능

function Cat(name, age) {
  this.name = name;
  this.age = age;
  
  this.printCat = () => {
  	console.log(`${this.name}은 ${this.age}살 고양이에요!`);
  }  
}

const cat1 = new Cat('cherry', 6);
const cat2 = new Cat('angdu', 3);
const cat3 = new Cat('mimi', 4);

cat1.printCat()
cat2.printCat()
cat3.printCat()

//cherry는 6살 고양이에요!
//andgu는 3살 고양이에요!
//mimi는 4살 고양이에요!

 

JS 사전 퀴즈 문제 2

Q) 다음 코드의 실행결과는?

  1. 함수가 선언되기만 하고 아무일도 일어나지 않는다.
  2. 'Hello,'만 출력된다.
  3. 'Hello, kitty'가 출력된다. ✅
  4. 오류가 발생한다. 
(function(name){
  console.log(`Hello, ${name}`)
})('kitty');
위 코드는 'Hello, kitty' 가 출력된다.
위 처럼 작성하는 함수 방식을 IIFE(즉시실행함수)라고 한다.

1. 전역(window)에 필요 없는 변수 생성을 막을 수 있다.
- 즉시실행함수 내부에 변수를 선언할 시, 내부 변수는 전역 변수가 아닌 지역변수로 남는다.
- 따라서 전역스코프가 오염되는 것을 줄일 수 있다.
 
2. privte 변수를 생성할 수 있다.

const animal = (function(){
 // age는 밖에서 접근할 수 없다. -> private 효과
let age = 6;
 function Age(){
    return age;
}
 return {Age : Age}
})();

위 코드에서 age는 직접적인 변경이 불가능하며, 접근자함수(Age)로만 age를 확인할 수있다.

 

JS 사전 퀴즈 문제 3

Q) 다음 코드의 실행결과는?

A) undefined jinah

var obj1 = {
  name : "kitty",
  color : "pink"
  obj2 : {
    obj3 : {
      memberName : "jinah",
      play : function(){
      	console.log(`${this.name} ${this.memberName}`)
      }
    }
  }
}

obj1.obj2.obj3.play()
play 함수내 this는 obj3 객체를 가르키기 때문에 kitty는 undefined가 나오고 jinah가 나온다.

- undefined가 나오지 않으려면 ${this.name}을 ${obj1.name}으로 바꾼다.

 

JS 사전 퀴즈 문제 4

Q) 다음 코드를 실행하면 오류가 발생한다. 오류가 발생하는 원인은 무엇이고 어떻게 해결할 수 있을까?

function zoo(animals){
  this.animals = animals;
  this.kind = function () {
    setTimeout(function(){
      this.animals.forEach(function(animal){
        animal.kind();
      })
    }, 1000)
  }
}

var cats = new zoo([
  {
    animal: 'cherry',
    kind: function() {
    	console.log('sound: m e o w m e o w')
    }
  }
});

cats.kind();
this의 범위에 대한 문제

setTimeout() 함수 내 this는 setTimeout() 내 function 을 가리킨다.
-> this안에 animals가 없기때문에 에러 발생(undefined)

해결 방법

1. Arrow Function 이용하기

  • Arrow Function은 자기 자신의 함수 스코프를 만들지 않고 해당 Arrow Function 상위의 Scope를 가진다.
  • 따라서 this는 zoo 객체를 가르키게 된다.
setTimeout(() => {
   this.animals.forEach(function(animal){
        animal.kind();
      })
   }, 1000)

 

2. bind() 사용하기

  • bind() 메서드를 사용하면 this를 지정해 줄 수 있다.
  • bind는 새로운 함수를 생성하는 메서드로 아래 코드에서 bind 내부 this는 zoo를 가르키기 때문에 내부 함수에서 this가 zoo를 가르키게 된다.
setTimeout(function(){
      this.animals.forEach(function(animal){
        animal.kind();
      })
    }.bind(this), 1000)

 

3. 클로저 이용하기

  • zoo를 가르키는 속성을 선언해주고 내부에서 사용해주는 방식이다.
    • this를 다른 변수에 담아두고 클로저를 통해 접근
function zoo(animals){
  var that = this;
  this.animals = animals;
  this.kind = function () {
    setTimeout(function(){
      that.animals.forEach(function(animal){
        animal.kind();
      })
    }, 1000)
  }
}

 

JS 사전 퀴즈 문제 5

Q) 다음 코드를 실행하면 숫자가 0부터 4까지 출력이 되지 않고 undefined가 다섯 번 출력이 된다. 그 이유는?

const numbers = [0, 1, 2, 3, 4]

for(var i = 0; i < numbers.length; i++) {
	setTimeout(function(){
    	console.log(`[${i}] number ${numbers[i]} turn!`)
    }, i * 1000)
}
클로저(closure)와 setTimeout 함수의 작동 방식 때문이다.
setTimeout 함수는 비동기적으로 동작하며, 콜백 함수가 실행되기까지 일정 시간이 소요된다.
따라서 for 루프가 반복될 동안 setTimeout 함수가 호출되고, 그 후에 콜백 함수가 실행된다.

여기서 주의해야 할 점은 setTimeout 콜백 함수 내에서 사용되는 i 변수이다.
var 키워드로 선언된 변수 i는 함수 스코프를 가지기 때문에, 콜백 함수가 실행될 때의 i 값이 아니라 반복문이 종료된 후의 i 값(즉, numbers.length인 5)이 사용된다.

이로 인해 모든 콜백 함수에서 numbers[5]를 참조하게 되고, numbers[5]는 정의되지 않았기 때문에 undefined가 출력된다.

 

해결법 1 : IIFE(즉시 실행 함수 표현)

  • i가 0, 1, 2, 3, 4일 때를 각각의 function scope로 가두어서 처리한다. (index)
    • setTimeout 실행 시점에 참고하는 index는 IIFE에서 인자로 넘긴 i의 값을 쓰기 때문에 문제 해결
const numbers = [0, 1, 2, 3, 4]

for(var i = 0; i < numbers.length; i++) {
	(function(index) {
		setTimeout(function(){
    		console.log(`[${index}] number ${numbers[index]} turn!`)
    	}, i * 1000)
    })(i)
}

 

해결법 2 : var 대신 let 쓰기 (매 루프마다 클로저 생성하게 하기)

  • let으로 선언할 경우 setTimeout 내에서 let i가 0일 때, 1일 때 각각 참조되기 때문에 정상 동작
  • IIFE로 해결한 것과 유사한 케이스 
const numbers = [0, 1, 2, 3, 4]

for(let i = 0; i < numbers.length; i++) {
	setTimeout(function(){
   		console.log(`[${index}] number ${numbers[index]} turn!`)
    }, i * 1000)
}

 

해결법 3 : for 대신 forEach 메서드 사용하기

  • forEach로 numbers를 순회하면서 각각 function을 만들기 때문에 i의 값이 고유해진다.
const numbers = [0, 1, 2, 3, 4]

numbers.forEach(function (number, i) {
	setTimeout(() => {
   		console.log(`[${i}] number ${numbers} turn!`)
    }, i * 1000)
})

 

JS 사전 퀴즈 문제 6

Q) var, let, const의 차이

var function scope
변수 재할당 가능
호이스팅 O
let  block scope
변수 재할당 가능
const block scope
변수 재할당 불가능

 

  • 호이스팅 : function scope상 맨 위로 var 선언이 끌어올라지는 현상
    • 함수 선언부 위로 끌어올려지기 때문에 값 할당 전에 호출될 수 있다.
    • 이 부분을 잘 생각하지 않으면 버그 만날 수 있음

강의 예시

 

  • block scope는 if, for, while 등 block 구문 단위로 범위를 갖는다.
  • let과 const가 hosting이 일어나지 않는 것은 아니다.
    • 스코프에 변수가 만들어지고 TDZ(Temporal Dead Zone)이 생성되지만, 코드 실행 변수 실제 위치에 도달할 때까지 액세스 할 수 없는 것 (할당되기전에 호출 시 에러남)

 

JS 사전 퀴즈 문제 6

Q) 클로저란?

  • 함수가 선언된 환경의 스코프를 기억하여 함수가 스코프 밖에서 실행될때에도 기억한 스코프에 접근할 수 있게 함
    • 은닉화 : 클로저를 이용하여 내부 변수와 함수를 숨길 수 있음
function makeFunc() {
  const name = "Mozilla";
  function displayName() {
    console.log(name);
  }
  return displayName;
}

const myFunc = makeFunc();
myFunc();

displayName() 내부 함수가 실행되기 전에 외부 함수에서 반환된다.

클로저는 함수와 함수가 선언된 어휘적 환경의 조합.

이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.

예시의 경우, myFunc makeFunc이 실행 될 때 생성된 displayName 함수의 인스턴스에 대한 참조이다. 

displayName의 인스턴스는 변수 name 이 있는 어휘적 환경에 대한 참조를 유지한다.

이런 이유로, myFunc가 호출될 때 변수 name은 사용할 수 있는 상태로 남게 되고 "Mozilla" 가 console.log 에 전달된다.