티스토리 뷰

JavaScript의 자료형과 JavaScript만의 특성은 무엇일까 ?

  • 느슨한 타입(loosely typed)의 동적(dynamic) 언어
    • 자바스크립트에서 값은 항상 문자열이나 숫자형 같은 특정한 자료형에 속한다. 자바스크립트에는 8가지 기본 자료형이 있다. 자바스크립트의 변수는 자료형에 관계없이 모든 데이터일 수 있다. -> 자료의 타입은 있지만 변수에 저장되는 값의 타입은 언제든지 바꿀 수 있는 언어를 '동적 타입 언어'라고 부른다. 

데이터 타입 (primitive type, object type) 

  • JS 언어의 타입은 크게 원시 값과 객체로 나뉜다. 
  • *원시 값- 객체를 제외한 모든 타입은 불변 값(변경할 수 없는 값)을 정의한다.

Primitive types 

: 문자열이든 숫자든 한 가지만 표현할 수 있기때문에 원시(primitive) 자료형

  • 숫자형(number type)은 정수 및 부동소수점 숫자(floating point num)을 나타낸다. 숫자형과 관련된 연산은 대표적으로 사칙연산이 있습니다. 숫자형엔 일반적인 숫자 외에 Infinity-InfinityNaN같은 '특수 숫자 값(special numeric value)'이 포함 
    • Infinity 는 어떤 숫자보다 더 큰 특수 값, 무한대를 나타낸다. 어느 숫자든 0으로 나누면 무한대를 얻을 수 있습니다. 나누지 않고 Infinity를 직접 참조할 수도 있다.  
    • NaN은 계산 중 에러가 발생했다는 것을 나타내는 값이다. 부정확하거나 정의되지 않은 수학 연산을 사용하면 계산 중에 에러가 발생하는데 이 때, NaN이 반환됩니다. (e.g. 문자열을 숫자로 나누면 NaN) -> NaN은 웬만하면 바뀌지 않기 때문에 어떤 추가 연산을 해도 계속해서 NaN이 반환됩니다. NaN이 어떤 연산에서 반환됐다면 모든 결과에 영향을 미칩니다. 
    • 자바스크립트에서 행해지는 수학 연산은 안전한 편입니다. 0으로 나눈다거나 숫자가 아닌 문자열을 숫자로 취급하는 등의 이례적인 연산이 JS에선 가능하기 때문입니다. 수학 연산이 잘못돼도 NaN를 반환하며 연산이 종료될 뿐입니다. 
  • BigInt
    • 아주 큰 숫자를 사용해야하는 경우는 BigInt 를 사용합니다(길이 제약X). 표준으로 채택된 지 얼마안된 자료형으로 길이에 상관없이 정수를 나타낼 수 있다. BigInt형 값은 정수 리터럴 끝에 n을 붙이면 만들 수 있다. (IE지원 X)
  •  문자형(String type)
    • "" 큰따옴표
    • '' 작은 따옴표 // JS에서 큰따옴표와 작은 따옴표는 차이 X 
    • `` 역따옴표(백틱, backtick) // ${...}를 통해 변수나 표현식을 문자열 중간에 넣을 때 용이
    • 글자 하나를 저장할떄 쓰이는 자료형, char type은 따로 지원하지 않습니다. 
  • 불린형(논리 타입)
    • true , false 두 가지 밖에 없는 자료형
    • 비교 결과를 저장할 때도 사용된다. e.g.) let isGreater = 4 > 1; alert( isGreater ); // true
  • undefined와 null 형
    • null 값과 undefined 값은 자신만의 자료형을 독립적으로 형성한다. 
    • JS에서 null존재하지 않는, 비어있는, 알 수 없는 값을 나타낸다. 
    • undefined값이 할당되지 않은 상태 를 나타낼 때 사용한다. (변수 선언만하고 값을 할당하지 않으면 해당 변수에 undefined가 자동으로 할당된다. ) -> 물론 직접 할당하는 것도 가능하나 예약어로 남겨두는 것이 바람직합니다. 

Object types 

  • 객체형(object type)
    • 객체(object)형은 특수한 자료형이다. 
    • 복잡한 데이터 구조를 표현할 때 사용
    • 객체형을 제외한 다른 자료형은 문자열이든 숫자든 한 가지만 표현할 수 있기 때문에 원시(primitive) 자료형이라 부르고 반면, 객체는 데이터 컬렉션이나 복잡한 개체(entity)를 표현할 수 있습니다.
  • 심볼형(symbol type)
    • 객체의 고유한 식별자(unique identifier)를 만들 때 사용된다.
    • ES6에 추가, 객체 속성을 만드는 데이터 타입

자료형을 반환하는 연산자:  typeof 

  • typeof x 혹은 typeof(x) 로 확인 가능(연산자, 함수 두 가지 형태의 문법 지원)-> 변수의 자료형을 빠르게 알아내고 싶을 때 사용
  • return값은 인수의 자료형을 나타내는 '문자열' 
주의❗
1) null의 경우 typeof의 결과로 'object'가 반환되지만 null은 별도의 고유한 자료형을 가지는 특수 값으로 객체가 아닙니다. 언어 자체의 오류지만 하위호환 문제로 오류를 남겨둔 것 뿐입니다. 즉, null은 객체가 아니다. 
2) typeof의 피연산자가 함수면 'function'을 반환하지만 함수형은 따로 존재하지 않습니다. 함수는 객체형에 속한다. 위와 같은 호환성 문제로 남겨진 상태

 

  • JavaScript 형변환
    • 자바스크립트는 매우 유연한 언어이기 때문에 개발자의 의도에 따라 명시적 형변환과 암시적 형변환을 할 수 있다. 
    • 산술 연산자를 활용해서 형변환 // +(더하기)의 경우 우선순위: string > number ,  나머지 연산자(*, / , %, - )는 우선순위: number > string 
  • ==, === (동치 비교)
    • 기본적으로 비교를 통해 true/ false를 반환한다. 
    • == 는 엄격하지 않은 동치 비교이며 값만 비교한다. 
    • === 는 엄격한 동치 비교로 값과 데이터 타입까지 비교한다.
  • 느슨한 타입(loosely typed)의 동적(dynamic) 언어의 문제점은 무엇이고 보완할 수 있는 방법에는 무엇이 있을지 생각해보세요.
    • 느슨한 타입의 동적언어는 런타임에 비로소 타입이 결정되는 언어입니다. 이에 반대되는 개념인 정적 언어는 컴파일 시간(런타임 이전)에 변수의 타입이 결정됩니다. 빌드 시점에 데이터타입이 결정되는 것이 아니라 실행을 하면서 데이터 타입이 결정되기 때문에 Type error를 일으킬 수 있습니다. 해당 방법에 대한 대안책으로 TypeScript가 있습니다. 
    • TypeScript는 자바스크립트에서 코드를 입력할 때 타입을 미리 부여하는 기능을 추가한 정적 타입 언어입니다.

JavaScript 객체와 불변성이란 ?

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

  • 기본형 데이터는 위에 나와있는 원시타입을 말하고 참조형 데이터는 객체타입을 말합니다. 
  • 기본형 데이터는 메모리상에 고정된 크기로 저장되며 원시 데이터 값 자체를 보관하므로, 불변적입니다. 
  • 참조형은 기본형 데이터의 집합이며 참조형 데이터는 값이 지정된 주소값을 할당합니다. 
  • 변수를 선언하면 데이터가 담길 공간을 확보하고, 확보된 데이터의 주소값을 가지고 변수면과 매칭시키는 선언과정까지 동일하나, 할당 과정에 차이를 갖는다.

🍕불변 객체를 만드는 방법

  • ES6에 추가된 const 키워드 Object.freeze() 를 사용한다.
  • const의 재할당불가 + Object.freeze()의 객체속성 변경불가 // 둘을 같이 사용해야 한다.
const test = { 'name' : 'jung' }; Object.freeze(test);

🍕얕은 복사와 깊은 복사

  • 자바스크립트의 값은 원시값(Number, String, Boolean, Null, Undefined)과 참조값(Object, Symbol)으로 나뉩니다. 
  • 원시값은 값을 복사할 때 복사된 값을 다른 메모리에 할당하기 때문에 원래의 값과 복사된 값이 서로에게 영향을 미치지 않습니다.
  • 참조값은 변수가 객체의 주소를 가리키는 값이기 때문에 복사된 값(주소)이 같은 값을 가리키게 됩니다. 
// 원시값 복사 예시
const a = 1;
let b = a;

b = 2

console.log(a); //1
console.log(b); //2

// 참조값 복사 예시

const a = {number: 1};
let b = a;

b.number = 2

console.log(a); // {number: 2}
console.log(b); // {number: 2}

이러한 객체의 특징 때문에 객체를 복사하는 방법은 크게 두가지로 나뉩니다. 

 

🌞얕은 복사 (Shallow Copy) 

  • 객체를 복사할 때 위의 예제처럼 원래값과 복사된 값이 같은 참조를 가리키고 있는 것을 말한다. 
  • 객체 안에 객체가 있을 경우 한개의 객체라도 원본 객체를 참조하고 있다면 이를 얕은 복사라고 한다.

얕은 복사를 하는 법

  • 방법1) Object.assign() : 첫번째 요소로 들어온 객체에 다음인자로 들어온 객체를 복사해준다. 
const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const copiedObj = Object.assign({}, obj);

copiedObj.b.c = 3

obj === copiedObj // false
obj.b.c === copiedObj.b.c // true
  • 방법2) 전개 연산자 (ES6) 사용 
const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const copiedObj = {...obj}

copiedObj.b.c = 3

obj === copiedObj // false
obj.b.c === copiedObj.b.c // true
🛠 1depth 까지는 깊은 복사가 가능합니다. 하지만 2depth부터는 참조가 이어지기 때문에 얕은 복사로 분류합니다. 

🌞깊은 복사(Deep Copy)

-깊은 복사된 객체는 객체 안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어진 객체를 말한다.

  • 방법1) 재귀함수를 이용한 복사 
  • 방법2) JSON.stringify() 사용 //해당 방법은 사용은 쉽지만 다른 방법에 비해 아주 느리다고 알려짐 
  • 방법3) loadsh 라이브러리 사용 

*방법1 방법2 예시는 https://velog.io/@th0566/Javascript-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC 를 통해 확인하자.

// lodash 라이브러리 사용 
const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const copiedObj = _.cloneDeep(obj);

copiedObj.b.c = 3

obj.b.c === copiedObj.b.c //false

호이스팅과 TDZ는 무엇일까 ?

🌞스코프, 호이스팅, TDZ

 

스코프(scope)란? 사전적으로 영역, 범위라는 뜻을 가지고 있다.  JS에서는 변수에 영향을 미칠 수 있는 범위 혹은 변수에 접근할 수 있는 범위(식별자에 대한 유효범위)로 이해하면 된다. 
  • 스코프는 크게 전역(global)스코프지역(local)스코프로 분류한다.
  • 변수가 함수 바깥에 선언되어 있거나  아무런 키워드(var, let, const)없이 변수를 선언하고 할당하게 되면 전역 스코프에 포함된다.  
  • scope의 개념은 대부분의 언어에 존재한다. 다만 ES5까지의 자바스크립트는 특이하게도 전역공간을 제외하면 오직 함수에 의해서만 scope가 생성된다. 
  • let, const 키워드로 선언한 변수는 모두 코드 블록(e.g. 함수, if, for, while, try/catch, while 문 등)을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다. 

🌞스코프 체인, 변수 은닉화

  • 스코프 체인이란 scope를 '안에서부터 바깥으로' 차례로 검색해 나가는 것입니다.
여러 scope에서 동일한 식별자(동일한 이름의 변수)를 선언한 경우에는 무조건 scope chain 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 된다. 
var a = 1;
const outer = function () {
  const inner = function () {
    console.log(a); //undefind
    var a = 3;
  };
  inner(); //리턴값이 없으므로 딱히 뭐가 뜨진않음
  console.log(a); //1
};
outer();
console.log(a); // 1
  • 스코프 체인 상에 있는 변수라고 해서 무조건 접근이 가능한 것은 아니다. 식별자 a는 전역 공간에서도 선언했고 inner 함수 내부에서도 선언했다. 즉 inner 함수 내부에서 a변수를 선언했기 때문에 전역공간에서 선언한 동일한 이름의 a변수에는 접근할 수 없는 셈이다. 이를 변수 은닉화라고 한다. 
🤸‍♀️호이스팅이란?  변수 선언이 함수 또는 전역 코드의 상단에 이동하는 것과 같은 행동을 말합니다. 선언을 최상단으로 끌어올려주는 것 
  • 변수 선언이 런타임에 되는 것이 아니라, 그 이전 단계에서 먼저 실행된다. 자바스크립트 엔진은 소스코드를 한 줄씩 순차적으로 실행하기 앞서, 변수선언을 포함한 모든 선언문(e.g.변수선언문, 함수 선언문 등)을 찾아내 먼저 실행한다. 선언이 어디에 있든 상관없이 다른 코드보다 먼저 실행되는 특징을 hoisting이라 한다. 
  • 변수 선언뿐만 아니라, var, let, const, function, function*, class 키워드를 사용해 선언한 모든 식별자(변수, 함수, 클래스 등)는 호이스팅이 된다.
주의❗)var의 경우, 변수의 중복 선언이 가능하고 함수 레벨 스코프로 인해 함수 외부에서 선언한 변수는 모두 전역 변수가 된다. 변수 선언문 이전에 변수를 참조하면 언제나 undefined를 반환한다. (그냥 쓰지말자) 

🍕함수 선언문과 함수 표현식에서 호이스팅 방식의 차이

  • 함수 선언문(함수 이름 생략 불가 가장 basic한 방법) - 런타임 이전에 자바스크립트 엔진에서 먼저 실행되어, 함수 자체를 실행시킬 수 있음
  • 함수 표현식 - 변수 호이스팅과 같이 런타임 이전에 해당 값을 undefined로 초기화만 시키고, 런타임에서 해당 함수 표현식이 할당되어 그때 객체가 되기 때문에 호이스팅 x 
// 함수 선언문
function add(x, y) {
  return x + y
}

// 함수 표현식
const sub = function(x, y) {
  return x + y
}

🍕실행 컨텍스트와 콜스택

 

🌞실행 컨텍스트(Execution Context) 란?

-자바스크립트 엔진이 코드를 실행하기 위해선 코드에 대한 정보가 필요하다. 코드에 선언된 변수와 함수, 스코프, this, arguments등을 묶어 코드가 실행되는 위치를 설명한다는 뜻의 Execution Context라고 부릅니다. 자바스크립트 엔진은 Execution Context 내에서 실행합니다. 

 

🌞실행 컨텍스트 종류 2가지

  1. Global 
    • 코드를 실행하며 단 한개만 정의되는 전역 Context입니다. global object를 생성하며 this값에 global object를 참조합니다. 전역 실행 컨텍스트는 Call Stack에 가장 먼저 추가되며 앱이 종료될 때 삭제된다. 
  2. Function 
    • 함수가 실행될 때마다 정의되는 context입니다. 전역 실행 컨텍스트가 단 한번만 정의되는 것과 달리, 함수 실행 컨텍스트는 매 실행시마다 정의되며 함수 실행 종료(return)되면 Call Stack에서 제거된다.
  3. Eval (얜 무시) - 보안상 취약, 비권장 함수

🌞실행 컨택스트의 관리: Call Stack

js 엔진은 생성된 Context를 관리하는 목적의 Call Stack(호출스택)을 가지고 있습니다. JS는 단일 스레드 형식이기 때문에 런타임에 단 하나의 Call Stack이 존재합니다. js 엔진은 전역 범위의 코드를 실행하며 Global execution context를 생성해 stack 에 push합니다. 그리고 함수가 실행 또는 종료될 때마다 global execution context의 위로 function execution context stack을 push(추가), pop(제거)합니다.  

 

Call Stack은 최대 stack 사이즈가 정해져 있기 때문에 call stack에 context stack이 최대치를 넘으면 "RangeError: Maximum call stack size exceeded"라는 에러가 발생합니다. 이를 Stack Overflow라고 부릅니다. 

 

🌞TDZ(Temporal Dead Zone): 일시적 사각지대

"스코프의 시작 점부터 초기화 시작 지점까지의 구간"을 TDZ 라고 합니다.

🌞JS의 변수 선언 단계

  1. 선언 단계 - 변수를 실행 컨텍스트의 변수 객체에 등록하는 단계, 이 변수 객체는 스코프가 참조하는 대상이 됩니다.
  2. 초기화 단계 - 실행 컨텍스트에 존재하는 변수 객체에 선언 단계의 변수를 위한 메모리를 만드는 단계입니다. 이 단계에서 할당된 메모리에는 undefined 로 초기화 됩니다. 
  3. 할당 단계 - 사용자가 undefined 로 초기화된 메모리의 다른 값을 할당하는 단계입니다. 

-> let, const 로 선언된 변수는 var 키워드와 다르게 선언 단계와 초기화 단계가 분리되어 진행됩니다. (var 키워드는 선언 + 초기화가 동시에 이뤄집니다.) 그렇기 때문에 실행 컨텍스트에 변수를 등록했지만, 메모리가 할당되지 않아 접근할 수 없어 참조 에러(Reference Error)가 발생하는 것입니다. 추가로 말하자면 let, const 의 초기화 시점은 선언문에 도달했을 때 됩니다. 그렇기 때문에 선언 이전에 접근하려고 한다면 참조에러가 발생합니다. 이에 대한 자세한 내용은 아래의 링크를 참고해주세요. 

 

+ function 키워드는 변수 선언 3단계를 동시에 진행합니다. 

실습 과제

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

 

- hi 내부에 있는 변수 a 를 block밖에서 호출하려고 하면 변수 은닉화 때문에 접근할 수 없다. 이를 해결하기 위해서 전역 컨텍스트에 전역 변수를 하나 만들어주면 된다. 

let b = 1;
let a = 2;
function hi() {
  let a = 1;

  let b = 100;

  b++;

  console.log(a, b);
}

console.log(a); //오류를 수정하려면 global context에 전역변수를 하나 만들어주면 된다.

console.log(b); // 1, 전역변수 b가 출력

hi(); //  1 101  hi함수 내부의 지역변수 a와 b가 출력

console.log(b); // 1 // 전역변수 b가 출력

 

 

https://poiemaweb.com/es6-block-scope

 

let, const | PoiemaWeb

ES5까지 변수를 선언할 수 있는 유일한 방법은 var 키워드를 사용하는 것이었다. var 키워드로 선언된 변수는 아래와 같은 특징이 있다. 이는 다른 언어와는 다른 특징으로 주의를 기울이지 않으면

poiemaweb.com

 

댓글