JavaScript의 자료형과 JavaScript만의 특성은 무엇일까 ?
- 1 느슨한 타입의 동적언어
JavaScript는 느슨한 타입의 동적 언어이다. JS의 변수는 어떤 특정한 타입과 연결되지 않으며, 모든 타입으로 할당 및 재할당이 가능하다.변수의 타입을 미리 선언할 필요가 없다. 프로그램이 처리되는 과정에서 자동으로 파악되고, 이 말은 결국 같은 변수에도 불구하고 상황에 따라 값의 타입이 바뀔 수 있다는 뜻이기도 하다.
- 문제점
모든 타입으로 할당 및 재할당을 하니 혼자 프로그램을 짤 때는 문제가 없지만 대형프로젝트나 협업 프로젝트시 타입이 올바른지 체크하는 것이 까다롭기 때문에 배포시 예상치 못한 문제와 직면 할 수 있다.
- 보완점
JS 느슨한 타입의 보완 방법은 강력한 문법을 추가한 TypeScript 를 사용하여 보완 할 수 있다. (TypeScript 란? MS에서 개발한 오픈소스, 프로그래밍 언어이며, JS의 단점을 보완하기 위해 만들어졌다. Js는 동적타입 언어이기 때문에 런타임 속도는 빠르지만 안전성이 보장되지 않는다. 타입스크랩트는 정적 타입 언어이기 때문에 컴파일시 시간이 조금 걸리더라도 안정성을 보장한다는 것이 장점이다.)
- 형변환 2종류
형변환 이란 명시적일 수도 있고, 암시적일 수도 있다. 명시적 형변환(explicit coercion)은 Number("123") 처럼 프로그래머의 코드에서 암시적으로 자료형을 정해서 변환하는 과정이다. 암시적 형변환(implicit coercion)은 연산자 사용으로 인해 자연적으로 일어나는 형변환이다. 대표적인 예시로 ==, +, > 등 연산자의 사용이 있다. 예외적으로 ===는 형변환을 얘기하지 않는다.
- 명시적 형변환
Js에서의 명시적 형변환은 주로 세 가지가 있다. String() 은 문자형으로 변환, Number() 숫자형으로 변환, 마지막으로 Boolean() 이 값이 True 인지 False 변환 해준다.
1. String ()
문자열이 아닌 값을 문자열로 바꾸려면 이렇게 String 함수를 활용하면 되는데, 소괄호 안에 값을 넣어주면 그 값이 문자열로 변한다.
2. Number()
console.log(Number('')); // 0
console.log(Number('abc')); // NaN
console.log(Number('123')); // 123
console.log(Number('123a')); // NaN
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
console.log(Number({name: 'bigtop'})); // NaN
console.log(Number({})); // NaN
가장 먼저 볼 수 있는 빈 문자열은 0으로 변환된다.
그리고 '123'과 같이 따옴표를 벗겨냈을 때 온전히 숫자로 사용이 가능한 문자열은 숫자 123으로 변환된다.
그러나 '123a'와 같이 숫자와 문자가 혼합된 문자열은 NaN값으로 변환된다.
또 한 가지, boolean형태의 값은 true는 1, false는 0으로 변환된다.
비슷한 의미의 null과 undefined는 여기서 차이를 보이는데, null은 0으로 변환되지만, undefined는 NaN값으로 변환된다.
마지막으로 빈 문자열이 0으로 변환되기 때문에 빈 객체 값도 0일까 싶지만 빈 객체는 그대로 NaN값으로 변환된다.
참고로 '123a'와 같은 숫자와 문자가 혼합된 경우에도 숫자로 시작하는 문자열인 경우 parseInt 혹은 parseFloat 함수를 사용하면 숫자 형태로 변환이 가능하다.
3. Bloolean
Boolean 형태는 당연히 true 혹은 false 두 가지 값으로 변형되는데, 단순하게 생각해서 뭔가 어떤 값이 있는 것 같은 가진 값들은 true, 그 반대는 false로 변환된다.
- 암시적 형변환
1. 덧셈 연산자 ( + )
// string
console.log('문자' + 1234); // 문자1234
console.log('문자' + true); // 문자true
// number
console.log(1234 + '1234'); //12341234
console.log(1234 + true); // 1235
// boolean
console.log(true + 123); // 124
console.log(false + 123); // 123
연산을 할 때는 피연산자가 한쪽이라도 '문자열'이면 문자열 연산이 된다.
그런 원리로 string 구간을 보면, 한쪽만 문자열인 경우 반대편도 문자열 형태로 형변환을 일으킨 뒤 연산을 수행한다.
그리고 나머지 경우는 산술덧셈 연산을 수행하는데, string을 제외한 구간을 살펴보면 덧셈 연산을 할 때 양쪽 피연산자를 모두 숫자형으로 형 변환을 일으킨 뒤 연산을 수행한다.
2. 관계 연산자 ( <, <=, >, >= )
// string
console.log('a' <= 'b'); // true
console.log('a' <= 1234); // false
// number
console.log(1234 <= 1234); // true
console.log(1234 <= true); // false
// boolean
console.log(false <= true); // true
console.log(false <= false); // true
관계 연산자도 산술연산과 동일하다. 양쪽 모두 문자열인 경우를 제외하면 양쪽 모두 숫자형으로 형 변환을 일으킨 후 연산한다. 관계 연산자도 산술연산과 동일하다. 양쪽 모두 문자열인 경우를 제외하면 양쪽 모두 숫자형으로 형 변환을 일으킨 후 연산한다.
3. 논리 연산자(&&, ||, !)
// string
console.log('문자' && 1234); // 1234
console.log('문자' && true); // true
console.log('문자' || 1234); // 문자
console.log('문자' || true); // 문자
// number
console.log(1234 && 1234); // 1234
console.log(1234 && true); // true
console.log(1234 || 1234); // 1234
console.log(1234 || true); // 1234
// boolean
console.log(true && true); // true
console.log(true && false); // false
console.log(true && null); // null
console.log(true && undefined); // undefined
- undefined와 null의 미세한 차이들을 비교해보세요.
undefined 와 Null 의 차이점은 우선 undefined 은 원시값으로 선언한 후에 값을 할당하지 않은 변수나 값을 주어지지 않은 인수에 자동으로 할당된다 Null 은 원시값 중 하나로 어떤 값이 의도적으로 비어있음을 표현한다 여기서 차이가 나타난다 undefined 는 값이 지정되지 않은 경우를 의미하지만 Null 의 경우에는 해당 변수가 어떤 객체더 가리키지 않다는 것을 의미한다
- ==, === 차이점
==, === 차이점은 ==는 a == b 라고 할때, a와 b의 값이 같은지를 비교해서, 같으면 true, 다르면 false라고 한다. ===는 Strict, 즉 엄격한 Equal Operator로써, "엄격하게" 같음을 비교할 때 사용하는 연산자이다. ===는 a === b 라고 할때, 값과 값의 종류(Data Type)가 모두 같은지를 비교해서, 같으면 true, 다르면 false라고 한다.
JavaScript 객체와 불변성이란 ?
- 자바스크립트에서는 불변성을 유지하는 값들과 그렇지 않은 값들이 나누어져 있다. 불변성을 유지하는 값들을 기본형 타입이라고 부르며 메모리 내에 고정된 크기로 저장되면서, 원시 데이터 값 자체를 보관한다. 종류 Boolean, Number, String, null, undefined 가 있다 . 참조형 Object 타입들은 변경가능한 값들이다. 참조형 종류에는 Array(배열), function(함수) 가 있다
원시타입
원시 타입이란 있는 그대로 저장되는 데이터를 표현한다. 그리고 원시값을 변수에 할당하면 값이 복사되어 들어간다. 즉, 원시값이 할당된 변수들은 모두 자기 자신만의 고유한 값을 가지게 된다.
원시 타입 종류 :
- boolean : true, false
- 숫자 : 1, 2, 3...
- 문자열 : "Hello World"
- null
- undefined
참조타임
참조 타입이란 자바스크립트 객체를 나타낸다. 참조 타입은 변수에 값을 직접 저장하지 않는다. 변수에 저장되는 것은 메모리 안에서 객체의 위치를 가리키는 포인터이다. 무엇이 저장되느냐, 이것이 원시 타입과 참조 타입의 가장 큰 차이점이다.
참조 타입의 종류
원시 타입을 제외하고 전부 참조 타입으로 봐도 좋다.
- 객체 : {}
- 배열 : []
- 함수 : function
- Date
불변 객체를 만드는 방법
- 불변 객체란 '변하지 않는 객체' 즉 이미 할당한 객체가 변하지 않는다는 뜻을 가지고 있다 JS에서 불변 객체를 만들 수 있는 방법은 기본적으로 2가지 인데 const와 Object.freeze() 를 사용하는 것이다. const 키워드는 변수를 상수로 선언할 수 있다.Object.freeze() 자바스크립트에서 기본적으로 제공하는 메소드인 Object.freeze() 메소드이다. 공식 문서에서는 "객체를 동결하기 위한 메소드" 라고 적혀있다.
얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)
얕은 복사는 객체의 참조값(주소 값)을 복사하고, 깊은 복사는 객체의 실제 값을 복사한다.
자바스크립트에서 값은 원시값과 참조값 두 가지 데이터 타입의 값이 존재한다.
자바스크립트에서 원시 타입(primitive type)의 값은 새로운 메모리 공간에 독립적인 값을 저장하기 때문에 깊은 복사가 되고 참조 타입(reference type)값은 얕은 복사가 된다. 원시 타입과 참조 타입의 가장 큰 차이점은 원본이 바뀌면 참조 타입은 복사본도 같이 변경되지만, 원시 타입은 변경되지 않는다는 점이 큰 차이점이다.
// 원시 타입의 깊은 복사
let a = '원본 데이터';
let b = a;
a = '수정 데이터';
console.log(a); // '수정 데이터'
console.log(b); // '원본 데이터'
원시 타입은 복사 시 값 자체를 담은 독립적인 메모리를 생성하기 때문에 a가 재할당 되더라도 b에는 아무런 영향을 미치지 않는다.
// 참조 타입의 얕은 복사
let a = {name:'원본 데이터'};
let b = a;
a.name = '수정 데이터';
console.log(a); // '수정 데이터'
console.log(b); // '수정 데이터'
원시 타입과 달리 참조 타입인 오브젝트는 새로운 값으로 변수 값을 재할당 하자 복사된 변숫값도 같이 변경되는 것을 확인할 수 있다. 즉, 데이터가 그대로 하나 더 생성된 것이 아닌 해당 데이터의 메모리 주소를 전달하게 돼서, 결국 한 데이터를 공유하게 되는 걸 알 수 있다
호이스팅과 TDZ는 무엇일까 ?
호이스팅 (hoisting)
호이스팅은 선언한 함수와 변수를 자바스크립트가 해석할 때 가장 상단에 있는 것처럼 인식하는 것을 말한다.
자바스크립트는 코드의 라인 순서와 관계없이 함수 선언식과 변수를 위한 메모리 공간을 먼저 확보한다. 따라서 함수와 변수(var)는 코드의 최상단으로 끌어 올려진 것처럼 보이게 되는데 이를 호이스팅이라고 한다
TDZ( Temporal Dead Zone )
TDZ는 Temporal Dead Zone의 약자로 일시적인 사각지대라는 뜻이다. 이 일시적인 사각지대는 스코프(Scope)의 시작지점부터 초기화 시작지점까지의 구간을 TDZ라고 일컫는다.
자바스크립트에서 변수는 선언, 초기화, 할당 3가지의 단계를 거쳐 생성된다.
- 선언 단계(Declaration phase): 변수를 실행 컨텍스트의 변수 객체에 등록하는 단계를 의미하며 이 변수 객체는 스코프가 참조하는 대상이 된다.
- 초기화 단계(Initialization phase): 실행 컨텍스트에 존재 하는 변수 객체에 선언 단계의 변수를 위한 메모리를 만드는 단계로 이 단계에서 할당된 메모리에는 undefined로 초기화 된다.
- 할당 단계(Assignment phase): 사용자가 undefined로 초기화된 메모리의 다른 값을 할당하는 단계.
이 3가지 단계의 순서는 var와 let/const에 차이를 준다.
1) var: var키워드 변수는 변수 선언 전 선언 단계와 초기화 단계를 동시에 진행한다. 자바스크립트는 실행 컨텍스트 변수 객체의 변수를 등록하고 메모리를 undefined로 만들어 버리는데 그 때문에 변수를 선언하기 전에 호출을 해도 undefined로 호출이 되는 호이스팅이 발생하게 되는 것이다.
2) let: let으로 선언된 변수는 var 키워드와는 다르게 선언단계와 초기화 단계가 분리되어서 진행이 된다. 그렇기 때문에 실행 컨텍스트에 변수를 등록했지만, 메모리가 할당이 되질 않아 접근할 수 없어 참조 에러(ReferenceError)가 발생하게 된다.
이런 현상을 보고 '호이스팅이 되질 않는다'고 생각하게 된 것이다.
let 또한 선언 전 실행 컨텍스트 변수 객체에 등록이 되어 호이스팅이 되지만 이 TDZ 구간에 의해 메모리가 할당되지 않아 참조 에러(ReferenceError)가 발생한다.
함수 선언문과 함수 표현식에서 호이스팅 방식의 차이
함수 선언식 (function declartion)
function sum(a,b) {
return a + b;
}
함수명이 정의되어 있고, 별도의 할당 명령이 없는 것
함수 표현식 (function Expression)
const sum = function(a,b) {
return a + b;
}
정의한 function을 별도의 변수에 할당하는 것
주요 차이점은, 호이스팅에서 차이가 발생한다.
함수 선언식은 함수 전체를 호이스팅 합니다. 정의된 범위의 맨 위로 호이스팅되서 함수 선언 전에 함수를 사용할 수 있다는 것이다.
함수 표현식은 별도의 변수에 할당하게 되는데, 변수는 선언부와 할당부를 나누어 호이스팅 하게 된다. 선언부만 호이스팅하게 된다.
실행 컨텍스트와 콜 스택
자바스크립트 코드가 실행되는 환경을 의미한다.
자바스크립트에서 대표적으로 두 가지 타입의 실행 컨텍스트(Execution context)가 있다.
실행할 코드에 제공할 환경 정보들을 모아놓은 객체들로
자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념이다.
- Global Execution context
자바스크립트 엔진이 처음 코드를 실행할 때 Global Execution Context가 생성된다. 생성 과정에서 전역 객체인 Window Object (Node는 Global) 를 생성하고 this가 Window 객체를 가리키도록 한다. - Function Execution context
자바스크립트 엔진은 함수가 호출 될 때마다 호출 된 함수를 위한 실행 컨텍스트(Execution context)를 생성한다.
모든 함수는 호출되는 시점에 자신만의 실행 컨텍스트(Execution context)를 가진다.
자바스크립트는 실행 컨텍스트가 활성화되는 시점에 다음과 같은 현상이 발생한다.
- 호이스팅이 발생한다(선언된 변수를 위로 끌어올린다)
- 외부 환경 정보를 구성한다
- this 값을 설정한다.
콜 스택 (call stack)
코드가 실행되면서 생성되는 실행 컨텍스트를 저장하는 자료구조
엔진이 처음 script를 실행할 때, Global Execution Context를 생성하고 이를 Call Stack에 push한다.
그 후 엔진이 함수를 호출할 때 마다 함수를 위한 실행 컨텍스트(Execution context)를 생성하고 이를 Call Stack에 push 한다.
자바스크립트 엔진은 Call Stack의 Top에 위치한 함수를 실행하며 함수가 종료되면 stack에서 제거(pop)하고 제어를 다음 Top에 위치한 함수로 이동한다.

스코프 체인, 변수 은닉화
스코프란 ?
scope의 사전적인 의미는 범위이다. 자바스크립트에서 스코프란 작성된 코드를 둘러싼 환경으로, 어떤 변수들에 접근할 수 있는지를 정의한다. 어떤 범위 내에 속해있는지를 정의한다고 생각하면 사전적 의미와 유사하다고 할 수 있다. 스코프는 전역(global)과 지역(local) 스코프로 정의할 수 있다.
전역 스코프는 함수 안에 포함되지 않은 곳에 정의하는 것으로 코드 어디에서든지 참조할 수 있고, 지역 스코프는 함수 내에 정의된 것으로 정의된 함수 내에서만 참조할 수 있다. 이 개념은 다른 프로그래밍 언어를 하신 적이 있는 사람들 이라면 지역변수와 전역변수를 배울 때 비슷하게 들어봤을 것 이다. 하지만 자바스크립트의 스코프는 다른 언어와 다른 특징을 가지고 있는데, 바로 자바스크립트는 Function-level scope(함수 레벨 스코프)를 사용한다는 것이다. 대부분의 언어는 Block-level scope(블록 레벨 스코프)를 사용함으로써, 변수 선언이 코드 블록 단위로 유효한다. 하지만 Function-level scope인 자바스크립트는 함수 블록 내에서 선언된 변수는 함수 블록 내에서만 유효하고 함수 외부에서는 참조할 수 없다.
렉시컬 스코프란(Lexical Scope)란?
함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정되는 것을 말한다.
즉, 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정한다는 뜻이며, 가장 중요한 점은 함수의 호출이 아니라 함수의 선언에 따라 결정된다는 점이다.
다른 말로, 정적 스코프(Static scope)라 부르기도 하다.
클로저(Closure)
함수 + 함수를 둘러싼 환경(Lexical environment)
함수를 둘러싼 환경이라는 것이 바로 앞에서 설명했던 렉시컬 스코프다.
함수를 만들고 그 함수 내부의 코드가 탐색하는 스코프를 함수 생성 당시의 렉시컬 스코프로 고정하면 바로 클로저가 되는 것이다
자바스크립트의 클로저
자바스크립트에서 클로저는 함수가 생성되는 시점에 생성된다.
= 함수가 생성될 때 그 함수의 렉시컬 환경을 포섭(closure)하여 실행될 때 이용한다
어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상을 말한다.
const A = function () {
let a = 1;
const B = function () {
return ++a;
}
return B;
};
const copyA = A();
for (let i = 0; i < 3; i++) {
console.log(copyA()); // 2 3 4
}
a의 값이 계속 남아 2 3 4처럼 하나씩 증가 되는 값을 console log를 통해 확인 할 수 있었다.
예제를 확인해보면 내부함수를 외부로 전달 하였을 때 컨텍스트가 종료 된 이후에도 변수는 사라지지 않는 것을 보여준다. 왜냐하면 copyA가 A함수를 호출하면서 내부함수 B를 참조하게 되었기 때문이다.
GC 특성 상 더이상 사용하지 않는 변수를 수거하지만 copyA가 B를 참조하면서 B내부에 있는 a 변수를 계속해서 참조를 하게 되었다. 그로인해 A함수 실행 완료 후 A함수가 실행컨텍스트에서 제거 되어야하지만 변수 a를 사용한 B 함수에 의해 A 함수의 변수 a는 제거되지 않고 남아있게 된다.
클로저의 개념을 다시 설명하게 되면 내부함수 B가 Lexical Environment(자신이 실행되었을 때의 환경)를 기억하는 함수이다.
은닉화
(function s(){
let a = 'hi'
})() //a is not defined
이러한 방식과 같이 직접적으로 변경되면 안 되는 변수에 대한 접근을 막는 것을 은닉화라고 한다.
function hello(name) {
let _name = name;
return function () {
console.log('Hello, ' + _name);
};
}
let a = new hello('영서');
let b = new hello('아름');
a() //Hello, 영서
b() //Hello, 아름
이렇게 a와 b라는 클로저를 생성함으로써 함수 내부적으로 접근이 가능하다.
function a(){
let temp = 'a'
return temp;
}
// console.log(temp) error: temp is not defined
const result = a()
console.log(result); //a
위 함수 내부적으로 선언된 temp에는 직접적으로 접근을 할 수 없습니다. 함수 a를 실행시켜 그 값을 result라는 변수에 담아 클로저를 생성함으로써 temp의 값에 접근이 가능합니다.
실습 과제
콘솔에 찍힐 b 값을 예상해보고, 어디에서 선언된 “b”가 몇번째 라인에서 호출한 console.log에 찍혔는지, 왜 그런지 설명해보세요. 주석을 풀어보고 오류가 난다면 왜 오류가 나는 지 설명하고 오류를 수정해보세요.
let b = 1;
function hi () {
const a = 1;
let b = 100;
b++;
console.log(a,b);
}
//console.log(a);
// error 나는 이유: const는 블록 스코프이기 때문에 코드 블록 내에서만 가능하고, 외부에서는 접근할 수 없다. 그래서 a 변수를 외부에서 console로 찍을 수 없다.
console.log(b); // 1
// 맨 첫 줄인 전역변수 b=1이 콘솔에 찍힌다.
hi(); // 1 101
// a는 const로 1의 값을 고정해놨기 때문에 a에는 1이 할당된다.
// b는 hi라는 함수 안에서 지역변수로 선언되어서 100이 할당되었기 때문에
// console에 100+1=101이 찍히게 된다.
console.log(b); // 1
// 전역변수의 값을 console에 찍는다.