데이터 타입(심화), 실행 컨텍스트, this
값의 타입(기본형 vs 참조형)을 나누는 기준은 아래 2가지 !
식별자, 변수 (헷갈림 주의 !!)
var str; // str = 식별자 = 변수명 // 변수 선언 !!!
str = 'test!'; // 'test!' = 데이터 = 변수 // 데이터 할당 !!!
'변수이면서 불변하다'라는 말 존재
가비지컬렉터(GC, Garbage Collector) : 필요없는 메모리를 모아주는 기능


불변 객체를 위해 얕은 복사라는 방법이 있지만 중첩된 객체에 대해서는 완벽한 복사를 할 수 없기 때문에 깊은 복사를 통해 완벽한 복사를 구현해야 한다 !!
바로 재귀적 함수를 통해 깊은 복사를 할 수 있다.
// 깊은 복사 - 재귀적 함수
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;
};
//결과 확인
var obj = {
a: 1,
b: {
c: null,
d: [1, 2],
},
};
var obj2 = copyObjectDeep(obj);
obj2.a = 3;
obj2.b.c = 4;
obj2.b.d[1] = 3;
console.log(obj); // { a: 1, b: { c: null, d: [ 1, 2 ] } }
console.log(obj2); // { a: 3, b: { c: 4, d: { '0': 1, '1': 3 } } }
+ 주석 추가 +
var a;
console.log(a); // (1) 변수에 값이 지정되지 않은 경우
// (= 데이터 영역에 메모리 주소가 없다는 의미 🥸)
var obj = { a: 1 };
console.log(obj.a); // 1
console.log(obj.b); // (2) .이나 []로 객체나 배열에 접근하려고 할 때, 해당 데이터(값)이 존재하지 않는 경우
// console.log(b); // 오류 발생
var func = function() { };
var c = func(); // (3) return 문이 없는데 호출하려고 하는 경우
console.log(c); // undefined
var n = null;
console.log(typeof n); // object // 자바스크립트의 버그
//동등연산자(equality operator)
console.log(n == undefined); // true // 동등연산자는 타입까지 일치하지 않아도 됨
console.log(n == null); // true
//일치연산자(identity operator)
console.log(n === undefined); // false // 일치연산자는 타입까지 체크하므로 null과 undefined는 구별됨
console.log(n === null); // true
- 스택 (stack) : Last In First Out
- 큐 (queue) : First In First Out
자바스크립트의 실행컨텍스트(스코프, 변수, 객체, 호이스팅) : 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 이것을 스택의 한 종류인 콜스택(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);
실행 콘텍스트 구성 예시 코드

실행 콘텍스트 콜스택 진행 과정 (대충 그려보기 ...ㅋ)
그렇다면 실행 컨텍스트 객체의 실체 내부(=담기는 정보)에는 무엇이 있을까 ?
1. VariableEnvironment : 현재 컨텍스트 내의 식별자(= 변수명, record) 정보와 외부 환경 정보(= outer)를 갖고 있음, 변경 사항을 반영하지 않아 처음 모습과 똑같음 = 스냅샷을 유지함
2. LexicalEnvironment : VariableEnvironment와 동일한 것, 다른 점은 시간이 갈수록 L.E는 변경사항을 실시간으로 반영함 (모습이 바뀜)
3. ThisBinding
LE와 VE에는 모두 현재 실행 컨텍스트와 관련된 식별자 정보(record)들이 수집(<=> 기록) 되는데, 이 수집 대상 정보에는 (1) 함수에 지정된 매개변수 식별자, (2) 함수 자체, (3) var로 선언된 변수 식별자 등이 있고, 컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집함. (물론, 코드 실행은 아직 아님)
즉, 이 위에 과정을 호이스팅이라고 한다.
호이스팅(hoisting) : 식별자 정보만 위로 다 끌어올림
(그러나 실은 변수 정보 수집 과정을 이해하기 쉽게 설명한 ‘가상 개념’임)
// 매개변수 적용 (호이스팅 적용 전)
function a () {
var x = 1;
console.log(x); // 1
var x;
console.log(x); //undefined
var x = 2;
console.log(x); // 2
}
a(1);
// 호이스팅 적용
function a () {
var x;
var x;
var x;
x = 1;
console.log(x); // 1
console.log(x); // 1
x = 2;
console.log(x); // 2
}
a(1);
호이스팅 적용 전과 후의 결과를 보면 알 수 있듯이, 호이스팅이라는 개념을 모르면 예측하기 어려운 결과
// 호이스팅 적용 전
function a () {
console.log(b); // undefined
var b = 'bbb';
console.log(b); // bbb
function b() { }
console.log(b); // ~~undefined~~ function()
}
a();
// 호이스팅 적용 후
function a () {
var b; // 변수 선언부 호이스팅
function b() { } // 함수 선언부 전체 호이스팅
console.log(b); // ~~b~~ function()
b = 'bbb';
console.log(b); // bbb
console.log(b); // bbb
}
a();
이 역시 결과 예측이 어려움.
함수 정의의 3가지 방식 : (1) 함수 선언문 : 함수명이 곧 변수명 / 함수 표현식 : 정의한 함수를 별도 변수에 할당 - (2) 익명 함수 표현식,
(3) 기명 함수 표현식
예시 코드는 참고 링크
그로 인해 함수 선언문은 함수 전체를 호이스팅하고, 함수 표현식은 변수 부분만 호이스팅한다.
주의할 점 !
협업을 할수록 함수 선언문은 코드 중복 등 오류가 내포되어있을 가능성이 큼.
따라서 코드 협업 때는 함수 선언문보다 함수 표현식을 활용하는 것이 더 좋다.
- 스코프 : 식별자(= 변수명)에 대한 유효한 범위, 대부분 언어에 존재
- outerEnvironmentReference(= outer) : 스코프 체인이 가능토록 하는 것, 즉 외부 환경의 참조정보
스코프 체인 : outer는 ⭐️현재 호출된 함수가 선언될 당시⭐️의 LexicalEnvironment를 참조, 결국 타고, 타고 올라가다 전역 컨텍스트의 LexicalEnvironment를 참조하게 됩니다. 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능하다.
var a = 1;
var outer = function() {
var inner = function() {
console.log(a); // 이 값은 뭐가 나올지 예상해보세요! 이유는 뭐죠? scope 관점에서!
var a = 3;
};
inner();
console.log(a); // 이 값은 또 뭐가 나올까요? 이유는요? scope 관점에서!
};
outer();
console.log(a); // 이 값은 뭐가 나올까요? 마찬가지로 이유도!
// 호이스팅 후 스코프 관점에서 결과
var a = 1;
var outer = function () {
var inner = function () {
var a;
console.log(a); // (1) undefined
a = 3;
};
inner();
console.log(a); // (2) 1
};
outer();
console.log(a); // (3) 1
Q. 그럼 전역 컨텍스트의 outer는 뭘 참조하지 ?
A. 전역 컨텍스트는 가장 최상위 컨텍스트이므로 outer는 null이다. (this: 전역객체)
참고 링크
즉, 각각의 실행 컨텍스트는 LE 안에 record와 outer를 가지고 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있으니 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.
var obj = {
methodA: function () { console.log(this) },
inner: {
methodB: function() { console.log(this) },
}
};
obj.methodA(); // this === obj
obj['methodA'](); // this === obj
obj.inner.methodB(); // this === obj.inner
obj.inner['methodB'](); // this === obj.inner
obj['inner'].methodB(); // this === obj.inner
obj['inner']['methodB'](); // this === obj.inner
var obj1 = {
outer: function() {
console.log(this); // (1)
var innerFunc = function() {
console.log(this); // (2), (3)
}
innerFunc();
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod();
}
};
obj1.outer();
TEST => { outer: [Function: outer] }
TEST => <ref *1> Object [global] {
global: [Circular *1],
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
structuredClone: [Getter/Setter],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
atob: [Getter/Setter],
btoa: [Getter/Setter],
performance: [Getter/Setter],
fetch: [AsyncFunction: fetch],
crypto: [Getter]
}
TEST => { innerMethod: [Function: innerFunc] }
일반 함수와 화살표 함수의 가장 큰 차이 ? => this binding 여부 !
생성자 : 구체적인 인스턴스(어려우면 객체로 이해!)를 만들기 위한 일종의 틀
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
};
var choco = new Cat('초코', 7); //this : choco
var nabi = new Cat('나비', 5); //this : nabi
즉, 위 코드에서는 새로운 Cat이라는 인스턴스가 (var choco에 담겨) 생성되었는데 this는 이 인스턴스를 지칭함 !
var func = function (a, b, c) {
console.log(this, a, b, c);
};
// call() 메소드
func.call({ x: 1 }, 4, 5, 6}; // { x: 1 } 4 5 6
// apply() 메소드
func.apply({ x: 1 }, [4, 5, 6]}; // { x: 1 } 4 5 6
유사 배열의 조건 : 배열과 마찬가지로 length와 index가 꼭 필요함. 특히 index는 0번부터 시작해서 1씩 증가해야함.
유사 배열은 push()나 slice()와 같은 진짜 배열 메소드는 사용하지 못하지만, call과 apply 메소드를 이용해 배열 메소드를 차용할 순 있음.
// 유사배열
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// 객체 -> 배열
var arr = Array.from(obj); // Array.from에 유사 배열을 넣으면 객체가 배열로 출력
// 찍어보면 배열이 출력됩니다.
console.log(arr); // [ 'a', 'b', 'c' ]
생성자 내부에서 다른 생성자를 호출(공통된 내용의 반복 제거)
여러 인수를 묶어 하나의 배열로 전달할 때 apply 사용
bind 메서드 : call과 다르게 즉시 호출하지 않고 미리 적용함, 또한 부분 적용 함수 구현할 때 용이
var obj = {
outer: function () {
console.log(this); // { outer: [Function: outer] }
var innerFunc = () => {
console.log(this); // { outer: [Function: outer] }
};
innerFunc();
},
};
obj.outer();
위 코드처럼 화살표함수 내부에서 this는 절대 전역객체를 가리키지 않음
문제
답
해설