우아한 테크러닝 3기 React & TypeScript 2회차 (2)

yujuck·2020년 9월 18일
0
post-thumbnail

new 연산자

new 연산자가 호출되면 새로운 빈 오브젝트를 생성하고 this를 새롭게 생성된 객체에 바인드 시킨다.
새롭게 생성된 this는 constructor(생성자) 역할을 한다.

// 생성자 함수
function foo() {
	this.age = 10;
}

const y = new foo();  // return 값이 명시적으로 없어도 this의 값이 리턴됨

console.log(y);

foo 함수는 생성자 함수가 된다.
y는 new 연산자로 호출된 foo()함수를 받고 있는데 이 한 줄의 코드는

  1. 새로운 빈 오브젝트 생성 (y 오브젝트)
  2. thisy 오브젝트에 바인딩 됨. 따라서 this를 참조하면 y 오브젝트가 참조됨
  3. proto가 추가됨
  4. 새로운 y가 리턴되어 y변수에 할당

(출처: 자바스크립트 개발자라면 알아야 할 33가지 개념 #16 자바스크립트 : 'new' 연산자)
이런 과정을 거치게 되고 y는 foo의 instance 객체가 된다.
y를 출력하면 proto에 constuctor가 있는 것을 볼 수 있다.
해당 변수가 instance객체인지는 instanceof 를 통해 확인할 수 있다.

if (y instanceof foo) {} // true

생성자 함수는 다른 함수와 구분하기 위해 보통 이름을 대문자로 시작한다.
생성자 함수를 인스턴스 생성에만 사용하도록 강제할 수는 없다. (함수는 new 연산 없이도 호출이 가능하기 때문)


클래스

ES6 이후부터는 클래스 문법을 제공하고 있다.

class bar {
  constructor() {
    this.name = 'yujuck';
  }
}

console.log(new bar());  // {name: 'yujuck', construtor: object}

new 연산자보다 클래스 문법이 좀 더 명시적으로 생성자를 확인할 수 있다.
클래스는 new 없이는 호출할 수 없기 때문에 생성자 함수보다 사용이 명확하다.


실행 컨텍스트

실행 컨텍스트(Execution Context)는 스코프, 호이스팅, this 등의 동작원리를 이해하기 위해서는 알고 있어야 하는 자바스크립트의 핵심원리라고 할 수 있다..!

실행 컨텍스트: 코드가 실행되는 범위

const person = {
  	name: 'yujuck',
  	getName() {
      	return this.name;
    }
}

console.log(person.getName());  // 이때 this는 person

위의 예시에서, person의 getName()this.name을 반환하고 있다.
밑에 person.getName()을 출력하면 'yujuck'이 출력되는 것을 볼 수 있다.


this

실행컨텍스트에 의해 this가 결정되는데, 간단히 생각하면 코드가 실행되는 맥락에서의 소유자(호출하는 순간의 소유자)가 this가 된다.
문제는 그 소유자가 확인이 안되는 순간이 생긴다는 것.(예외 상황)

// person의 getName이라고 생각하기보다 (이렇게 하면 person이 this가 될 것 같으니까)
// getName은 `값`이고 함수(값)를 man에 대입시키고 있다고 생각하면 더 쉬울 것 같다.
const man = person.getName;  

console.log(man());  // error

person.getName()일 때는 제대로 출력이 되었는데 man()으로 호출했을 때 오류가 나는 이유는?
첫번째 상황에서 this는 person.getName()으로 호출되었기 때문에 person이 this로 지정되면서 'yujuck'이라는 값이 출력되었지만,
두번째 상황에서 this는 man()으로 호출되었기 때문에 전역객체(window객체)가 this가 되어버리고, 전역에 name이라는 변수가 없기 때문에 에러가 나게 된다.


위와 같은 상황에서 this가 person으로 고정되었으면 좋겠다!할 때 사용하는 것이 bind, apply, call이다.

const man = person.getName.bind(person);

console.log(man());

이렇게 사용하면 man()처럼 소유자가 없이 호출이 되어도 person을 bind했기 때문에 thisperson으로 지정되어 있다.


** 요즘은 bind, call, apply를 잘 안쓰기는 하는데 기본 자바스크립트 지식으로 알고 있는 것이 좋다고 한다.



화살표 함수에서의 this

화살표 함수는 this에 객체가 정적으로 바인딩된다. 화살표 함수의 this는 항상 상위 scope의 this를 가리키는데, 이를 Lexical this라고 한다.

const person = {
	name: 'yujuck',
  	getName: () => { return this.name; }
}

const man = person.getName;

console.log(man());  // 'yujuck'

man()으로 호출을 해도 man이 가지고 있는 getName()에서의 this는 상위 스코프인 person이기 때문에 person의 name인 'yujuck'을 반환하게 된다.

클로저 (Closure)

function foo(x) {
	return function() {  // 클로저 함수. 값을 보호하고 싶을 때 많이 사용함
    	return x;  // 인자로 받은 x가 계속 유지됨
    }
}

const f = foo(10);
console.log(f())  // 10 출력됨

변수 f에 리턴되는 것은 function() { return x;}이다.
일반적인 생각으로 보면 x가 정의되어있지 않기 때문에 에러가 날 수 있지만 자바스크립트는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성하기 때문에 에러가 생기지 않는다.

MDN에서 클로저를 다음과 같이 정의하고 있다.

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

위의 코드에서 foo 함수가 리턴하는 함수가 클로저 함수가 되는데 이 함수가 리턴하는 x는 인자로 받은 x가 계속 유지되어 반환된다.
따라서 f()를 출력했을 때 10이라는 값이 출력되는 것이다.


const person = {
    age: 10
}
person.age = 500;

위의 코드에서는 person.age = 500;이라는 코드로 인해 person 객체 내 age값이 변경되어버린다.
외부에서 객체 내의 값이 변경할 수 없도록 막고 싶은 경우 클로저를 이용하면 된다.

function makePerson() {
    let age = 10;
    return {
        getAge() {  // age 값을 보존해서 가지고 있음.
            return age;
        },
        setAge(x) {
            age = x > 1 && x < 130 ? x : age;
        }
    }
}

let p = makePerson();
console.log(p.getAge()); // 10

p.setAge(200);
console.log(p.getAge());  //10

p.setAge(50);
console.log(p.getAge());  // 50

위와 같이 작성하면 age값을 보호할 수 있다.
변수 p에는 makePerson이 리턴해주는 getAge()setAge(x)를 가지는데, 이 두 함수에서 쓰고 있는 age는 makePerson 스코프 안에 있는 age이다.
외부에서 makePerson 내 age에 직접적으로 접근할 방법은 없어진다.


** (채팅방 tip) 클로저는 구체적으로 쓰로틀링, 디바운스, 커링함수 등에 자주쓰여요 해당 키워드로 검색하시면 클로저의 쓸모에 관해 이해할 수 있다고 한다.


비동기 함수

비동기 처리: 특정 로직의 실행을 기다리지 않고 나머지 코드를 먼저 실행하는 것

setTimeout이나 API 호출의 경우 비동기적으로 실행된다.

setTimeout(function (x) {
    console.log('hello');
    setTimeout(function(y) {
        console.log('안녕');
    }, 2000);
}, 1000);  

위의 코드 형태로 비동기 처리를 위해 콜백함수를 많이 쓰다보면 콜백 지옥을 만날 수 있다.
콜백 지옥을 해결하기 위해 비동기 처리에 많이 사용되는 함수는 PromiseAsync

Promise

promise 타입의 결과값이 리턴되면 이 결과값을 가지고 다음 작업을 수행할 수 있음

const p1 = new Promise((resolve, reject) => {    
	resolve();
  	reject();
});
  
p1.then((res) => console.log(res))
  .catch((err) => console.log(err));  

new Promise()로 객체를 생성하면 콜백함수의 인자로 resolve reject 를 사용할 수 있다.
resolve()가 호출되면 then으로 결과값이 넘어가고, reject()가 호출되면 catch로 넘어간다.


Async & Await

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

async function main() {
  console.log(1)
  await delay(2000)
  console.log(2)  // delay 2초 후 실행됨
}
  
main();

async await의 배경은 promise인데, promise보다는 조금 더 가독성이 좋다.

함수 앞에 async 키워드를 붙이고 비동기 처리가 필요한 코드에 await을 붙여서 사용하면 되는, 사용법은 굉장히 간단하다.
주의점은 await 뒤에는 promise 객체를 리턴해주는 코드여야 한다.
await은 promise가 성공한 결과값을 리턴하기 때문이다.


Promise, Async-Await 모두 비동기 함수.
비동기 처리 코드이지만 마치 동기 처리 코드처럼 보이는, 개발자가 좀 더 읽기 쉬운 코드처럼 보이게 해주는 함수가 비동기 함수이다.

0개의 댓글