Javascript Koans

YEN J·2022년 9월 8일
0

code states

목록 보기
9/43

호이스팅(hoisting)

호이스팅이란?
인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미함

  • var로 선언한 변수는 호이스팅 시 undefined로 변수를 초기화

  • let과 const로 선언한 변수는 호이스팅 시 변수를 초기화하지 않음

  • 선언만 호이스팅 대상

    • JavaScript는 초기화를 제외한 선언만 호이스팅
      console.log(num) // 호이스팅한 var 선언으로 undefined 출력
       var num; // 선언
      console.log(num) // 선언만 호이스팅 대상이기 때문에 에러 발생
       num = 9; // 선언 없는 초기화
    • var 키워드의 경우 undefined로 초기화되기 때문에 선언을 통해 호이스팅이 발생하여 선언 코드가 내부적으로 최상단으로 끌어올려지고 초기화된 값으로 출력됨
    • let과 const로 선언한 변수도 호이스팅 대상이지만 var 키워드와는 다르게 선언만으로는 변수를 초기화하지 않기 때문에 호이스팅의 대상이 되기 위해서는 초기화를 수행하고 코드를 작성해야 함
  • 함수 호이스팅

    • 함수 선언식 vs 함수 선언식

      함수 선언식 예시
      hoisted(); // 함수 실행
      
      function hoisted() {
        console.log('foo');
      }
      함수 표현식 예시
      hoisted(); // 에러 발생
      
      var notHoisted = function() {
        console.log('bar');
      }
    • 함수 선언식의 경우 호이스팅의 대상이 되어 함수가 정의되기 전에 호출되도 끌어올려지지만 함수 표현식의 경우 호이스팅의 대상이 아니기 때문에 함수가 정의되기 전에 호출되면 끌어올려지지 않음
      mdn-호이스팅
      mdn-함수 호이스팅

스코프(scope)

스코프란?
변수에 접근할 수 있는 범위로 이 범위는 블록이나 함수에 의해 나누어짐
스코프에서 기억해야 할 점은 전역 가장 바깥 스코프는 전역 스코프, 그 외에는 지역 스코프이며 지역 변수는 전역변수보다 높은 우선순위를 가진다는 점!

  let message = 'Outer';

  function getMessage() {
    return message;
  }  

  function shadowGlobal() {
    let message = 'Inner';
    return message;
  }

  function shadowGlobal2(message) {
    return message;
  }

  function shadowParameter(message) {
    message = 'Do not use parameters like this!';
    return message;
  }
  • getMessage 함수 호출 시 함수에 매개변수가 없기 때문에 message는 전역 변수를 가리키므로 'Outer'를 반환
  • shadowGlobal 함수 호출 시 함수에 매개변수가 없지만 함수 내부 let 키워드로 지역 변수 message가 선언 및 초기화되었기 때문에 'Inner'를 반환
  • shadowGlobal2 함수 호출 시 함수에 매개변수가 있기 때문에 만약 messege라는 매개변수에 'parameter'라는 전달인자가 들어간다면 이 함수는 'parameter'를 반환
  • shadowParameter 함수 호출시 매개변수에 어떠한 전달인자가 들어가도 함수 내부에서 전달인자가 'Do not use parameters like this!'로 대입되기 때문에 이 메세지만 출력됨(권장하지 않는 방법)

클로저(closure)

클로저란?
외부함수의 실행이 끝났지만 내부함수를 외부함수 밖에서 실행해서 외부함수에 접근할 수 있는 형태의 함수로 이 때 렉시컬 스코프를 이용함

렉시컬 스코프 - 함수가 호출된 위치가 아닌 함수가 정의된 위치에 따라 상위 스코프가 결정됨

  let age = 27;
  let name = 'jin';
  let height = 179;

  function outerFn() {
    let age = 24;
    name = 'jimin';
    let height = 178;

    function innerFn() {
      age = 26;
      let name = 'suga';
      return height;
    }

    innerFn();

    //age의 값은? --- 1
    //name의 값은? --- 2

    return innerFn;
  }

  const innerFn = outerFn();

  //age의 값은? --- 3
  //name의 값은? --- 4
  //innerFn 함수 호출 후 리턴값은? --- 5
 
  • 1에서 age의 값은 26
    • 1에서 age의 값은 외부함수인 outerFn 함수 안에서 내부함수 innerFn 함수가 호출된 후의 age 값을 묻고 있는데 내부함수의 age는 let 키워드 없이 작성하였기 때문에 외부함수 안의 age를 가리키므로 외부함수의 age의 값이 26으로 재할당되어 1에서의 age 값은 26으로 나옴
  • 2에서 name의 값은 'jimin'
    • 2에서 name은 외부함수 내의 name을 말하므로 jimin이 출력됨 이 때 내부함수 안의 name은 let 키워드로 선언 및 초기화하였기 때문에 외부함수 안의 name에는 영향을 미치지 않음
  • 3에서 age의 값은 27
    • 3에서 age는 전역변수 age를 가리키므로 27이 나옴 여기에서 외부함수는 이미 실행된 후지만 외부함수의 age는 let 키워드로 선언한 지역변수이기 때문에 전역변수 age에는 영향을 미치치 못함
  • 4에서 name의 값은 'jimin'
    • 4에서 name을 출력하기 전에 innerFn이라는 변수에 외부함수를 호출한 뒤 나오는 리턴값인 내부함수를 넣어주었기 때문에 이미 외부함수는 실행된 후라고 볼 수 있음 외부함수의 name은 전역변수 name을 가리키므로 4에서 name은 전역변수에 재할당된 값인 'jimin'이 나옴
  • innerFn 함수 호출 후 리턴값은 179
    • 변수 innerFn은 내부함수를 나타내므로 이를 실행하면 내부함수의 리턴값인 height이 나오게 되는데 이 때 내부함수 안에는 height이라는 변수가 없기 때문에 상위 스코프인 외부함수에서 height 이라는 변수에 접근하여 height의 값인 178를 가져오게 됨

Shallow copy와 Deep copy

배열이나 객체와 같은 참조 자료형의 경우 다른 변수에 이 객체를 할당하면 값 자체가 아닌 주소가 전달되어 두 객체는 같은 주소값을 가리키는데 이를 Shallow copy(얕은 복사)라고 함 반면, 주소를 공유하지 않고 값 자체를 복사해서 다른 변수에 대입하는 것을 Deep copy(깊은 복사)라고 함
그렇다면 배열이나 객체는 깊은 복사가 불가능할까?

Object.assign() 메서드를 통한 깊은 복사 가능 여부

Object.assign(target, ...sources) // 메소드 사용 방식
Object.assign() 메서드를 이용하면 객체의 속성 값을 복사할 수 있고 두 객체의 주소값은 달라짐 하지만 주의할 점은 객체가 1차원 객체일 경우만 깊은 복사가 가능하다는 점임 예를 들어 객체 안에 또 다른 객체가 요소로 들어가있는 2차원 이상의 객체일 경우 내부의 객체는 주소만을 공유함 따라서 Object.assign() 메서드를 통한 깊은 복사 가능 여부를 묻는다면 정확하게는 '아니다'라고 답할 수 있음

JSON.parse(JSON.stringify(obj)) 사용

객체의 깊은 복사와 관련하여 대표적으로 JSON.parse(JSON.stringify(obj)) 사용 방법이 알려져 있음

  • JSON.stringify()는 입력값으로 넘어온 데이터를 문자로 변형시켜주는 메소드
    • 자바스크립트의 문자열은 원시 타입이기 때문에 원시 타입의 immutable한 성질을 가져오기 위해 문자로 변형 시켜주는 메소드를 사용함
  • JSON.parse()는 변형된 문자를 다시 원래 객체로 되돌려주는 역할을 하는 메소드

한계: 자바스크립트의 일부 내장 객체나 bigint, 함수와 같이 JSON.stringify가 처리할 수 없는 객체들(즉, 문자로 바꿀 수 없는 객체들)을 만나면 이 역시 깊은 복사가 불가능함

  • 참고적으로 JSON 명세서를 보면 JSON 값으로 표현될 수 있는 종류를 object, array, number, string, true, false, null로 명시해놓았는데 해당값들만 JSON 값으로 인정하겠다는 의미로 볼 수 있음

Spread syntax를 이용한 병합

객체에 다른 객체를 참조(주소값)하는 속성이 있는 경우 원본 객체의 속성과 병합본 대상 객체가 동일한 객체를 참조하므로 스프레드 연산자를 이용한 병합 역시 shallow copy로 볼 수 있음

관련 링크1
관련 링크2
관련 링크3

참고할 만한 내용

arguments 객체

  • 함수에 전달된 인수에 해당하는 Array 형태의 객체로 유사 배열 객체에 해당함 이 객체는 모든 함수 내에서 이용가능한 지역 변수로 함수 내에서 모든 인수를 참조할 수 있으며, 호출할 때 제공한 전달인자 각각에 대한 항목을 갖고 있으며 항목의 인덱스는 0부터 시작함 인덱스는 argument 객체의 키가 되며 각 인수에 접근, 재할당이 가능함
  • 참고: arguments 객체는 배열과 형태가 유사하고 length를 통해 길이를 출력할 수도 있지만 본질은 '객체'이기 때문에 그 외의 배열의 내장 메소드는 가지지 않음
    arguments 객체

Array.from() 메소드

  • 유사 배열 객체나 반복 가능한 객체를 얕게 복사해 새로운 Array 객체를 만드는 메소드
    Array.from() 메소드

<오늘의 일기>
새로운 방식으로 도전해봤던 페어 프로그래밍. 처음에는 낯설게 느껴져 버벅거리기도 했지만 내가 부족한 점들을 페어를 통해 보완하기도 하고 내가 알고 있는 부분을 페어에게 공유하며 진행하다보니 어지럽게 흩어져 있던 여러 개념들이 조금씩 정리되는 것 같았다. 특히 마지막에 과제 제출하는 순간까지 당황하던 나에게 큰 도움을 준 페어님께 너무 감사했다.
공부하면 할수록 내가 아는 것과 모르는 것을 구분할 수 있는 것이 얼마나 중요한 일인지 깨닫게 된다. 앞으로 더 많은 것들을 배우며 모든 지식을 다 아는 사람보다 내가 무엇을 알고 모르는지 잘 구분할 수 있는 사람이 되어야겠다.(세상의 모든 지식이 다 내 머릿속에 있다면 좋겠지만 그건 현실적으로 불가능하니까..ㅎ)

0개의 댓글