[Day 22] 퀴즈(array to object) + 값 더 알아보기 + 참조 + 불변성 + 래퍼 객체 + 함수 더 알아보기

퀴즈(array to object)

  • 아래와 같이 cart 라는 배열 안에 items와 price를 객체 배열로 변형하고 싶다.

    const items = ['toy', 'bread']
    const prices = [10000, 3000]
      
    const cart = ______
    // cart: [ {name: 'toy', price: 10000}, {name: 'bread', price: 3000} ]
      
    console.log(cart)
      
    // 답
    const cart = items.map((item, index) => ({name: item, price: prices[index]}))
    
  • 9명의 개발자들로부터 좋아하는 프레임워크가 무엇인지를 조사해본 결과를 배열 preferredFrameworks 에 담았다. 각 프레임워크 별 득표수를 알고 싶다. (reduce 메소드 활용)

    const preferredFrameworks = ['vue', 'react', 'react', 'react', 'angular', 'react', 'angular', 'vue', 'vanilaJS']
      
    const result = // 여기에 들어갈 적절한 코드를 만들어 주세요.
            
    console.log(result)
    // {vue: 2, react: 4, angular: 2, vanilaJS: 1}
      
    // 답
    const result = preferredFrameworks.reduce((acc, item, index, array) => {
      acc[array[index]] = ++acc[array[index]] || 1
      return acc
    }, {})
    // 또는
    const result = preferredFrameworks.reduce((acc, item) => {
      if(!(acc[item])){
        acc[item] = 1
      } else {
        acc[item] += 1
      }
      return acc;
    }, {})
    

값 더 알아보기

  • let, const 변수와 블록 스코프

    • letconst는 같은 이름을 갖는 변수의 재선언을 허용하지 않는다.
    • 변수가 선언되기 전에 참조하려고 하면 에러가 난다.(hoisting X)
    • letconst는 블록 스코프를 갖는다.
      • 함수의 매개변수나 var 변수는 함수 스코프를 갖는다.
    • if, for, while, function 등의 구문을 사용하면 블록이 형성되어, 그 안에서 let 또는 const를 통해 선언된 변수는 외부에서 접근할 수 없다.
    • 같은 이름의 let 변수를 재선언하기 위해 특별한 기능이 없는 {} 블록을 만들 수 있다.
      const let var
    스코프 블록 스코프 블록 스코프 함수 스코프
    재대입 X O O
    재선언 X X O
    호이스팅 X X O
    사용 권장 1순위 2순위 3순위
  • 전역 변수 (Global Variable)

    • 전역 스코프는 스코프 체인의 가장 바깥쪽에 있는 스코프를 말한다.

    • 전역 스코프에서 선언된 변수를 전역 변수(global variable) 라고 한다.

    • 변수를 명시적으로 전역 스코프에서 선언하지 않더라도, 한 번도 선언되지 않은 이름으로 안쪽 스코프에서 let, const, var 를 붙여주지 않고 변수를 선언하면 전역 스코프에 변수가 만들어진다.

      function func() {
        variable = 1; // `variable`이라는 변수가 선언된 적 없으므로, 전역 변수가 됩니다.
      }
          
      func();
      console.log(variable); // 1
      
    • 전역 변수는 코드의 어떤 부분에서든 아무런 제한 없이 접근하고 조작할 수 있다.

    • 전역 변수에 의존해서 프로그래밍을 하는 것은 굉장히 금기시되는 일이다.

      • 전역 변수에는 아무런 제한 없이 접근할 수 있으므로, 프로그램의 크기가 커짐에 따라 변수의 값이 어디서 어떻게 변경될지 예측하기 힘들다.
      • 전역 변수를 통해 프로그램의 너무 많은 부분이 결합(coupling) 된다. (A를 고쳤을 때 전혀 상관 없는 B의 코드가 오동작 할 수 있다.)
      • 코드가 전혀 다른 곳에 위치한 부분에 의존하게 되므로, 코드를 이해하기 어렵다.
  • 전역 객체 (Global Object)

    • 전역 변수(var)가 선언되면, 이 변수는 전역 객체의 속성이 되어 전역 객체를 통해서도 접근이 가능해진다.
      • let 변수는 전역 객체의 속성이 되지 않는다.
    구동 환경 전역 객체 이름
    웹 브라우저 window
    웹 워커 self
    Node.js global
    • 전역 객체에는 구동 환경에서 유용하게 쓸 수 있는 속성과 함수가 미리 적재되어 있다.
      • 브라우저의 fetch 함수
      • Node.js의 require 함수

참조 (Reference)

  • Javascript의 7가지 타입

    • Boolean
    • Null
    • Undefined
    • Number
    • String
    • Symbol
    • Object
  • 타입들 중 객체(Object)를 제외하고는 모두 원시 타입(primitive type)으로 불린다. 객체는 참조 타입(reference type)으로 불린다.

  • 참조(reference)란, 객체가 컴퓨터 메모리 상에서 어디에 저장되었는지를 가리키는 값을 말한다.

  • 우리가 객체라고 생각하고 다뤄왔던 값은, 실제로는 객체에 대한 참조

  • 객체의 속성에 접근하면 Javascript 엔진은 참조를 통해 메모리(heap)에 저장되어 있는 객체에 접근해서 해당 객체의 속성을 읽는다. 이를 역참조(dereference)라고 한다.

  • 함수 호출

    • 함수 호출 시에 인수가 복사되어 매개변수로 넘어갈 때, 인수로 객체를 넘긴다면 참조가 복사되어 매개변수로 넘어간다. 이를 이용해 원본 객체의 내용을 변경할 수 있다.
    • 원시타입을 인수로 넘길 때에는 값이 복사되기 때문에 원본을 변경할 수 없지만, 참조 타입을 인수로 넘길 때에는 참조가 복사되기 때문에 원본을 변경할 수 있다.
  • 객체의 같음 (Equality)

    • 객체에 대해서 비교 연산자(===) 를 사용할 경우 객체의 값이 아닌 참조를 비교한다.

      • 두 참조가 같은 객체를 가리키고 있을 경우에만 등호 연산자가 true를 반환한다.
    • 객체의 값을 통한 비교를 하고 싶다면, 직접 함수나 메소드를 만들어서 사용하는 방법이 있다.

      // 계정 관리 시스템에서는, 사용자의 '아이디'가 같다면 같은 사용자라고 볼 수 있습니다.
      function User(id) {
        this.id = id;
      }
          
      User.prototype.isEqual = function(other) {
        return this.id === other.id;
      }
          
      const john1 = new User('john');
      const john2 = new User('john');
          
      john1 === john2; // false
      john1.isEqual(john2); // true
      

불변성 (Immutability)

  • 원시 타입의 값 자체의 내용을 변경할 수 있는 방법은 없다.
  • Javascript의 원시값은 불변(immutable) 이다.
  • 변수에 저장된 원시 타입의 값을 바꾸려면, 오직 변수에 다른 값을 대입하는 방법밖에 없다.
    • 문자열을 변형하는 메소드는 모두 기존 문자열의 내용을 바꾸는 것이 아니라 새 문자열을 반환한다.
  • 객체는 가변(mutable) 이다.
    • 객체의 가변성 문제를 해결하기 위한 해결책
      • Object.freeze 를 사용해 객체를 얼려서 속성의 추가/변경/삭제를 막는다.
        • 다만, 객체 안에 있는 객체까지는 얼리지 못하기 때문에 중첩된 객체에서는 사용하기 까다롭다.
      • Immutable.js 또는 Immer.js 같은 라이브러리를 사용해서 객체를 불변인 것 처럼 다룬다.
        • 내용이 달라지면 참조 역시 달라지게 된다.
  • constimmutable 구분
    • immutable(불변성)은 ‘값 자체가 변하지 않는다’ 는 것이다.
    • const 는 재대입이 불가능하다는 것이다.
      • const로 선언된 변수에 객체를 대입하면, 새로운 값을 대입할 수는 없지만 객체의 내용은 변경할 수 있다.
      • 즉, 재대입이 불가능할지라도 가변일 수 있다.

래퍼 객체 (Wrapper Object)

  • 원시 타입의 값은 객체가 아님에도 불구하고 점 표기법을 써서 메소드를 호출하거나 속성을 읽어올 수 있는데, 이는 래퍼 객체 기능 덕분에 가능한 것이다.
  • 원시 타입의 값에 대해 속성을 읽으려고 시도하면, 그 값은 그 순간에만 객체로 변환되어 마치 객체인 것처럼 동작한다.
  • 래퍼 객체를 생성시키기 위해 사용되는 생성자들
    • String
    • Number
    • Boolean
    • Symbol

함수 더 알아보기

  • 객체로서의 함수

    • 함수는 Function 생성자로부터 생성되는 객체이다. 다만, 다른 객체들과는 다르게 호출할 수 있다(callable)는 특징이 있다.
    • 함수 객체의 두 가지 속성
      • length - 함수의 매개변수의 개수를 반환
      • name - 함수의 이름을 반환
  • 주인 없는 this

    • 생성자나 메소드가 아닌 함수에서 this 키워드를 사용한다고 해서 에러가 나지는 않는다.
    • 이 때, this 키워드는 전역객체를 가리킨다.
      • 전역 객체에 접근해서 조작하는 것은 매우 안좋은 일이다.
  • 엄격 모드 (Strict Mode)

    • 주인 없는 this와 같은 실수들을 방지하기 위한 모드
    • 엄격 모드에서는 구버전 자바스크립트의 특징으로 인해 실수하기 쉬운 몇 가지 문법에 대해 제약사항을 추가한다.
    • 엄격 모드를 활성화하려면
      • .js 파일의 가장 위에 'use strict'; - 파일 전체 엄격 모드
      • 함수의 가장 위에 'use strict'; - 해당 함수만 엄격 모드
    • ES2015 모듈을 이용해 작성된 코드는 항상 엄격 모드로 동작한다.
      • 최근의 클라이언트 측 Javascript 코드는 대부분 Babel과 Typescript 같은 트랜스파일러를 통해 ES2015 모듈 방식으로 작성된다.
  • this 바꿔치기

    • this가 우리가 원하는 값을 가리키기 만들기 위해서는 함수 객체의 bind, call, apply 메소드를 사용하면 된다.

    • bind

      • 함수 객체의 bind 메소드를 호출하면 메소드의 인수로 넘겨준 값이 this가 되는 새로운 함수를 반환한다.
      function printGrade(grade) {
        console.log(`${this.name} 님의 점수는 ${grade}점입니다.`);
      }
          
      const student = {name: 'Mary'};
      const printGradeForMary = printGrade.bind(student);
          
      printGradeForMary(100); // Mary 님의 점수는 100점입니다.
      
    • call , apply

      • 새로운 함수를 만들지 않고도 임시적으로 this를 바꿔버릴 수 있다.
      • callapply는 인수를 넘겨주는 형식에 차이가 있을 뿐, 나머지 기능은 동일하다.
      function printGrade(grade) {
        console.log(`${this.name} 님의 점수는 ${grade}점입니다.`);
      }
          
      const student = {name: 'Mary'};
          
      printGrade.call(student, 100); // Mary 님의 점수는 100점입니다.
      printGrade.apply(student, [100]); // Mary 님의 점수는 100점입니다.
      
  • argument와 나머지 매개변수 (Rest Parameters)

    • arguments는 유사 배열 객체(array-like object)이자 반복 가능한 객체(iterable object)로, 함수에 주어진 인수가 순서대로 저장되기 때문에 인덱스를 가지고 인수를 읽어오거나 for...of를 통해 순회할 수 있다.
    • 나머지 매개변수(rest parameters) 문법
    function sum(...ns) {
      let result = 0;
      for (let item of ns) {
        result += item;
      }
      return result;
    }
      
    sum(1, 2, 3, 4); // 10
    
    • 매개변수 앞에 ... 을 붙여주면 해당 매개변수에 모든 인수가 저장된다. arguments와는 달리 나머지 매개변수는 실제 배열이기 때문에, 배열의 메소드를 활용할 수 있다.
    function sum(...ns) {
      // `for...of` 루프 대신에 `reduce` 메소드를 사용해서 합계를 구할 수 있습니다.
      return ns.reduce((acc, item) => acc + item, 0);
    }
      
    sum(1, 2, 3, 4); // 10
    
    • 단, ... 문법은 마지막 매개변수에만 사용할 수 있다. (마지막이 아닌 매개변수에 사용하면 에러 발생)
    function printGrades(name, ...grades) {
      console.log(`${name} 학생의 점수는 ${grades.join(', ')} 입니다.`);
    }
      
    printGrades('Mary', 96, 78, 68); // Mary 학생의 점수는 96, 78, 68 입니다.
    
    • 자바스크립트에서는 함수의 매개변수의 개수와 인수의 개수가 달라도 에러가 나지 않는다.
      • 필요없는 매개변수를 생략할 수 있다는 장점이 있다.
  • 화살표 함수 (Arrow Function)

    • ES2015 에서 도입된 새로운 유형의 함수

    • (매개변수 목록) => {함수 내용}

    • 화살표 함수의 특별한 성질

      • 화살표 함수는 생성자로 사용될 수 없다. 따라서 prototype 속성을 갖지 않는다. 또한 스스로의 new.target을 가지지 않는다.

      • 화살표 함수는 스스로의 this, arguments, super를 가지지 않는다.

        • 함수 내부에서 this를 사용할 수 없다는 말은 아니다.

        • 화살표 함수 내부에서 this를 사용하면, 그 this는 함수가 정의된 스코프에 존재하는 this를 가리킨다.

        • 스스로의 this를 갖지 않기 때문에, 화살표 함수에 대해 bind, call, apply 메소드를 호출해도 아무런 효과가 없다.

          function Person1(name) {
            this.name = name;
            this.getName = () => {
              // 여기에서 사용된 `this`는 '함수가 정의된 스코프',
              // 즉 'Person 함수 스코프'에 존재하는 `this`를 가리키게 됩니다.
              return this.name;
            }
          }
                  
          const mary = new Person1('mary');
          console.log(
            'mary.getName():',
            mary.getName()
          )
                  
          // `this`를 바꿔보려고 해도, 아무런 효과가 없습니다.
          console.log(
            "mary.getName.call({name: 'john'}):",
            mary.getName.call({name: 'john'})
          );
          
      • 화살표 함수 내부에서 yield 키워드를 사용할 수 없다. 즉, 제너레이터로 사용될 수 없다.

      • 화살표 함수 내부에서 this를 사용하면 함수가 정의된 스코프에 있는 this를 가리킨다.

        • 화살표 함수 내부의 this화살표 함수가 정의된 문맥에 의해 결정된다.

        • function 구문으로 정의된 함수에서 쓰이는 this어떻게 호출되는지에 의해 결정된다.

          function getName() {
            return this.name;
          }
                  
          const john = {
            name: 'john',
            getName
          };
                  
          const bob = {
            name: 'bob',
            getName
          }
                  
          // .getName() 앞에 있는 john이 함수 내부의 this로 사용됩니다.
          console.log('john.getName():', john.getName())
                  
          // .getName() 앞에 있는 bob이 함수 내부의 this로 사용됩니다.
          console.log('bob.getName():', bob.getName())
          
      • 객체의 속성 값에 메소드를 직접 정의할 때에는 화살표 함수를 사용해서는 안 된다.

        • this가 전역 객체를 가리키게 된다.
      • 화살표 함수는 함수를 다른 함수의 인수로 넘겨야 할 때 사용하면 편리하다.

      function Person2(name) {
        this.name = name;
        this.getName = () => {
          return this.name;
        }
        this.getName2 = function () {
          return this.name;
        }
      }
          
      const kate = new Person2('kate');
          
      // 함수를 인수로 받는 함수
      function printResult(func) {
        // 아래 func는 '메소드로서 호출'되고 있지 않습니다.
        // 따라서 function 키워드를 통해 생성된 함수일 경우 문제가 생길 수 있습니다.
        console.log(func());
      }
          
      // 화살표 함수로 정의된 메소드를 다른 함수의 인수로 넘겨도 아무런 문제가 없습니다!
      console.log('printResult(kate.getName)')
      printResult(kate.getName);
          
      // function 키워드 함수의 경우 this에 문제가 생깁니다. '메소드로서 호출'되고 있지 않기 때문입니다.
      console.log('printResult(kate.getName2)');
      printResult(kate.getName2);
          
      // 위와 같은 경우 bind 메소드를 사용하면 됩니다만, 좋아보이지는 않습니다.
      console.log('printResult(kate.getName2.bind(kate))')
      printResult(kate.getName2.bind(kate));
      
    • function 구문으로 생성되는 함수는 단순한 함수 이외에 생성자나 제너레이터 등의 여러 기능까지 구현할 수 있다.

    • 반면, 화살표 함수는 오직 함수 혹은 메소드로 사용되도록 만들어졌다.

      • 화살표 함수의 this는 어떻게 호출되더라도 변하지 않기 때문에 함수를 값으로 다루어야 하는 경우(함수를 다른 함수의 인수로 넘겨야 하는 경우) 에는 화살표 함수가 일반 함수보다 편리한 경우가 많다.
  • 매개변수의 기본값 (Default Parameter)

    • 매개변수가 있는 함수에 아무런 인수도 주지 않으면 매개변수에 undefined가 대입된다.
    • 이를 이용해 인수가 주어지지 않았을 때, 대신 미리 설정된 값을 사용하는 함수를 만들 수 있다.
    function hello(name) {
      // 매개변수는 `var` 변수와 같은 성질을 갖기 때문에, 재대입을 할 수 있습니다.
      if (name === undefined) {
        name = 'Mary';
      }
      console.log(`Hello, ${name}!`);
    }
      
    hello('John'); // Hello, John!
    hello(); // Hello, Mary!
    hello(undefined); // Hello, Mary!
    
    • 위와 같은 기법이 많이 사용되어왔기 때문에, ES2015에서는 이를 편리하게 해주는 문법이 도입되었다. 이를 매개변수의 기본값(default parameter) 이라고 한다.
    // 매개변수의 기본값 문법
      
    // 'Mary'가 `name` 매개변수의 기본값이 됩니다.
    function hello(name = 'Mary') {
      // 코드가 훨신 더 깔끔해졌습니다!
      console.log(`Hello, ${name}!`);
    }
      
    hello('John'); // Hello, John!
    hello(); // Hello, Mary!
    hello(undefined); // Hello, Mary!
    

댓글남기기