목차
💻오늘 배운 내용
데이터 타입의 종류
데이터 타입은 크게 기본형과 참조형 2가지로 나뉘는데 구분 기준은 **값의 저장 방식**과, **불변성 여부**이다.
- 복제의 방식
- 기본형 : 값이 담긴 주소값을 바로 복제
- 참조형 : 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제
- 불변성의 여부
- 기본형 : 불변성을 띔
- 참조형 : 불변성을 띄지 않음
/** 선언과 할당을 풀어 쓴 방식 */
var str;
str = 'test!';
/** 선언과 할당을 붙여 쓴 방식 */
var str = 'test!';
⬇ 위를 예제로 값이 들어간다면
주소 … 1002 1003 …
데이터 | str/@5002 | |||
주소 | … | 5002 | 5003 | … |
데이터 | test! |
표와 같이 데이터가 저장된다고 할 수 있다
값을 바로 변수에 대입하지 않는 이유, 1002주소에 ‘str/test!’ 않는 것은
자유로운 데이터 변환과 효율적인 메모리 관리를 위함이다
다만 기본형 데이터와 참조형 데이터의 저장방식은 조금 다른데, 데이터 불변성의 차이다.
아래 표를 보면 참조형 데이터가 불변하지 않다(가변성이 있다)라고 하는 이유를 쉽게 이해할 것이다.
var obj1 = { a : 1, b : 'bbb' };
⬇️ 위의 데이터가 저장된다면
주소 1001 1002 1003 1004
데이터 | obj/@7103- | |||
주소 | 5001 | 5002 | 5003 | 5004 |
데이터 | 1 | bbb |
주소 7103 7104 7105 7106
데이터 | a/@5001 | b/@5002 |
요렇게 obj의 요소를 위한 주소가 필요하다
이러한 식으로 데이터 값이 지정되고 변경되다보면 참조카운터가 0인 메모리의 주소도 발생할 것이다.
그 객체는 더 이상 사용되지 않으므로, 가비지 컬렉터에 의해 메모리에서 제거된다.
얕은 복사와 깊은 복사
어떤 객체 ‘user’가 있다고 쳤을 때,
var user = { name: 'wonjang', gender: 'male' };
이름을 변경하는 함수를 만들어 두고
var changeName = function (user, newName) {
var newUser = user;
newUser.name = newName;
return newUser;
};
changeName 함수를 이용해서 ‘user2’의 이름만 변경한다고 친다면
var user2 = changeName(user, 'twojang');
이렇게 작성할 수 있다 하지만 이렇게 되면 가변성이 있기 때문에
user의 데이터에도 영향을 받아 user와 user2 모두 'wonjang'이 'twojang'으로 변경된다.
이를 방지 하기 위해 얕은 복사라는 방법이 고안되었는데,
var copyObject = function (target) {
var result = {};
for (var prop in target) {
result[prop] = target[prop];
}
return result;
}
이처럼 for ~ in 구문을 이용하여 객체의 모든 프로퍼티에 접근 할 수 있다.
이 방법을 이용하여 다시 코드를 작성한다면
var user = {
name: 'wonjang',
gender: 'male',
};
var user2 = copyObject(user);
user2.name = 'twojang';
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name);
console.log(user === user2);
이렇게 객체를 복사해도 불변성을 유지 할 수 있다.
하지만 얕은 복사는 depth가 1이라 중첩된 객체에 대해서는 완벽한 복사를 할 수 없기 때문에 한계가 있었다.
그렇게 고안된 것이 깊은 복사.
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;
}
이렇게 함수 안에서 자기 자신을 다시 호출하는 재귀적 수행의 형태로 만든다면
객체의 프로퍼티 중, 기본형 데이터는 그대로 복사 + 참조형 데이터는 다시 그 내부의 프로퍼티를 복사
이런 형태로 코드를 작성하면 완벽하게 복사 할 수 있는 깊은복사가 가능하다.
실행 컨텍스트
**실행 컨텍스트**는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
1] 콜 스택 (call stack)
자바 스크립트는 call stack언어.
가장 위에 쌓여있는 컨텍스트와 관련된 코드를 실행하는 방법으로 코드의 환경 및 순서를 보장할 수 있다.
예시)
// ---- 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) → 코드종료
2] VariableEnvironment LexicalEnvironment
🌟‘environmentRecord’와 ‘outerEnvironmentReference’로 구성
현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.
차이점
- VE : 스냅샷을 유지.
- LE : 스냅샷을 유지하지 않음. 즉, 실시간으로 변경사항을 계속해서 반영.
결국, 실행 컨텍스트를 생성할 때, VE에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LE를 만들고 이후에는 주로 LE를 활용한다.
3] 호이스팅 (hoisting)
변수 정보 수집 과정을 이해하기 쉽게 설명한 ‘가상 개념’
텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집
- Hoisting 대상
- var 변수/함수 선언문 에서만 호이스팅이 일어난다.
- var 변수/함수의 선언만 위로 끌어올려지며, 할당은 끌어올려지지 않는다.
- let, const 변수 선언과 함수표현식에선 호이스팅이 발생하지 않는다.
- Hoisting 우선순위
- var 변수 선언 vs 함수 선언문
- var 변수 선언이 함수 선언문보다 위로 끌어올려진다.
- 값이 할당되어있지 않은 var vs 값이 할당되어 있는 var
- 값이 할당되어 있지 않은 변수의 경우, 함수선언문이 변수를 덮어쓴다.
- 값이 할당되어 있는 변수의 경우, 변수가 함수선언문을 덮어쓴다.
- var 변수 선언 vs 함수 선언문
스코프, 스코프 체인
- 스코프 : 식별자에 대한 유효범위를 의미
- 스코프 체인
- 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조 오직 자신이 선언된 시점의 LexicalEnvironment를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근 가능
- 결론 : 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능
각각의 실행 컨텍스트는 LE 안에 record와 outer를 가지고 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있으니 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.
❓발생한 이슈/고민
3-9 스코프 체이닝
“함수 표현식의 호이스팅!?”
강의를 듣던 도중 함수 표현식에서 호이스팅이 어떤식으로 어떤 원리로 이해되지 않았다.
예시)
var a = 1;
var outer = function () {
var inner = function () {
console.log(a);
var a = 3;
};
inner();
console.log(a);
};
outer();
console.log(a);
이런 식이 있다고 가정할 때, 호이스팅이 어떻게 일어나고 각각의 콘솔의 값은 무엇일까?
답은 console.log(a)의 값이 위에서 부터 각각 undefined / 1 / 1 이라고 하는데, 왜???? 도저히 이해가 되지 않았다.
의문 1) 첫번째 콘솔의 경우 위에서 var a=1 이니까 1이 나와야 하는 것이 아닌가?
의문 2) 두번째 콘솔의 a는 왜 3이 아닌가
💡해결과정
의문 1의 해결)
내가 고려했던 부분
💡 호이스팅 우선순위
변수 선언이 함수 선언보다 위로 끌어올려진다.
var 변수/함수의 선언만 위로 끌어올려지며, 할당은 끌어올려지지 않는다.
변수에 할당된 함수 표현식은 호이스팅되지 않는다.
위의 조건을 고려해서 호이스팅이 된다면? 하고 내가 작성해본 것
var a;
var outer = function () {
console.log(a); // undefined
};
var inner = function () {
var a;
console.log(a); // undefined
a = 3;
};
a = 1;
console.log(a); // 1
변수들을 다 위로 끌어올리고, 와중에 함수표현식은 할당이랑 같이 올라가서..
콘솔 값을 대입해보니 전혀 다른 결과가 나와서 수렁에 빠짐
내가 개념 자체를 잘 못 이해 하고 있는 것 같아서 혼자서 해결하기는 포기하고 도움을 요청 하러갔다.
// 튜터님과 함께
var a;
var outer;
var inner;
// 전역
a = 1;
// inner
var a;
console.log(a); // undefined // inner 함수 안의 콘솔
a = 3;
inner(); // inner 함수 스택에서 나감
//outer
console.log(a); // 1 // outer 함수 안의 콘솔
// out의 record에 a가 없어서 외부환경에서 a값 가져옴
outer(); // outer 함수 스택에서 나감
// 전역
console.log(a); // 1
그림을 그려주시면서 설명을 해주셔서 이걸 글로 풀어봤는데 이게 맞는진 모르겠지만…
아무튼 나 혼자서는 절대 모르는 것이 맞았다.
문제를 해결하지 못 하고 있을 때는 도움을 구하는 것을 망설이지 말자..
만약 inner 함수안에 var a = 3 이라고 선언하지 않았더라면 inner 함수 안의 콘솔a의 값도 1이었을 것이다.
외부환경참조를 했을 것이기 때문.
의문 2의 해결)
call stack의 원리로 두번째 콘솔 a가 실행되기 이전에 inner 함수의 실행이 완료 되어 이미 스택을 나갔기 때문에 a=3이라는 값이 없다.
따라서 outer 함수 내부에는 a가 선언되지 않았기 때문에 외부환경에서 a의 값을 참조한다.
이 경우 전역에 있는 a=1을 참조. 따라서 console.log(a)의 값은 1
📋레퍼런스
https://doozi0316.tistory.com/entry/JavaScript-호이스팅Hoisting이란-호이스팅의-개념과-예제
'내일배움캠프 > Today I Learned' 카테고리의 다른 글
[TIL 2023.05.29] 영화 검색 페이지 만들기 (GOE’s CINEMA) (0) | 2023.05.30 |
---|---|
[TIL 2023.05.26] this의 정의, 활용방법, call, apply, bind (0) | 2023.05.27 |
[TIL 2023.05.24] ES6, 일급객체로서의 함수, Map과 Set (1) | 2023.05.25 |
[TIL 2023.05.23] TIL 쓰는법, JavaScript 조건문, 비교문 (0) | 2023.05.24 |
[TIL 2023.05.22] JavaScript 특징과 기본 문법 (0) | 2023.05.23 |