Javascript 기본 탐구하기

pds·2022년 11월 18일
0

TIL

목록 보기
7/60
post-thumbnail

실습과제 바로 보기



자바스크립트의 기본적인 개념과 특성에 대해 간단하게 공부하고 정리하였다.


자바스크립트 특성


타입과 형변환

  • 자바스크립트는 변수 선언과 함께 자료형을 선언해주어야 하는 언어들과 달리 값으로부터 타입을 찾아준다.

  • 함수나 연산자에 전달되는 값을 알아서 적절한 자료형 타입으로 변환해준다.


유연해서 발생하는 문제

  • 런타임 시점에 변수 타입이 결정되기 때문에 의도치 않은 타입에러가 잦을 수 있다.

  • typescript를 사용하면 컴파일러 언어 처럼 컴파일 시점에 타입 문제를 파악하여 오류를 출력하게 해주어 타입을 어느정도 강제화 할 수 있다.


객체와 불변성

객체

string, number , boolean 등 기본 타입을 제외한 데이터들은

선언하고 할당될 때 주소라는 이 할당되고 주소에는 지정된 값이 들어가는 형태가 된다.

객체 내부를 변경한다는 것은 주소로 메모리영역에 저장된 실제 값을 가져와 그 내부를 변경한다는 것이다.

자바의 jvm처럼 javascript engine에도 Memory Heap(힙 영역), Call Stack(스택 영역) 이 존재하고

각각 참조형 데이터들과 기본형 데이터들을 처리하게 된다.

// 풀어보자

let a = 5;
let b = a;
b = 9999
a // ???

let c = {a: 5};
let d = c;
d.a = 9999
c // ???

복사

위의 객체 예제의 경우가 얕은 복사 방식이다.

가진 주소를 그대로 다시 새로운 변수에 할당하게 되는 것이다.

이렇게 될 경우 같은 주소를 가지고 있기 때문에 메모리 영역에 저장된 실제 값들을 공유하게 된다.

let a = [1,2];
let b = a; // 얕은 복사
b[0] = 9999;
console.log(a); // [9999, 2]

깊은 복사하기

특정 객체의 레퍼런스가 아닌 실제 값을 가져와 컨트롤해야 할 경우가 많다.

해당 객체에 영향을 주지 않고 새롭게 값들만 가져와 사용하고자 할 때 깊은 복사를 해야 한다.

용도에 따라 다양한 방법이 있다. 예시 몇 개를 알아보자!

const arr = [1,2,3];
const deepArr1 = [...a];
const deepArr2 = arr.map(v => v);
deepArr1[0] = 9999;
deepArr2[1] = 5555;
a; // ???

const obj = {a: 5, b: 4};
const deepObj1 = {...c, a: 9999};
c; // ???

불변성

말 그대로 변하지 않고 변하면 안되는 것을 의미한다.

불변 객체를 사용하면 해당 객체를 사용하는 쪽에서는 변하지 않음을 알고 사이드 이펙트 없이 사용할 수 있게 되고

해당 객체의 변하지 않음을 의도한 쪽에서는 누군가 사용하고 난 뒤 변경되지 않음을 확신할 수 있다는 장점이 있다.


결국 상수 키워드를 통해 선언하고 할당하게 되는데

객체형 자료의 경우 앞서 주소가 할당된다고 하였고 내부의 값은 다른 메모리 영역에 존재한다고 하였는데

이런 구조라면 상수로 선언을 해도 내부 값은 불변하지 않은(수정가능한) 상태일 것이다.


Object.freeze()

객체 내부의 속성도 불변하게 만들어 준다.

const a = Object.freeze([1,2,3])
a[0] = 9999;
a; // [1, 2, 3];

호이스팅, TDZ

TIL 첫날에도 다뤘고 너무 많이 다뤄서 3줄 요약하겠다.

자바스크립트 실행 시 변수, 함수 메모리 공간을 미리 할당하고 할당을 제외한 선언부가 스코프의 최상단으로 옮겨지는 것.

단 TDZ(Temporal Dead Zone)라는 사각지대가 존재해 선언만 올려진 const, let, 함수표현식들은 실제 할당되는 구문 해석 전까지는 거기 갇히고 해당 구문이 읽혀서 할당이 되어야 나와서 사용될 수 있다.

함수 선언문은 선언이기 때문에 호이스팅 시 스코프 최상단에 위치하게 된다.


Reference




자바스크립트의 기본적인 개념과 문법을 잘 익혔나 퀴즈와 과제를 통해 공부해보자



간단퀴즈


1.

console.log(typeof NaN);
console.log(("hi" / 2)); // NaN
console.log(Math.sqrt(-1)); // NaN

number

숫자 자료형 핸들링 시 숫자가 아닌 잘못된 결과를 얻었을 때 나오는 변수로 타입은 number


2.

console.log([] == ![]); // true
console.log([] === ![]); // false

true

빈 배열은 object형 자료이고 undefined가 아님.

따라서 ! 를 붙여 논리형으로 변경 시 false가 됨

엄격한 비교연산 시에는 boolean을 object와 비교하게 되기 때문에 false의 결과가 나옴

1 == true // true
1 === true // false

참고

!1 // false
!-1 // false
!0 // true
!function() {} // false
!{} // false
!'' // true
!NaN // true
!null // true
!undefined // true

3.

const num = 01234;
console.log(num); // 668
console.log(01888); // 1888

668

0으로 시작하는 숫자형 자료는 8진수 처리됨

단 한 자리수가 8 이상인 값을 넣을 경우 10진수로 처리됨

0x prefix의 경우 16진법을 제공함

console.log(0x17); // 23
console.log(017); // 15
console.log(17); // 17

4.

console.log("hello" + + "world”);

helloNaN

띄워쓰기에 속지말자

hello라는 문자열에 무언가 붙일 것인데 + 라는 부호를 붙인 숫자를 붙여 문자열로 만들고자 하는 것이다.

+부호를 붙인 다는 것은 숫자이기 때문에 숫자형 자료가 나오지 않아 NaN 이 출력된다.

console.log("hello" + +"1");  // hello1
console.log("hello" + -"45"); // hello-45
console.log("hello" + +55); // hello55

5.

console.log(+"111"); // 111
console.log(typeof +"111"); // number
console.log(+"111" + "HI"); // 111HI
console.log(+"111" + 5); // 116
console.log(+"111" + "5"); // 1115

6.

console.log(+[]);

0

+ 는 숫자에 필요한 연산자임을 위에서 말했는데

[] => false => 0 이라는 자동 형변환에 의해 +0 이라는 값을 만들어낼 수 있었던 것 같다.

맞나?

-[] + 5 -4; // 1
-[] + "5"; // '05'
+![] + 1 // 

7.

console.log([] + {});

"[object Object]”

아 솔직히 이건 잘 모르겠다.

빈 배열을 공백의 문자열로 변경해주고 문자열 더하기 연산을 하는 것으로 추측하고 있다.

"ㅁ" + {a: 1} + {}; // 'ㅁ[object Object][object Object]'

우선은 객체들 간의 + 연산 시 문자열 처럼 이어붙여지는 것이 아닐까 하는 것으로 이해했다.

(new Map() + {} + [] + {}); // '[object Map][object Object][object Object]'

문자열 간 이어붙이기 연산이기 때문에 빈 배열은 공백 '' 으로 치환되지 않았을까 생각한다.


8.

parseInt(null, 24);

23

이게 되는 이유?

parseInt 메소드 설명을 찾아 읽어보면 이해될 것이다.

/**
 * Converts a string to an integer.
 * @param string A string to convert into a number.
 * @param radix A value between 2 and 36 that specifies the base of the number in `string`.
 * If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal.
 * All other strings are considered decimal.
 */
declare function parseInt(string: string, radix?: number): number;

기본적으로 parseIntradix 파라미터가 없을 때 prefix 값에 맞춰 문자열을 8,10,16 진수로 변경해줌

parseInt('0xF'); // 16
parseInt('011'); // 9 => (ECMAScript5 이하만)
parseInt('12'); // 12

따라서 문제에서의 parseInt 동작은 null 이라는 문자열을 24진법 숫자로 변경시켜 주는 것이다.

또한 parseInt는 문자열이 숫자로 시작할 때 숫자가 더이상 나오지 않는 곳 까지만 숫자로 변경해준다.


valueidx
00
11
99
10A
11B
12C
13D
14E
15F
16G
17H
18I
19J
20K
21L
22M
23N

24진법은 24번째 값인 N 까지 밖에 표현을 할 수 없기 때문에 null 의 n이라는 한자리만 존재하는 것이 되고 이게 24진수로 변환되면 23이 된다.


9.

console.log(Math.min() < Math.max());

false

한줄설명

Math.min() === -Math.max(); // true

10.

console.log([,,,].join());

",,”

한줄설명

console.log([,,,].join("hell")); // hellhell


실습과제


문제 1

콘솔에 찍힐 b 값을 예상해보고, 어디에서 선언된 “b”가 몇번째 라인에서 호출한 console.log에 찍혔는지, 왜 그런지 설명해보세요.
주석을 풀어보고 오류가 난다면 왜 오류가 나는 지 설명하고 오류를 수정해보세요.**

let b = 1;

function hi () {

const a = 1;

let b = 100;

b++;

console.log(a,b);

}

//console.log(a);

console.log(b);

hi();

console.log(b);

출력
1
1 101
1

첫번째 b 콘솔 출력은 맨 위에 할당된 1을 출력한다.
두번째 b 콘솔 출력 또한 맨 위에 할당된 1을 출력한다.


왜그럴까

hi 함수에 선언된 let b은 해당 함수 스코프 내에서만 생명 주기를 가진다.

즉 함수가 호출되어 선언과 할당이 되고 함수가 종료되면 소멸된다.

이름만 같지 hi 함수 내부의 변수는 해당 지역에서만 사용할 수 있는 변수라고 생각하면 된다.

루트 스코프에 맨 위에 선언할당된 let b = 1 의 이름을 변경하여 출력해보면 쉽게 알 수 있다.


console.log(a); // Uncaught ReferenceError: a is not defined

같은 이유로 hi 내부에 존재하는 상수 a는 해당 함수가 호출되어 끝나기 전까지만 유지되기 때문에

해당 함수 외부의 루트 스코프에서는 찾을 수 없다.


문제 2

두 값이 다른 이유

1 == "1"; // true
1 === "1"; // false

== 비교 연산은 비교 대상의 타입을 추론해 암묵적으로 형변환시켜 비교를 수행하는 유연한 비교 방식이다.

=== 비교 연산은 비교 대상의 타입과 값을 모두 비교하는 엄격한 비교 방식이다.

타입과 값 모두 같아야 참의 결과가 나온다.


자동 형변환 비교는 의도하지 않은 타입에 대해서도 참의 결과가 나오기 때문에

개발자가 의도하지 않은 이펙트가 발생할 수도 있다.

type-safe 한 엄격한 비교방식을 사용하는 것이 명확하고 안전하지 않을까 생각한다.


Reference

profile
강해지고 싶은 주니어 프론트엔드 개발자

0개의 댓글