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

송히·2023년 10월 4일
post-thumbnail

Today I Learn📖

  • 바닐라 JS 사전 학습 (강의)

this에 대한 이해

this quiz

나는 "nana"가 출력될 것이라고 생각했다. 하지만 결과는 TypeError가 발생했다.

this는 함수가 실행되는 시점에 결정되기 때문에, this는 Cat의 상위인 window를 가리키게 된다. 심지어 Cat은 return 하는 것도 없어서 undefined이다.,,
(window.name 찍으면 Cat에 넣은 name 찍힘)

원래 의도대로 "nana"를 출력하는 방법은 Cat 앞에 new 키워드를 붙이면 된다.
함수 실행시 new 키워드 붙이면 this는 새로 생긴 객체를 가리켜서 따로 return을 안 넣어도 새로운 객체명을 가리키게 되기 때문이다.

this를 이용하면 객체지향 문법을 흉내낼 수도 있고, 반복 작업에도 효율이 올라갈 것 같다.
this를 잘 이해하지 못하면 전역 윈도우 객체를 건드리게 될 수도 있으니 주의해서 사용해야겠다🥲

this 응용편


즉시 실행 함수

이러한 형태의 코드는 처음봤다. 예측이 가지 않아 답을 고를 수 없었다.,,
이는 함수를 선언함과 동시에 실행시킨 것이다.

function alphabet(alp) {
  console.log(`alphabet ${alp}`)
}
alphabet("A"); // A

(function(alp) {
  console.log(`alphabet ${alp}`)
})("A") // A

위 코드처럼 함수 선언 뒤, 실행시킬 때 적은 함수명 aplhabet 자리에 함수명 대신 함수 내용을 쓴 것이다. (ES6 모듈에 쓰이기 전에는 컴포넌트 모듈화에 쓰인 패턴)

  • 자바스크립트는에서 변수나 함수를 선언 -> 해당 변수와 함수는 선언한 곳을 감싸는 함수에 붙음
  • 별다른 함수로 변수나 함수를 묶지 않고, 그냥 선언한 뒤 끌어다 쓰는 방법 -> 변수나 함수가 window 객체에 묶임 (전역 객체가 오염될 수 있음)
const logger = (function () { // 변수 logger 값으로 즉시 실행 함수 넣음
  let logCount = 0; // logCount는 반환 안 함, 변수 안에 선언돼서 밖에서 접근 불가 (private 효과)

  function log(message) {
    console.log(message);
    logCount++;
  }

  function getLogCount() {
    return logCount;
  }

  return { // 변수 logger는 log, getLogCount를 객체로 반환
    log: log,
    getLogCount: getLogCount,
  };
})();

logger.log("Hi") // Hi
logger.log("Hello") // Hello

console.log(logger.getLogCount()) // 2 -> 앞에서 logger.log 2번 찍었음 (클로저)
console.log(logger.logCount) // undefined

위 코드를 살펴보면 변수 logger만 전역에 묶이고, 안에 선언된 변수 & 함수들은 안 묶임 (전역 오염 최소화!)

=> 즉시 실행 함수를 잘 사용하면 모듈화, private 효과 가능~


this의 참조 범위에 대한 이해 1

위 사진에서 볼 수 있듯 thisTest.whoAmI()를 실행하면 thisTest의 내용 전체(testInTest, (thisTest의)whoAmI)가 출력된다.
하지만 thisTest.testInTest.whoAmI()를 실행하면 (testInTest의)whoAmI만 출력된다.

이는 앞서 살펴봤듯 this는 바로 위만 가르키기 때문이다.

const DAY6 = {
  name: "DAY6",
  genre: "IdolBand",
  members: {
    wonpil: {
      memberName: "Kim Wonpil",
      play: function () {
        console.log(`${this.name} ${this.memberName} sings a song`);
      },
    },
  },
};

DAY6.members.wonpil.play(); // undefined Kim Wonpil sings a song

this가 가리키는 wonpil에는 name이 없기 때문에 undefined가 출력된다.
이를 해결하기 위해서는 this 대신 필요한 정보가 포함된 것의 이름을 쓰면 된다.

        console.log(`${this.name} ${this.memberName} sings a song`);
        ➡️ console.log(`${DAY6.name} ${this.memberName} sings a song`); // DAY6 Kim Wonpil sings a song

this의 참조 범위에 대한 이해 2

  • 오류 발생 원인: setTimeout의 매개변수 속 함수의 this는 RockBand의 this와 다름 (매개변수로 받은 새 function을 가리킴)
    -> 그래서 members가 없기 때문에 undefined 뜸

  • 해결 방안 1: 화살표 함수 이용

    setTimeout( () => {
      function () {
        this.members.forEach(function (members) {
          members.perform();
        })
      }, 1000)

화살표 함수의 this는 자체 스코프를 형성하지 않고, 상위 스코프를 참조하게 되어있음
-> RockBand의 this 사용 가능


  • 해결방안 2: bind 사용
    setTimeout(
      function () {
        this.members.forEach(function (members) {
          members.perform();
        });
      }.bind(this),
      1000
    );

bind는 함수 내의 this를 특정한 this로 변경함(즉, 함수 생성하는 함수임)
=> bind(this)의 this는 setTimeout 매개변수 속 this가 아님(상위 스코프의 것)
-> bind가 this를 생성(bind의 this는 밖의 this 가리킴 -> bind가 this 생성했으니 .으로 연결된 함수(매개변수)에 영향 미침 -> 매개변수 안의 this는 밖의 this와 연결됨)


  • 해결 방안 3: 클로저 사용
function RockBand(members) {
  var that = this;
  this.members = members;
  this.perform = function () {
    setTimeout(
      function () {
        that.members.forEach(function (members) {
          members.perform();
        });
      }.bind(this),
      1000
    );
  };
}

RockBand의 this을 변수 that에 넣어서 사용
=> 매개변수 밖에 있는 변수 참조 방식으로 밖의 this 사용


클로저에 대한 이해 1

실제로 setTimeout은 for문이 다 돈 후 실행됨 -> i는 for문이 끝난 후의 i값이어서 5
=> i는 setTimeout의 매개변수인 function에 속해있지 않고 그 위에 속해있기 때문

  • 해결 방법 1: IIFE (즉시 실행 함수)
const numbers = [0, 1, 2, 3, 4];

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

즉시 실행 함수로 만들어 for문 속 i의 값을 index로 가두어버리기 (새로운 scope 생성)
-> setTimeout 실행 시점에 참고하는 index는 인자로 넘겼던 그 때의 i를 사용하기 때문에 문제 없음


  • 해결 방법 2: var 대신 let 사용
for (let i = 0; i < numbers.length; i++) {
  setTimeout(function () {
    console.log(`${i} = ${numbers[i]}`);
  }, i * 1000);
}
  • var는 함수 scope 안에서 돌아가는 변수
  • let(+const)은 block scope이므로 같은 블록 안이면 다 적용 가능 (각각 참조 가능)
    -> for문 안에 있으면 다 유효하게 적용됨

  • 해결 방법 3: for 대신 forEach 사용
numbers.forEach(function (number, i) {
  setTimeout(function () {
    console.log(`${i} = ${number}`);
  }, i * 1000);
});

forEach로 numbers를 순회하며 각각의 함수를 생성해 실행 -> i의 값이 고유해짐


var, let, const

  • var로 선언된 변수 / 함수는 호이스팅 발생
  • 호이스팅: 실행시 해당되는 function의 맨 위로 var 선언이 끌어올려지는 것
    => 필요한 곳에만 사용하고 그 범위 벗어나면 아예 날리고 싶은데 그럴 수 없음 (맨 위에 선언되어 살아있기 때문에 할당된 값이 없어도 undefined로 호출 가능)
    -> 위치에 따라 변수 / 함수의 값이 달라질 수 있음)

왼쪽 코드처럼 var 선언을 따로 해도, 실제로 실행될 때는 오른쪽 코드처럼 전부 위로 올라감

  • let, const: 블록 스코프(if, for, while 등의 블록 구문 단위 범위)

let, const도 호이스팅 발생하지만 Temporary Dead Zone이라는 개념이 존재해서 발생하지 않는 것처럼 보임

  • Temporary Dead Zone: 할당하기 전에 호출하면 에러 발생하는 것
// var -> 선언될 때마다 변수의 값이 바뀜
function test() {
  var name = "aa";

  console.log(name); // aa
  console.log("i = " + i); // i = undefined

  if (true) {
    var name = "bb";
  }

  console.log(name); // bb

  for (var i = 0; i < 10; i++) {
    var name = `i = ${i}`;
  }

  console.log(name); // i = 9
}

test();

// let, const
function test() {
  const name = "aa"; // 변수의 값 고정

  console.log(name); // aa
  /* console.log("i = " + i); // 해당 줄은 i가 선언되어 있지 않아 에러 발생 (Temporary Dead Zone) */


  if (true) {
    const name = "bb"; // bb는 if문 안에서만 유효, 벗어나면 사라짐
  }

  console.log(name); // aa

  for (let i = 0; i < 10; i++) {
    const name = `i = ${i}`; // for문 안에서만 유효, 벗어나면 사라짐
  }

  console.log(name); // aa
}

test();

클로저에 대한 이해 2

function test() {
  const tmpName = "songhee";

  return function () {
    console.log(tmpName); // 해당 함수 외부의 tmpName 참조 -> 클로저
  };
}

const printName = test();

printName(); // songhee

위 코드에서 const printName = test();로 test()가 한번 실행되었으니 tmpName은 소멸해야하는데, 다음 줄에서 printName();으로 실행하면 여전히 tmpName이 출력된다.
이는 클로저가 동작했기 때문이다.

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

클로저의 활용 방안
1. private 효과 내기: 클로저를 이용하면 이전의 상태 저장 가능

function Counter() {
  let count = 0;

  function increase() { // 내부에서는 외부 변수(count) 접근 가능
    count++;
  }

  function printCount() { // 내부에서는 외부 변수(count) 접근 가능
    console.log(`count: ${count}`);
  }

  return { // 위에 선언한 함수들 반환
    increase,
    printCount,
  };
}

const counter = Counter(); // return된 함수들(increase, printCount)을 담은 객체

counter.increase(); // count+1 = 1
counter.increase(); // count+1 = 2
counter.increase(); // count+1 = 3
counter.printCount(); // count: 3

console.log(counter.count); // undefined 
// -> 외부에서는 내부 변수(count) 접근 불가능
// -> increase, printCount만 반환되고 count는 반환되는 값에 없음

😊오늘의 느낀점😊

7문제 중 이유를 정확하게 설명할 수 있는 문제는 상위 스코프를 참조하는 this문제 1개 뿐이었다.
내가 지금까지 얼마나 얼렁뚱땡이처럼 실행만 되면 그만이라는 마음으로 코드를 작성하고 있었는지 알게되었다.
반성하는 마음으로 하나하나 다시 배울 수 있었다.

profile
데브코스 프론트엔드 5기

0개의 댓글