쾌락코딩

Typescript Quickstart 공부1. 변수 선언 및 기본 타입

|

타입 스트립트는 점진적 타입 검사

java와 C++은 타입의 생략이 불가능하며 실행 시간 전에 타입 검사를 하는 정적 타입 검사이다. 반면 자바스크립트는 실행 시간에 동적으로 타입 검사를 하는 동적 타입 검사를 가진다. 타입스크립트는 점진적 타입 검사를 가진다(파이썬도 그렇단다). 컴파일 시간에 타입 검사를 수행하면서 필요에 따라 타입 선언의 생략을 허용하기도 한다.

기본적인 타입

기본 타입

string, number, boolean, symbol, enum, 문자열 리터럴

symbol은 Symbol() 함수를 이용해 생성한 고유하고 수정 불가능한 데이터 타입으로 객체 속성의 식별자로 사용.

let hello = Symbol();

enum은 number의 확장된 타입. ES6에 제안. 첫 번째 Enum 요소에는 숫자 0 값이 할당.

enum WeekDay {Mon, Tue, Wed, Thu}
let day: WeekDay = WeekDay.Mon

enum은 number타입의 하위 타입으로 js로 컴파일된 후에는 객체 리터럴이나 배열처럼 객체 타입이 됨. typeof를 통해 확인해보면 object로 표시됨.

문자열 리터럴 타입은 string 타입의 확장 타입. 아래는 type 키워드를 이용해 “keyup” 문자열 또는 “mouseover”문자열만 허용하는 문자열 리터럴 타입을 정의한 것.

type EventType = "keyup" | "mouseover";

객체 타입

Arry, Tuple, Function, 생성자, Class, Interface

Array

let items: number[] = [1,2,3];

tuple

let x: [string, number];
x = ["tuple", 100];

생성자 형식

new < 타입1, 타입2> (매개변수1, 매개변수2, ...) => 타입

기타 타입

유니언

let x: string | number;

인터섹션 타입: 두 타입을 합쳐 하나로 만들 수 있는 타입 (&)

interface Cat { leg: number;}
interface Bird { wing: number;}
let birdCat: Cat & Bird = { leg: 4, wing: 2 };

void, null, undefined

변형 타입

non-nullable: null 이나 undefined를 허용하지 않는 타입. lookup(룩업) type: 인터페이스를 이용해 키값을 설정할 수 있는 타입.

타입스크립트의 내장 타입

any 타입

모든 타입의 최 상위 타입. 따라서 any 타입은 js의 모든 값을 할당받을 수 있다.

타입스크립트 2.1 버전부터는 굳이 any타입은 선언하지 않고 사용해도 되지만(알아서 any를 붙여주기 때문), 명시적으로 any 타입임을 선언하는 것이 더 명확하므로 강제하는게 좋다. 그러기 위해서는 컴파일러 옵션중 noImplicitAny를 true로 설정하면 된다.

배열 타입과 제네릭 배열 타입

유티언 타입을 이용해 선언한 배열

let myVar: (number | string | boolean)[] = [1, "hi", true];

제네릭 배열

Array 형태로 선언.

let num:Array<number> = [1,2,3];
let num2:Array<number | string> = [1, "Hello", 2];

타입을 참조할 때는 타입 쿼리를 이용한다. 타입 쿼리는 typeof 연산자를 이용해 참조할 변수의 타입을 얻어와 타입을 지정.

let num:Array<number | string> = [1, "Hello", 2];
let num2:typeof num = [1, "Hello", 2];

튜플 타입

배열 요소에 대응하는 n개에 대한 타입. 튜플에 선언된 타입 수와 배열의 요소 수가 정확히 일치돼야 한다.

let x: [string, number] = ["typle", 100];

javascript 의 == vs ===

|

javascript를 공부해 보았다면 아마 한 번 쯤은 이런말을 들어 보았을 것이다.

자바스크립트에서는 == 를 쓰면 안되고 === 로 비교해야 됨!

뭐 나름대로 코딩하면서 이 말을 잘 지켜온 것 같다. 말은 잘 들으니까! 처음 js 기본서를 보며 공부할 당시, 분명 이 글에 대한 설명이 있었을텐데 잘 이해가 가지 않았었고, 이해 안가는 것을 낑낑 끌어안고 있기에는 더 배워야 할 문법들이 산더미 처럼 쌓여 있었기 때문에 미루고 미루다 보니 지금 까지 와버렸다는, 누가 봐도 핑계인 핑계를 대며 위로해 보다가 이제야 문득 이런 생각이 들었다. 그럼 == 는 왜 만든거야? 왜 괜히 초보자 혼란스럽게? ==와 ===는 동작 방식이 어떻게 다르길래? 그래서 한번 알아본 것을 정리해 보았다.

=== a.k.a 일치연산자, 엄격한 같음

=== 는 두 단계의 일치를 확인한다. 첫 번째로 타입(형) 검사, 두 번째로 내용물 검사. 아주 쉬운 예시로 이해해보자.

(1) console.log(1 === 1); // true
(2) console.log(1 === '1'); // false

우선 피연산자들의 타입(형)을 확인한다. 두 피연산자의 타입이 서로 다르면 내용물 검사를 할 필요도 없이 탈락(false)이다. 그러나 (1)의 예시는 두 피연산자 모두 기본형 타입 Number으로 우선 1차 검사는 통과했다. 두 번째로 내용물 검사를 실시하자. 두 피연산자 모두 내용물이 1으로 동일하다. 끝! 따라서 엄격하게 1 과 1은 같은 것이다!

이번엔 (2)를 보자. 너무 쉽게 예상 가능하다. 1차 검사인 타입(형)검사에서 탈락이다. 하나는 Number이고 하나는 string 이므로 엄격하게 1과 ‘1’은 false이다.

또다른 예시를 보자.

const a = {"1":"hello world"};
const b = {"1":"hello world"}
console.log(a === b); // false;

오호라! 이번엔 기본형(number,string,boolean etc)끼리의 비교가 아닌 참조형([],{})끼리의 비교다! 어디 한번 위에서 사용 했던 방법을 그대로 적용 시켜보자(일단 틀릴거니까 오해없길 바란다) . 1단계, 타입(형) 비교니까… 둘다 [ ]이 아닌 { }니까 타입은 일치 맞겠지? 맞다 치고 2단계 내용물 검사로 넘어 가볼까? 내용물이 둘다 {“1”:”hello world”} 잖아? 그럼 결과는 true군! 그러나 예고했듯이 틀린 답이다. 왜 틀렸을까? 참조형 비교는 기본형 비교와는 조금 다른 점이 있다. === 연산은 참조형을 비교할 때 레퍼런스(주소 값)을 비교한다. 즉, 예제의 a와 b는 겉으로 볼 떄 내용물이 같아 보이긴 하지만 사실은 서로 다른 메모리의 주소를 가지고 있는(a가 변경되어도 b에 전혀 영향을 미치지 않는) 상황이기 때문에 엄격하게 서로 같지 않다는 것이다! 조금 확실하게 하기 위해 아래의 예제를 보자.

const a = {"1":"hello world"};
const b = a;
console.log(a == b); // true

이렇게 된다면 a와 b는 정확히 서로 같은 주소값을 가리키고 있다. 즉, a[“2”] = “whyJ!”; 라는 명령어를 실행한다면, b객체에도 마찬가지로 b[“2”] = “whyJ!”가 적용 된다는 것이다. 엄격하게 두 변수는 동일하다!

== 동등 연산자, 느슨한 같음

== 는 뭔가… 잘 쓰이지 않으면서 아주 조금 더 복잡한 면이 있다. 결과 부터 말하자면 == 연산자를 사용하면 내부적으로 ===가 추가되는 과정이 추가되는데, 개인적인 생각으로는 애초에 그냥 === 를 쓰면 되고 이해도 쉬우니까 == 보단 ===를 쓰라고 하는게 아닌가 하는 생각이든다.

그럼 어디서 내부적으로 ===가 추가 되는 걸까? 일단 동작 방법부터 살펴보자. ===는 1단계 에서 타입(형)검사를 하는데, 타입이 틀리면 그냥 탈락(false)이다. 그러나 ==는 기회를 한번 더 준다(만약 타입이 같다면 바로 2단계로 진행한다). 즉, 서로 비교가 가능하도록 피 연산자의 타입을 변환(number형태로)시켜버리고 그 상태에서 === 연산을 수행한다. 무슨 말인지 이해가 어렵다면 예제를 보자.

(1) console.log(1 == '1'); // true
(2) console.log(true == '1'); // true
(3) console.log('true' == '1'); // 퀴즈

(1)부터 살펴보자. 먼저 1단계, 타입이 number와 string으로 다르다. 그럼 false인가? 아니다. 기회를 한 번 더 준다. 어떻게? 서로 비교 가능하게 string을 number로 바꾼다. 그럼 오른쪽의 ‘1’은 number 타입인 1이 된다. 이제 여기서 === 연산이 실행 된다. 결국 1 === 1이고 이것은 true! 즉,

new Number(1) === new Number("1") //true

과 같다.

(2)를 살펴보자. 이건 조금 어려워 보인다. 일단 해보자! 1단계, 타입이 boolean과 string으로 다르다. 그럼 false인가? 아니다. 기회를 한 번 더 주자. 여기서 중요하다. ‘1’은 number로 바꾸면 1 이 됨을 안다. 그럼 ture는? 1 이다!

new Number(true); // 1

(다들 알고 있을거라 믿는다:D) 결국 1 === 1 연산임으로 true!

(3)를 살펴보자. 이건 조금 더 어려워 보일라나 모르겠다. 내가 이상하게 자꾸 헷갈렸던 부분이다. 너무나 쉬운데! 해보자. 1 단계, 타입이 string string으로 동일하다. 이미 서로 동일한 타입이므로 Number화 시키지 않는다. 스트링 그대로 비교하는면 되는 것이다. 2단계 === 검사를 통해 내용물을 검사하니 true와 1은 생김새가 다른 string이다. 따라서 false! 나만 낚인 문제였나..

==의 참조형([],{})은 ===와 동일하다!

결론

쉽고 깔끔하고 쿨하게 ===를 사용하자.

참고자료

비동기 프로그래밍 작성(feat.싱글스레드)

|

새로운 기술을 배우는데 급했고, React, Django, Express.js 같은 라이브러리, 프레임워크를 공부하는데 재미가 있어서 내가 좋아하는 node.js의 기본 원리를 대충 넘어간게 아닌가 싶다. node.js를 떠올리면 가장 먼저 떠오르는 비동기 프로그래밍, 싱글 스레드에 대해서 우선 적당히 알아보자!

비동기 프로그래밍

말은 많이 들어봤다. 무엇을 뜻하는지도 알고 있다. 시간이 많이 걸리는 file I/O나 network I/O 처리 요청을 운영체제에 전달하고 CPU는 다른 연산을 계속 진행하는 것. 따라서 CPU 사용 효율성이 늘어나고, 요청했던 I/O처리의 값이 돌아오면 콜백으로 값을 받아 처리하는 것이다. 자바스크립트나 Node.js로 코딩을 조금이라도 해본 사람들이라면 이런 작업들이 코드상에서 callback 함수로써 등장하게 된다는 것을 알고 있을 것이다. 많은 I/O 처리 라이브러리들의 사용법을 보면(특히 자주 쓰는 데이터베이스 라이브러리를 생각해보자), 보통 아래와 같은 식이기 때문이다.

// 코드1
// 비동기 함수 호출
// db에서 option에 해당하는 유저를 찾는 함수라고 생각하자
asyncFunc(option,(err,result) => {
  //blablabla
});

많이 봐서 익숙하다! node.js는 비동기로 처리한다고 배웠으니 위의 함수가 실행되는 동안 그 가만히 기다리지 않고 다른 연산을 수행하다가 결과가 도착하면 err나 result를 다루면 되겠구나?! 아니. 과연 이걸 훌륭하다고 말할 수 있을까? 이 정도 이해 상태라면 그저 비동기 == 콜백이 된다. 그냥 라이브러리를 가져다 쓰는데에 무리는 없겠지만 우리가 비동기 함수를 직접 만들 때 다 들통난다.

아래의 콘솔 출력 순서를 맞춰보자.

// 코드2
const func = (msg, cb) => {
  console.log(msg);
  //시간이 오래 걸리는 작업
  //예를 들어 네트워크 응답
  cb(null, 'callback function call');
};

//호출
func('hello world', (err, result) => {
  console.log(result);
});

//다른 작업
console.log('실행');

func 함수를 호출하는 방법은 코드1과 비슷하다. 함수의 첫번째 인자로 option(기타 등등)을 주고, 콜백함수의 인자로 err나 결과를 기다린다. 실행 순서는 어떻게 될까? 이 함수는 우리가 흔히 라이브러리를 쓸때 보았던 형식과 비슷하게 콜백을 사용하므로 비동기적으로 동작할까?

답은 ‘hello world’ -> ‘callback function call’ -> ‘실행’ 순서이다. 나 같은 초보는 처음 봤을 때, ‘hello world’ -> ‘실행’ -> ‘callback function call’ 라고 생각할 수 도 있다. 늘 저렇게 콜백을 받는 함수는 비동기식으로 진행되어왔고 그걸 그냥 사용만 해보았으니까!

그러나 코드2는 겉보기에만 우리가 익숙한 비동기 코드형태일 뿐 사실은 동일한 스레드 위에서 동기적으로 동작하는 함수이다. 생각해보면 당연한 일이다. 코드는 위에서 아래로 진행되고, 심지어 어디선가 node.js는 싱글스레드라는 말을 들은적이 있으니 당연한것 아닌가? 그럼 어떻게 비동기 적으로 시간이 오래 걸리는 I/O작업을 처리할 수 있을까?

우선은 한가지 오해부터 잡고 가자. node.js가 싱글 스레드이지만, 모두 같은 스레드에서 동작하는 것은 아니다. 내가 제대로 넘겨준 콜백을 처리하는 스레드는 다른 스레드라는 정도로만 알고 있자(이게 아니라면 알려주십쇼ㅠ). 그래서 다른 연산을 하는 중에도 비동기로 넘긴 코드가 실행이되는 것이다.

아무튼 이제 func 함수 내부에서 비동기적으로 콜백처리를 해보자. 키워드는 바로 process.nextTick 함수다. process.nextTick 함수는 첫 번째 인자로 함수를 넘겨주고, 이후 인자들에 파라미터를 넘겨주게 된다. 코드2에 적용시켜서 실행 순서를 ‘hello world’ -> ‘실행’ -> ‘callback function call’ 이렇게 바꿔보자.

// 코드3
const func = (msg, cb) => {
  console.log(msg);
  //시간이 오래 걸리는 작업
  //예를 들어 네트워크 응답
  process.nextTick(cb,null,'callback function call');
};
 
 //호출
func('hello world', (err, result) => {
  console.log(result);
});

//다른 작업
console.log('실행');

이제 드디어 비동기적으로 동작하는 코드가 완성되었고 실행순서는 ‘hello world’ -> ‘실행’ -> ‘callback function call’ 가 된다!

결론

  • process.nextTick 함수를 이용하면 비동기적 코드를 작성할 수 있다.

더 좋은 자료