자바스크립트 타입 변환, 함수 파트 정리 - 기초부터 완성까지, 프런트엔드 4장

khakiD·2022년 7월 6일
0
post-thumbnail

기초부터 완성까지, 프런트엔드 책 4장 [자바스크립트 기초 - 타입 변환과 함수] 파트

4.1 타입 변환


4.1.1 명시적 강제 변환

  • String(), .toString()
    • null과 undefined에 String() 호출시 문자열로 변환되지만, .toString()에는 TypeError
  • Number(), parseInt(str, radix) - parseInt는 문자열만 대상(자동으로 문자열로 변환됨)
    • Number() 함수는 숫자로 변경 불가능한 문자가 있으면 곧바로 NaN 반환
    • parseInt() 함수는 변경 불가능한 문자가 나타날 때까지 최대한 숫자로 변환하여 반환

4.1.2. 객체의 원시 타입 변환 (ToPrimitive 추상 연산)

  • 객체 → 문자열
    • toString() → 결과 값 ‘[Object object]’ → 결과가 원시 타입이면 결과를 문자열로 변환하여 반환하고, 그렇지 않다면 valueOf() 호출 → 결과 값이 원시 타입이면 결과를 문자열로 변환하여 반환하고, 그렇지 않다면 TypeError 발생
    • console.log(String({})); // '[Object object]'
  • 객체 → 숫자
    • valueOf() → 결과 값으로 객체를 그대로 반환 → 결과가 원시 타입이면 결과를 문자열로 변환하여 반환, 그렇지 않다면 toString() 메소드 호출 → 결과 값이 원시 타입이면 결과를 문자열로 변환하여 반환하고, 그렇지 않다면 TypeError 발생

4.1.3. 암시적 강제 변환

  • 디폴트 값 정하기 (논리 연산자 활용)
    function setDefault(a){ return a || 'default string';}
  • 피연산자가 truthy 값인 경우에만 함수 실행하기
  • 주로 리액트에서 컴포넌트를 조건에 따라 렌더링할 때 자주 사용하는 표현식
    const a = 'javascript';
    function doSome(){ // ...\ }
    a && doSome();
  • falsy 값이 아닌 null, undefined일 때만 디폴트 값 정하기 (nullish : 널 병합 연산자)
    const a ='';
    // a가 null, undefined 인 경우에만 'default' 문자열이 b의 값으로 할당
    const b = a ?? 'default';

4.2 함수

4.2.1. 함수란 무엇인가?

  • 함수는 객체의 특별한 형태이며 문(statement)으로 구성된 몸체를 가진 실행 단위
  • JS) 일급 함수(first-class function)로서 다른 함수의 매개변수나 반환 값으로 사용 가능
  • 다른 함수의 인자로 넘어가는 함수를 콜백 함수라고 부름 (ex. addEventListener)

4.2.2. 함수의 정의 방법

  1. 함수 선언문

    function myFunction(a, b) { return a * b; }
    • 반드시 이름이 정의되어야 하며, 선언문이 실행되면 같은 이름의 객체 변수가 할당됨
    • 호이스팅(hoisting)되는 것이 특징
  2. 함수 표현식

    const myFunction = function(a, b) { return a * b; }
    • 함수 이름이 선택 사항이고, 변수에 함수를 직접 할당하는 방식
    • myFunction은 함수의 이름이 아니라 변수라는 것을 명심
    • 이름이 없는 함수를 익명 함수 표현식이라 하며, 호출 시 함수를 할당한 변수를 사용하여 호출한다. console.log(myFunction(2, 3); // 6 ←이런식으로
      const myFunction = function doSome(a, b){ return a * b; }
      console.log(myFunction(2,3)); // 6
      console.log(doSome(2, 3)); // Uncaught ReferenceError: doSome is not defined
    • ❗이름이 포함된 함수 표현식을 기명 함수 표현식이라 하며, 외부 접근 불가!
    • 기명 함수 표현식은 주로 재귀적 호출에 사용된다.
      const factorial = function doSome(n){ return n<=1 ? 1 : n*doSome(n-1); }
      console.log(factorial(5)); // 120
    • ❗함수 표현식은 함수 선언문과 달리 호이스팅되지 않기에 변수에 함수를 할당하기 전에는 호출할 수 없다는 것을 명심

4.2.3. 함수의 호출

  • 함수 호출은 표현식이기에 ‘값’으로 평가되며, return문이 없다면 undefined가 된다.
  • 병신같은 자바스크립트는 매개변수 타입도 명시하지 않고, 인자 값도 검사하지 않으며, 인자의 개수도 검사하지 않는다. 정의된 매개변수보다 적은 수의 인자가 전달되면 나머지 매개 변수는 undefined 값으로 설정되고, 많이 전달되면 나머지 인자가 무시된다.
  • 인자는 함수 호출 시 전달되는 값이고, 매개변수는 함수에서 전달된 인자를 받아들이는 변수를 의미한다. 인자는 argument, 매개변수는 parameter
  • 매개변수에 해당 인자를 넘기지 않았을 때, undefined가 아닌 기본값을 할당하고 싶다면
    function greeting(name = 'Shin'){ return `Hello ${name}`; }
  • 모든 함수(화살표 함수 제외)에서는 arguments(인자)라는 객체를 사용할 수 있다.
    function sum(x, y, z) {
    	console.log(arguments[0]);
    	console.log(arguments[1]);
    	console.log(arguments[2]);
    	return x+y+z;
    } sum(1,2) // 1\2\undefined
    • arguments는 유사 배열 객체이므로 인덱스로 프로퍼티에 접근할 수 있다.

    • length 프로퍼티도 가짐

    • ES6부터 나머지 매개변수('…')로 대체할 수 있다. 나머지 매개변수는 유사 배열 객체가 아니라 진짜 배열이기에 인자들을 배열로 다룰 수 있다.

      function sum(...args){ // args는 배열이라 forEach() 사용가능
      	args.ForEach(function (arg)){ // ... } );
      }
      sum(1, 2);

4.2.4. 화살표 함수

  • 화살표 함수는 arguments와 this를 바인딩하지 않는다.
  • 화살표 함수에서는 나머지 매개변수(…)을 사용하여 arguments 객체를 대체한다.

바인딩? 일반적으로 변수와 변수에 관련된 프로퍼티를 연관시키고 값을 설정하는 것을 바인딩이라고 한다. JS에서는 함수 호출 시 값이 결정되는 arguments 객체나 this에 바인딩된다는 표현을 자주 사용한다.

// 화살표 함수의 나머지 매개변수 사용
const func = (...args) => args[0];
func(1); // 1

4.2.5. this

  • 함수에는 this라는 특별한 키워드를 사용할 수 있다. this는 읽기 전용 값으로 런타임 시 설정할 수 없으며 함수를 호출한 방법에 의해 값이 달라진다.
  • 일반 함수(함수 선언문 또는 함수 표현식으로 정의한 함수)
    • 전역 객체(JS 실행 환경(ex. 브라우저→window 객체, node.js 환경 → global 객체)) 컨텍스트에서 this는 항상 전역 객체를 참조한다. console.log(this === window);
    • 엄격 모드(’use strict’)로 전역 객체를 참조하지 않고 undefined로 남도록 할 수 있다.
      function func() {
      	'use strict';
      	console.log(this === window); // false
      	console.log(this === undefined); // true
      } func();
  • 생성자 함수 (new 키워드 사용)
    function Vehicle(type) { this.type = type; }
    const car = new Vehicle('Car');
    • Vehicle() 생성자 함수를 호출하여 객체를 생성하였다.
    • 1.객체 생성하여 this에 바인딩 → 2.프로퍼티 생성 → 3.객체 반환 순으로 동작
    1. 생성자 함수 내의 코드를 실행하기 전에 객체를 만들어 this에 바인딩한다. 생성된 객체는 생성자 함수의 prototype 프로퍼티에 해당하는 객체를 프로토타입으로 설정한다.

    2. this에 바인딩한 객체에 프로퍼티를 생성한다. (this.type = type; 부분)

    3. 생성된 객체, this에 바인딩한 객체를 반환한다. 반환 값을 따로 명시하지 않아도 this에 바인딩된 객체가 반환되지만, this가 아닌 다른 반환 값을 명시적으로 지정할 수 있다.

      function Vehicle(type) {
      	this.type = type;
      	return this; // 이 부분을 생략하여도 this에 바인딩한 객체(type)가 반환된다.
      }
    • 이 과정은 반드시 new 키워드와 함께 생성자 함수를 호출한 경우에만 실행된다.
  • 메소드
    • 객체의 프로퍼티인 함수를 일반 함수와 구분하여 메소드라고 부르며, this 바인딩도 일반 함수와는 다르게 동작한다. 메소드 호출 시 this는 해당 메소드를 소유하는 객체로 바인딩된다.
      const obj = {
      	lnag: 'javascript',
      	greeting() { // obj 객체의 프로퍼티인 함수 greeting() -> 메소드
      		// this가 obj 객체로 바인딩된다.
      		console.log(this);
      		return `hello ${this.lang}`;
      	}
      }
      console.log(obj.greeting()); // 'hello javascript'
    • ❗이 때, 메소드를 어떻게 호출했냐에 따라 this 바인딩이 달라진다는 것을 명심
      const obj = {
      	lnag: 'javascript',
      	greeting() {
      		console.log(this);
      		return `hello ${this.lang}`;
      	}
      }
      const greeting = obj.greeting;
      console.log(greeting()); // 'hello undefined'
      • 일반 함수를 호출했을 때와 동일하게 함수의 컨텍스트가 어디에 속하는지 알 수 없다. 이 경우 this가 전역 객체를 참조하거나 엄격 모드인 경우 undefined로 남는다. 메소드를 의도한대로 사용하기 위해서는 반드시 해당 객체의 컨텍스트(obj)로 명확하게 지정하여 호출해야 한다.
  • this 바인딩 객체 변경하기 (call(), apply(), bind())
    • call(), apply(), bind() 메소드를 이용하여 this로 바인딩될 객체를 변경할 수 있다.
    • 이러한 방법을 명시적 바인딩이라고 부른다.
    • call(), apply()
      • 어떤 함수를 다른 객체의 메소드처럼 호출할 수 있게 한다.

      • this를 특정 객체에 바인딩하여 함수를 호출하는 역할을 한다.

        // call()
        const obj = { name: 'javascript' };
        function greeting() {
        	return `Hello ${this.name}`;
        }
        console.log(greeting.call(obj)); // 'Hello javascript'
        // call() 메소드의 첫 인자 이후 모든 인자는 **호출하는 함수**로 전달된다.
        const obj = { name: 'Shin' };
        function getUserInfo(age, country) {
        	return `name: ${this.name}, age: ${age}, country: ${country}`;
        }
        console.log(getUserinfo.call(obj, 20, 'Korea'));
        // 'name: Shin, age: 20, country: Korea'
      • apply()는 call()과 같은 기능을 하지만, 호출되는 함수에 전달할 인자를 배열로 전달해야 한다는 차이점이 있다.

        // apply()
        const obj = { name: 'Shin' };
        function getUserInfo(age, country) {
        	return `name: ${this.name}, age: ${age}, country: ${country}`;
        }
        console.log(getUserinfo.call(obj, [20, 'Korea']));
        // 'name: Shin, age: 20, country: Korea'
    • bind()
      • bind() 메소드는 함수의 this 바인딩을 영구적으로 변경한다. (생성자 함수는 예외)
      • bind() 메소드로 this가 변경된 함수는 call(), apply() 또는 다른 bind() 메소드가 기능하지 않는다.
      • this를 바인딩하여 함수를 호출하는 것이 아니라 새로운 함수를 반환한다.
      • 함수가 어디서 어떻게 호출되는지에 관계없이 this의 값을 고정하고 싶을 때 사용
        **// bind()**
        const obj1 = { name: 'Lee' };
        const obj2 = { name: 'Shin' };
        
        function getUserInfo(age, country) {
            return `name: ${this.name}, age: ${age}, contry: ${country}`;
        }
        
        const bound = getUserInfo.bind(obj1);
        
        console.log(bound(20, 'Korea')); // 'name: Lee, age: 20, country: Korea'
        console.log(bound.apply(obj2, [20, 'Korea'])); // 위와 동일!
        
        **//** **함수에 전달시킬 인자를 고정할 수도 있다.**
        const fixedBound = getUserInfo.bind(obj1, 20);
        console.log(fixedBound('Korea')); // 'name: Lee, age: 20, country: Korea'
    • 화살표 함수와 렉시컬 this
      • 화살표 함수는 this를 바인딩하지 않는다. → 기존 this 바인딩 규칙과 완전히 다르게 동작
      • **렉시컬 this**란?
        함수 호출에 따라 동적으로 this를 바인딩하는 것이 아니라 함수를 어디에 선언하는지에 따라 this의 값이 결정된다. 화살표 함수를 둘러싸고 있는 렉시컬 스코프에서 this의 값을 받아 사용한다. 이 값은 변경되지 않는다.
        > 렉시컬 스코프는 자바스크립트 엔진이 변수를 찾는 검색 방식에 대한 규칙이며, 함수를 어디에 선언했는지에 따라 결정된다.
        > 
        
        ```jsx
        const obj = {
        	lang: 'javascript',
        	greeting: () -> {
        		return `hello ${this.lang}`;
        	}
        }
        console.log(obj.greeting()); // **'hello undefined'**
        ```
        
        - 화살표 함수의 렉시컬 this가 obj가 아닌 obj를 둘러싸고 있는 전역 컨텍스트에서 값을 받아오기 때문에 위와 같은 결과가 나온다. 전역 객체를 this로 가리키고 있다.
      • 화살표 함수의 특징
        • 변경되지 않는 렉시컬 this를 가지며, 이는 call(), apply(), bind() 함수로도 변경불가.
        • 화살표 함수는 생성자 함수로도 사용할 수 없다. (렉시컬 this의 특징때문에)
        • 정적인 렉시컬 this를 사용하기에 기존의 동적인 this 바인딩의 혼잡함에서 벗어나 단순하게 사용할 수 있다. 특히 setTimeout()과 같은 전역 객체의 함수를 메서드에서 호출할 경우 화살표 함수를 사용하면 더욱 명확하고 간결하게 표현할 수 있다.
          // setTimeout 사용 예제
          const obj = {
          	lang: 'javascript',
          	greeting() {
          		setTimeout((function timer() {
          			console.log(this.name);
          		}).bind(this), 1000);
          	}
          }
          // 화살표 함수로 setTimeout 함수 사용
          const obj = {
          	lang: 'javascript',
          	greeting() {
          		setTimeout(() => {
          			console.log(this.name);
          		}, 1000);
          	}
          } // 더욱 간결하게 변경할 수 있다.
          • 자신을 둘러싼 렉시컬 스코프인 객체 obj를 렉시컬 this로 참조하기에 bind() 메소드를 사용하지 않아도 this를 obj 객체로 바인딩할 수 있다.
        • DOM에 이벤트를 추가하는 addEventListener() 함수에서는 화살표 함수를 주의해서 사용해야 한다.
          block.addEventListener('click', function() {
          	// this는 block 객체에 바인딩된다.
          	console.log(this.id);
          });
          block.addEventListener('click', () => {
          	// this는 전역 객체에 바인딩된다.
          	console.log(this.id);
          });
          • 일반 함수를 콜백으로 넘기면 this는 event.currentTarget 프로퍼티와 동일한 값을 가진다. 하지만 화살표 함수를 사용하면 렉시컬 this로 동작이 완전히 달라지기에 this를 통해 요소에 접근할 수 없다!

정리하기

  • 자바스크립트의 명시적, 암시적 타입 변환은 매우 중요하다.
  • 암시적 타입 변환은 정해진 명세를 기준으로 동작하나 가독성 및 팀 컨벤션을 위해 타입 변환에 대한 명확한 규칙을 정하는 것이 좋다.
  • this 바인딩은 많은 개발자가 헷갈리는 부분이지만 한 가지만 명확하게 이해하면 된다.

    this 바인딩은 함수 호출에 따른 컨텍스트에 따라 달라지며 call(), apply(), bind()와 같은 메소드를 사용하여 변경할 수 있다. 하지만 ES6부터 등장한 화살표 함수는 렉시컬 this를 가지기에 생성자 함수로 사용할 수 없으며, this 바인딩을 변경할 수 없다.

profile
(이해 못했음) (개인 블로그로 이전)

0개의 댓글