[TIL #14]

이상현·2024년 8월 16일

[ TIL ]

목록 보기
14/38
post-thumbnail

1. 자바스크립트에서 데이터 타입 구분

  • 자바스크립트에서 데이터 타입은 크게 기본형, 참조형으로 구분됨.
  • 두개의 구분 기준은 값 저장 방식과 불변성 여부.

💡 [기본형과 참조형의 구분 기준]

  1. 복제의 방식
    1. 기본형 : 값이 담긴 주소값을 바로 복제
    2. 참조형 : 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제
  2. 불변성의 여부
    1. 기본형 : 불변성을 띔
    2. 참조형 : 불변성을 띄지 않음

변수 선언과 데이터 할당

var str = 'test!';
  • 변수를 선언하고 데이터를 할당해 주었다.
    그렇다면 데이터는 어떻게 저장되는가.

  • 주소 1002번에 식별자와 데이터의 주소값을 넣어주는 방식으로 저장된다.

  • 어째서 주소 1002번에 식별자와 데이터 동시에 저장하지 않는가??

그 이유는 두가지가 있다.

  1. 값을 바로 변수에 대입하지 않는 이유(=무조건 새로 만드는 이유)
  • 자유로운 데이터 변환
    • 이미 입력한 문자열이 길어진다면?
    • 숫자는 항상 8byte로 고정이지만, 문자는 고정이 아니므로
      이미 1002 주소에 할당된 데이터 값을 변경하려고 할 때 데이터의 크기가 증가한다면, 모든 데이터 값을 우측으로 다 미뤄야하는 비효율적 상황이 발생할 수 있음.

  1. 메모리의 효율적 관리
  • 똑같은 데이터를 여러번 저장해야 한다고 가정하자.
    • 1만개의 변수를 생성해 모든 변수에 숫자 1을 할당한다고 생각한다면 모든 변수를 위한 주소를 줘야하므로, 1만개의 변수 공간이 필요하다.
    • 그렇다면 1만개 * 8byte = 8만byte

    • 하지만 변수 영역에 별도로 저장하고 꺼내 사용한다면
    • 변수 영역 : 2byte * 1만개 = 2만byte
    • 데이터 영역 : 8byte 1개 = 총 2만 8byte
  • 이처럼 메모리를 효율적으로 관리할 수 있다.

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

  1. 불변값과 불변성(기본형 데이터)
var a = 'abc';

여기서 a라는 변수가 abc라는 값에서 abcdef가 되는 과정을 통해 불변성을 알 수 있다.

  • 1001번 주소에 a라는 식별자와 a의 데이터 값의 주소가 저장되 있고,
    데이터 값 'abc'가 주소 @5002번에 저장되 있다고 가정하면,
  • 'abcdef'로 변경될 때는 주소 @5003번에 새로 'abcdef'라는 값이 생기고 a의 데이터 값의 주소가 @5002->@5003으로 변경된다.
  • '즉' 데이터 값의 주소가 변경되었지 a라는 식별자는 변경되지 않았다.

= 변수 a는 불변하다.
(이때 @5002번은 더 이상 사용되지 않아 가비지컬렉터의 수거 대상이 됨)

  1. 가변값과 가변성(참조형 데이터)
var obj1 = {
  a: 1,
  b: 'bbb'
};

// 데이터 번경
obj1.a = 2;

이렇게 참조형 데이터가 존재할 때 기본형 데이터와 다른 점이 있다.

  • 객체의 변수(프로퍼티) 영역의 별도 존재가 있다는 것이다.

    표를 보면 우선 1001번에 obj1 식별자와 obj1에 저장된 데이터의 주소값을 적어준다. 하지만 obj1은 참조형 데이터이기 때문에 안에 존재하는 a와 b의 주소값을 따로 만들어 준다.
    나머지 할당 방법은 기본형 데이터와 같다.

  • 그런데 여기서 obj1.a = 2로 데이터를 변경한다면

    이렇게 a의 주소값이 변경된다. 이처럼 obj1의 데이터값은 불변이지만 obj1을 위한 참조형 데이터들의 별도 영역은 얼마든지 변경이 가능하므로 참조형 데이터를 흔히, 가변하다 라고 한다.


이러한 가변성은 가끔 큰 문제를 준다.
// user 객체를 생성
var user = {
	name: 'wonjang',
	gender: 'male',
};

// 이름을 변경하는 함수, 'changeName'을 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티(속성)에 접근해서 이름을 변경했네요! -> 가변
var changeName = function (user, newName) {
	var newUser = user;
	newUser.name = newName;
	return newUser;
};

// 변경한 user정보를 user2 변수에 할당하겠습니다.
// 가변이기 때문에 user1도 영향을 받게 될거에요.
var user2 = changeName(user, 'twojang');

// 결국 아래 로직은 skip하게 될겁니다.
if (user !== user2) {
	console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name); // twojang twojang
console.log(user === user2); // true
  • 이러한 코드를 실행하면 user.name과 user2.name 모두 twojang으로 변경되게 된다.

이를 해결하기 위해서는,

// user 객체를 생성
var user = {
	name: 'wonjang',
	gender: 'male',
};

// 이름을 변경하는 함수 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티에 접근하는 것이 아니라, 아에 새로운 객체를 반환 -> 불변
var changeName = function (user, newName) {
	return {
		name: newName,
		gender: user.gender,
	};
};

// 변경한 user정보를 user2 변수에 할당하겠습니다.
// 불변이기 때문에 user1은 영향이 없어요!
var user2 = changeName(user, 'twojang');

// 결국 아래 로직이 수행되겠네요.
if (user !== user2) {
	console.log('유저 정보가 변경되었습니다.');
}

console.log(user.name, user2.name); // wonjang twojang
console.log(user === user2); // false 👍

이렇게 changeName이라는 식별자의 함수를 정의해서 사용하는 것이다.
하지만 이또한, 완전한 해결책은 아니다.

  • why? ) user 객체가 지금은 2개지만 수없이 늘어난다면 전부 다 return문 안에 작성해야하는 엄 청난 하드코딩이 되기 때문이다.

해결책 -> 얕은 복사
//이런 패턴은 어떨까요?
var copyObject = function (target) {
	var result = {};

	// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근할 수 있습니다.
	// 하드코딩을 하지 않아도 괜찮아요.
	// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경하면
	// 되겠죠!?
	for (var prop in target) {
		result[prop] = target[prop];
	}
	return result;
}
  • 이러한 방식으로 for ... in 문을 사용하여 함수 안에 있는 모든 객체들을 돌아가며 할당해주는 방식이 있다.

하지만 이 방법 역시 함수안에 함수가 존재하는 경우 안쪽에 있는 객체들이 제대로 복사되지 않는 문제점이 존재한다.

해결책 -> 깊은 복사

var user = {
	name: 'wonjang',
	urls: {
		portfolio: 'http://github.com/abc',
		blog: 'http://blog.com',
		facebook: 'http://facebook.com/abc',
	}
};

// 1차 copy
var user2 = copyObject(user);

// 2차 copy -> 이렇게까지 해줘야만 해요..!!
user2.urls = copyObject(user.urls);

user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio);

user2.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog);
// 해결방법
var copyObjectDeep = function(target) {
	var result = {};
	if (typeof target === 'object' && target !== null) {
		for (var prop in target) {
			result[prop] = copyObjectDeep(target[prop]);
		}
	} else {
		result = target;
	}
	return result;
}
  • for ... in 문안에 본인 스스로를 대상으로 지정하여 계속하여 반복시켜 가장 깊은 곳에 객체 까지 변경 시킬 수 있다. 이것을 재귀적 수행이라고 한다.
    (재귀 함수)

2. 실행컨텍스트(스코브, 변수, 객체, 호이스팅)

<용어 정리>
실행 컨텍스트 : 실행할 코드에 제공할 환경 정보들을 모아놓은 객체.
호이스팅 : 선언된 변수를 위로 끌어올림.

실행 컨텍스트

실행 컨텍스트를 알기 위해 콜 스택을 알아보자

스택과 큐는 자료구조의 종류이다.
Stack은 후입선출(LIFO), Queue는 선입선출(FIFO)형태이다.

이제 본격적으로 실행 컨텍스트에 대해 알아보자.
실행 컨텍스트는 동일 환경에 있는 코드를 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이것을 위에서 본 콜스택에 쌓아 올린다.

컨텍스트의 구성

  • 전역공간
  • eval()함수
  • 함수
// ---- 1번
var a = 1;
function outer() {
	function inner() {
		console.log(a); //undefined
		var a = 3;
	}
	inner(); // ---- 2번
	console.log(a);
}
outer(); // ---- 3번
console.log(a);

이 코드를 실행하면 코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) → inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료 이러한 순서로 실행된다.


스택의 구조로 그려보면 이렇게 된다.

  • 실행 컨텍스트 객체의 실체
    1. VariableEnvirionment (VE)
    2. LexicalEnvironment (LE)
    3. ThisBinding

VE는 컨텍스트 내의 식별자 정보(=record)를 갖고 있다.

  • 위 코드에서는 var a = 3 , 즉 var a를 의미한다
  • 외부 환경 정보(=outer)를 갖고 있다.
  • 선언 시점에서의 LE의 스냅샷, 즉 그 당시 데이터를 쭉 가지고 있다.
    LE는 VE와 동일하지만, 변경사항을 실시간으로 반영한다.

LE와 VE는 호이스팅이라는 특징을 가지고 있다.

profile
Node.js_6기

0개의 댓글