TIL: 2022-05-20-2 javascript

김하연·2022년 5월 20일
0

TIL: Today I Leaned

목록 보기
12/27

JavaScript의 특성


1. 느슨한 타입(loosely typed)의 동적(dynamic) 언어

javascript는 변수를 선언할 때 특정 타입을 지정해줄 필요가 없으며, 모든 타입의 값으로 할당이 가능하다.

var a = '12345'; // a는 Number
a = '가나다라'; // String 으로 변경됨
a = true; // Boolean 으로 변경됨
a = {}; // Object로 변경됨

변수로 선언될 수 있는 자바스크립트의 자료형은 크게 원시값/객체로 나뉘는데, 그 내용은 아래와 같다.

  • 원시값
  1. Boolean 타입
    true, false 두 가지의 값을 가지는 논리 요소
  2. Null
    의도적으로 비어있는 값. Boolean에서 null은 false를 리턴한다
  3. Undefined
    변수가 선언되었으나 값이 할당되지 않은 상태
  4. Number
    정수 및 부동소수점 숫자를 나타낸다. *, /, +, - 등의 연산 가능.
    일반적인 숫자 외에도 Infinity, -Infinity, NaN같은 '특수 숫자 값(special numeric value)'도 포함된다.
    alert( 1 / 0 ); // 어느 숫자든 0으로 나누면 무한대의 값을 얻을 수 있음
     alert( "숫자가 아님" / 2 ); // NaN, 문자열을 숫자로 나누면 오류가 발생합니다.
  5. BigInt 타입
    BigInt형은 표준으로 채택된 지 얼마 안 된 자료형으로, 길이에 상관없이 정수를 나타낼 수 있다. 정수 리터럴 끝에 n을 붙이면 만들 수 있음.
  6. String
    텍스트 데이터를 나타낼 때 사용하며, 따옴표로 묶어 표현한다.
  7. Symbol 타입
    고유하고 변경 불가능한 원시 값이며 객체의 속성 키로 사용할 수 있다.
  • 객체 (Object)
    컴퓨터 과학에서의 객체란 식별자로 참조할 수 있는 메모리 상의 값을 말하며, 자바스크립트에서 객체는 특수한 자료형이다. 객체형을 제외한 다른 자료형은 문자열이든 숫자든 한 가지만 표현할 수 있기 때문에 원시(primitive) 자료형이라 부르는 반면 객체는 데이터 컬렉션이나 복잡한 개체(entity)를 표현할 수 있다.

2. JavaScript 형변환

위와 같은 느슨한 타입(loosely typed)의 동적(dynamic)언어 라는 특성 때문에 javascript에서는 변수의 타입을 미리 설정할 필요가 없, 그로 인해 자바스크립트가 실행되면서 변수의 타입이 상황에 따라 유연하게 바뀔 수가 있다.

형변환 방식에는 프로그래머가 코드를 사용해 명시적으로 자료형을 변경하는 명시적 형변환과 연산자 사용으로 인해 자바스크립트가 실행되면서 자연적으로 일어나는 암시적 형변환이 있다.

1. 명시적 형변환

명시적 형변환은 String, Number, Boolean 타입으로 이루어진다.

  • String()
String(5/0) 					// "Infinity"
String(-3/0) 				    // "-Infinity"
String(1111)                    // "1111"
String(-1111)                   // "-31111"
String(undefined)               // "undefined"
String(null)                    // "null"
String(true)                    // "true"
String(false)                   // "false"
String(BigInt(12))              // "12"

String() 함수를 사용하면 괄호 안에 입력된 값이 그대로 문자열로 반환된다.

  • Number()
Number('')						// 0
Number(false)					// 0
Number(null)					// 0
Number('a')						// NaN
Number('11a')					// NaN
Number(undefined)				// NaN
Number('111)					// 111
Number(true)					// 1

Number() 함수를 사용하면 숫자로 형변환이 가능한데, 우선 숫자가 아닌 요소들에 대해서는 NaN의 값이 리턴된다. Not A Number, 즉 숫자가 아니라는 뜻이다. 숫자와 문자가 섞인 요소일 경우에도 NaN이 반환된다. true의 경우에는 1, false의 경우에는 0이 반환되며 ''처럼 빈 문자열이나 null의 경우에는 0을 리턴한다는 점이 흥미롭다.

  • Boolean()
Boolean('')						// false
Boolean('aaa')					// true
Boolean(123)					// true
Boolean('123')					// true
Boolean(1)						// true
Boolean('0')					// true
Boolean(0)						// false
Boolean(NaN)					// false
Boolean(null)					// false
Boolean(undefined)				// false

Boolean() 함수를 이용하여 Boolean 타입으로의 형변환이 가능하다.
undefined, null, 0, NaN, 비어있는 문자 ''를 묶어서 falsy 값이라고 지칭하며, 이 값들을 제외한 모든 나머지 값들을 truthy 값이라고 지칭한다. 그렇기 때문에 이 조건에 따라 Boolean 함수에 적용된 값들의 리턴값이 정해질 수 있다.

1. 암시적 형변환

암시적 형변환의 경우 연산을 통해 형변환이 이루어지는 것을 말한다.

  • 더하기 연산자(+)
    더하기 연산자는 숫자보다 문자열이 우선시 되기 때문에, 숫자형이 문자형을 만나면 문자형으로 변환된다.
'abc' + 123			// "abc1234"
'abc' + true 		// "문자true"
'abc' + null		// "문자null"
number + number 	// number
number + string 	// string
string + string 	// string
string + boolean 	// string
number + boolean 	// number
  • 그 외 연산자(-,/,*,%)
    더하기 연산자 외의 연산자들은 숫자형이 문자형보다 우선시된다. 반환되는 값의 경우에는 숫자가 아닐 경우 NaN의 값을 반환한다.
'abc' / 1 			// NaN
123 / '1'			// 123
abc * 1				// NaN
'abcde' % 1			// NaN
123 / false			// Infinity
string * number 	// number
string * string 	// number
number * number 	// number
string * boolean 	//number
number * boolean 	//number
  • 항등연산자(==)
    항등연산자의 경우, 두 값을 비교할때 데이터타입을 변환하지 않는 엄격한 완전 항등연산자(===)를 사용하지 않도로 주의해야 한다.
'123' == 123		// fales
1 == 0				// false
null == undefined	// true
1 == true 			// true
0 == false 			// true
  • 그 외에도 논리 연산자(&&, ||, !), 관계 연산자 ( <, <=, >, >= )를 통해서도 형변환이 가능하다.

3. 항등연산자(==)와 완전 항등연산자(===)

  • 항등연산자(==)
    항등 연산자는 두 피연산자가 같은지 확인하여 결과를 반환합니다. 항등연산자의 경우 두 피연산자의 유형을 변환하고 비교하려고 시도한다.
  • 엄격한 항등연산자(===)
    완전 항등 연산자는 두 피연산자가 같은지 확인하여 결과를 반환하는 것은 같다. 그러나 항등 연산자와는 다르게 완전 항등연산자는 피 연산자의 유형이 다를 경우 두 피연산자는 다른 것으로 간주한다.

4. undefined와 null

undefined
변수를 선언하고 값을 할당하지 않은 상태
null
변수의 값에 의도적으로 빈 값을 할당한 상태. 빈 객체이다.

undefined의 경우 값이 할당되지 않았기 때문에 typeof를 통해 자료형을 출력해보면 undefined는 undefined로, null은 object로 출력된다.

비록 undefined 자체가 빈 값을 표현할지라도, 개발을 하면서 의도적으로 값이 없음을 명시하고자 할 때에는 undefined가 아닌 null을 사용해야 한다.
의도적으로 빈 값을 할당한 경우와, 애초에 할당된 적이 없는 비어있는 값의 구분을 해야하기 때문이다. 이 내용은 아래 코드를 참고하면 명확히 이해할 수 있다.

var a;
var b = undefined;
console.log(a) // undefined
console.log(b) // undefined

a와 b는 엄연히 다르다. 변수 a는 값을 할당받은 적이 없고 변수 b에는 undefined 라는 값이 할당된 상태인데 a,b변수 모두가 같은 undefined의 값을 반환하는 것은 아주 혼란스러운 경우이기 때문에...




기본형 데이터와 참조형 데이터

javascript의 데이터는 기본형 기본형 타입(Primitive Type)과 참조형 타입(Reference Type)으로 나누어진다. 그 구분은 아래와 같다.

기본형 타입(Primitive Type)

숫자(Number)
문자열(String)
불리언(Boolean)
null
undefined
심볼(Symbol)

참조형 타입(Reference Type)

객체(Object)
배열(Array)
함수(Function)
날짜(Date)
정규표현식(RegExp)
Map
WeakMap
Set
WeakSet

1. 기본형 타입(Primitive Type)의 메모리 저장 방식

기본적으로, 메모리 할당 시 두 영역을 사용한다고 보면 된다.
1. 식별자가 할당되는 변수 영역 : 식별자와 데이터 영역의 주솟값
2. 데이터 값이 담기는 데이터 영역 : 데이터

var name = 'AAA';

위와 같이 변수를 새로 선언할 경우에 대해 설명해보자면,

[변수영역]
(주소) 이름 : string
(데이터) 값 : @5000

[데이터 영역]
(주소) 5000
(데이터) AAA

변수 영역(1002)에 식별자(변수명)를 string으로 할당하고 데이터 영역(5000)에 문자열 '홍길동'을 할당한다.
식별자 string의 변수 영역에 데이터 영역의 주솟값을 값에 연결해주고, 사용자가 식별자 string을 호출했을때, 해당 변수 영역 값에 연결된 주소(5000)에 담긴 데이터가 반환된다.

여기서 string 변수에 다시 'BBB' 라는 값을 할당할 경우, 이 때 데이터 영역에는 새로 5001이라는 주소에 BBB라는 데이터가 생성되고, 식별자 string의 변수 영역 값에 연결된 주소는 5001로 바뀌게된다.

var string = 'AAA'; 
var string2 = string;

위와 같이 string2 라는 변수에 변수를 담았을 경우에는, string2의 변수 영역이 새로 생성되지만 string2의 변수 영역의 값에 연결된 주소는 string과 같은 5000을 갖게 되고, 같은 데이터를 반환하게 된다.

위와 같이, 데이터 영역의 데이터는 한번 생성되었을 경우 수정이 안되며 새로운 데이터일 경우 비어있는 데이터 영역에 새로 할당되는 불변성을 지녔다. 그리고 새로 할당된 데이터 영역의 주솟값을 변수 영역의 데이터 주솟값으로 재할당하는 것이다.
즉, 데이터 영역의 변경이란 새로 만드는 동작에서만 이루어진다.


2.참조형 타입(Reference Type)의 메모리 저장 방식

참조형 타입의 종류는 객체, 배열, 함수, 날짜, 정규표현식, Map, WeakMap, Set, WeakSet이 있다. 일반적으로 참조형은 '참조된다'고 알려져있다.
본형 타입과 마찬가지로 참조형 타입에도 변수 영역, 데이터 영역이 존재하는데 다른 점 하나는 영역이 하나 더 존재한다. '객체의 변수(프로퍼티) 영역' 이다.

var person = { name : 'Hayley', age : 32 };
[변수영역]
(주소) 이름 : person
(데이터) 값 : @5000

[데이터 영역]
(주소) 5000				(주소) 5001				(주소) 5002
(데이터) @7000~			(데이터) Hayley			(데이터) 30

[프로퍼티 영역]
(주소) 이름: name			(주소) 이름: age
(데이터) 값: @5001		(데이터) 값: @5002

위와 같이 참조형 타입의 경우, 데이터 영역 주솟값에 데이터가 바로 할당되는 것이 아니라 객체의 프로퍼티 영역의 주솟 값(7000~7001)이 연결된다.

위에서 데이터 영역은 불변하다고 말했던 것처럼 참조형 데이터도 데이터 영역은 불변하다고 할 수 있지만, 기본형 타입은 불변성을 띄고 참조형 타입은 가변적이라고 말한다.
그 이유는 person의 name값을 'Scarlet'으로 변경할 경우, 식별자 person의 변수 영역의 값과 데이터 영역(5000)의 값은 변경되지 않는다. 대신 데이터 영역에 'Scarlet'데이터가 새롭게 저장되고, 그 데이터 영역의 주소로 객체 프로퍼티 영역의 데이터 주솟값이 변경되는데, 이같은 부분을 보고 가변성을 띈다고 하는 것이다.

출처: https://okayoon.tistory.com/entry/코어-자바스크립트-데이터-타입-기본형-타입Primitive-Type과-참조형-타입Reference-Type [Zzolab Blog :)]




호이스팅과 TDZ


1. 호이스팅이란?

자바스크립트 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미한다. var로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화하고, let과 const로 선언한 변수의 경우 호이스팅 시 변수를 초기화하지 않는다.

catName("클로이");

function catName(name) {
  console.log("제 고양이의 이름은 " + name + "입니다");
}

/*
결과: "제 고양이의 이름은 클로이입니다"
*/

위의 결과처럼, 함수 선언문이 함수를 호출하는 코드보다 아래에 있음에도 불구하고 정상적으로 출력되는 것을 볼 수 있다.


2. 호이스팅 대상

호이스팅이 적용되는 대상은 "선언"에만 해당된다는 것이 중요하다.
JavaScript는 초기화를 제외한 선언만 호이스팅한다. 변수를 먼저 사용하고 그 후에 선언 및 초기화가 나타나면, 사용하는 시점의 변수는 기본 초기화 상태(var 선언 시 undefined, 그 외에는 초기화하지 않음)와 같다.

console.log(num); // 호이스팅한 var 선언으로 인해 undefined 출력
var num; // 선언
num = 6; // 초기화

위 코드의 순서를 보면 num을 호출 > num 선언 > num 초기화 가 진행된 것을 볼 수 있는데, 호이스팅에서는 "선언"만이 대상이기 때문에 초기화 부분은 반영되지 않은 상태로 선언만 호이스팅이 되었고 그 결과는 undefined가 출력된 것이다.

console.log(num); // ReferenceError
num = 6; // 초기화

위의 경우에는 num 호출 > num 초기화 가 진행되었고, num에 대한 선언이 아예 없기 때문에 호이스팅이 발생하지 않았고 그 결과 ReferenceError 에러를 출력한다.


3. let, const 그리고 DTZ

let과 const의 경우에는 var와 다르게 호이스팅이 발생하지 않는데 그 이유는 TDZ(Temporal Dead Zone) 에서 찾을 수 있다.
TDZ에서는 변수 선언 전에 변수를 사용 및 호출하는 것을 허용하지 않는다.
그렇기 때문에 let과 const는 예외 없이 변수 선언 전에 호출할 경우 RefferenceError를 발생시키며, TDZ의 적용을 받는 구문은 const, let 뿐만이 아니라 class구문, constructor() 내부의 super(), 그리고 함수 표현식이다.

4. 함수 표현식 vs 함수 선언식의 호이스팅

DTZ의 영향을 받는 구문으로는 함수 표현식이 있다고 했는데, 함수는 함수 표현식과 함수 선언식으로 나누어 설명할 수가 있다. (참고 사이트)

  • 함수 선언식 (function declartion)
    함수명이 정의되어 있고, 별도의 할당 명령이 없는 것
function sum(a,b) {
    return a + b;
}
  • 함수 표현식 (function Expression)
    정의한 function을 별도의 변수에 할당하는 것
const sum = function(a,b) {
    return a + b;
}

함수 표현식과 함수 선언식은 호이스팅 방식에서 크게 차이가 발생한다.
함수 선언식은 함수 전체를 호이스팅 한다. 해당 스코프의 맨 위로 호이스팅되어서 함수 선언 전에 함수를 호출할 수 있다.
그러나 함수 표현식의 경우에는 함수가 별도의 변수에 할당되는데, 이 때 변수는 함수의 선언부분과 할당 부분을 나누게 된다. 호이스팅의 대상은 위에서도 말했듯이 "선언"부분이기 때문에 선언부분만 호출이 가능하고, 함수가 작성된 곳보다 먼저 변수명을 호출할 경우 에러가 발생한다.


5. Execution context

Execution context는 자바스크립트 코드가 실행되는 환경을 의미하며, 대표적으로 두 가지 타입의 Execution context가 있다.

  1. Global Execution context
    자바스크립트 엔진이 처음 코드를 실행할 때 Global Execution Context가 생성된다. 생성 과정에서 전역 객체인 Window Object (Node는 Global) 를 생성하고 this가 Window 객체를 가리키도록 한다.

  2. Function Execution context
    자바스크립트 엔진은 함수가 호출 될 때마다 호출 된 함수를 위한 Execution Context를 생성한다. 모든 함수는 호출되는 시점에 자신만의 Execution Context를 가지게 된다.


6. call stack

Call Stack은 코드가 실행되면서 생성되는 Execution Context를 저장하는 자료구조이다. 엔진이 처음 script를 실행할 때, Global Execution Context를 생성하고 이를 Call Stack에 push한다. 그 후 엔진이 함수를 호출할 때 마다 함수를 위한 Execution Context를 생성하고 이를 Call Stack에 push 한다.

자바스크립트 엔진은 Call Stack의 Top에 위치한 함수를 실행하며 함수가 종료되면 stack에서 제거(pop)하고 제어를 다음 Top에 위치한 함수로 이동한다.

아래와 같은 코드를 예로 들어보자면

function first() {
    console.log('first')
    second();
}

function second() {
    console.log('second');
}

first();

Execution Context 는 아래와 같은 흐름으로 Call Stack에 추가(push) 및 제거(pop) 된다.
Execution Context 에는 두 가지 단계가 있는데, 단계는 아래와 같다.

  1. Creation Phase (생성 단계)
  2. Execution Phase (실행 단계)

Global Creation phase(생성 단계) 에서 자바스크립트 엔진은,

  • Global Object를 생성한다.
  • this 객체를 생성한다.
  • 변수와 함수를 위한 메모리를 준비한다.
  • 변수에는 undefined를 할당하고 (var 변수 경우만) 함수 선언문은 실제로 메모리에 할당한다.

그 다음 단계인 Execution phase(실행 단계)에서 자바스크립트 엔진은 코드를 한 줄씩 실행하고 변수에 실제 값을 할당한다.

호이스팅에서 var로 선언된 변수만 호이스팅이 가능한 점도 Execution Context의 위와 같은 단계 때문이라고 볼 수 있다. 변수의 초기화(할당)은 실행 단계에서 이루어지기 때문에 호이스팅된 변수는 undefined값을 반환하게 되는 것이다.

call stack 자료 출처 사이트


7.Scope chain

Scope는 local, script, global 세가지로 이루어져 있으며, 이것을 scope chain 이라고 한다. 어디서나 접근 가능한 것이 global scope이며 자바스크립트는 그것을 window에 저장한다. 그렇기 때문에 예를들어 global scope에 들어있는 alert('!')명령어를 window.alert('!') 와 가은 방식으로 실행시켜도 똑같이 작동한다.

var, let, const와 그리고 선언 없이 할당만 된 a = 'a'와 같은 형태들이 각 scope별로 어떻게 작동하는지 설명하자면 아래와 같다.

어디서나 접근 가능한 global execute context에서 실행 될 때에는 선언없이 할당된 값 a=1, 그리고 var로 선언된 변수는 globalo scope에 들어가지만 let, const의 경우에는 script scope에 들어간다.

그러나 이 요소들이 함수 안에서 작동 할 때에는 조금 달라진다.
a=1처럼 선언 없이 할당된 값은 global scope로 들어가지만, 그 외에 var와 let, const의 경우에는 함수 내의 Local scope에 들어간다.
추가로 함수 안의 함수에서 선언된 모든 변수는 부모 함수에서 선언된 변수들을 그 어떤 scope에도 불러들이지 못한다.




호이스팅 예제 문제

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);

위 코드를 console에 찍어본다면, 반환되는 값은 아래와 같다.

1
1, 101
1
undefined

그 이유는, 첫번째 console.log에서 호출하는 b는 맨 위에 선언된 b가 대상이 된다. const와 let은 블럭스코프 요소이기 때문에, hi() 함수 안에 있는 b변수와 함수 밖에 있는 b변수는 서로 영향을 끼치지 않기 때문이다.
그 후에 hi() 함수가 실행되어 함수 안으로 들어가게 되고, 함수 안의 console.log는 a의 값인 1과 b의 값인 100에 +1을 한 값을 적용하여 1,101을 반환하게 된다.
그 후 함수 hi()는 종료되고, hi() 함수 다음에 있는 console.log에서 b를 호출하는데 해당 코드에 유일하게 존재하는 b라는 변수는 hi()함수 안에 들어있기 때문에 블럭스코프 요소인 const를 읽지 못하므로 undefined를 반환하게 된다.

또한 위 코드에서 주석처리 되어있는 console.log(a)를 풀게되면, a변수 역시 hi()함수 안에만 존재하고 global 영역에는 존재하지 않기 때문에 오류가 발생하게 된다.

0개의 댓글