기본형과 참조형 데이터 타입을 나누는 기준: 값의 저장 방식, 불변성 여부.
변수 영역: 변수와 상수를 구분하는 기준 (변수에 새로운 값을 할당할 수 있는가)
데이터 영역: 불변성의 여부를 구분하는 기준 (데이터 영역의 값을 수정할 수 있는가)
bit: 0과 1을 가지고 있는 메모리를 구성하기 위한 작은 단위.
byte: 8개의 비트.
메모리: byte 단위로 구성
데이터: 메모리 주소값으로 구분이 됨.
변수 영역과 데이터 영역은 따로 관리됨. (값을 바로 변수에 대입하는 것이 아닌, 변수의 데이터에 값의 주소를 저장)
참조형 데이터는 객체의 변수(프로퍼티) 영역이 별도로 존재함.
불변성이 없다는 것은 위의 3번인 별도 영역에 프로퍼티의 값(데이터의 주소)이 바뀔 수 있다는 것을 뜻함.
중첩객체(객체의 프로퍼티가 객체)에 대해서도 별도 영역이 새로 생김.
예)
obj = {
x: 3;
arr: [3, 4, 5];
}
// changeName 함수 내에서 newUser 객체에 user 객체를 복사(단순 할당).
// 단순 할당은 가변이기 때문에 newUser.name에 새로운 값을 할당하면 기존의 객체도 영향을 받음.
var user = {
name: "wonjang",
gender: "male",
};
var changeName = function (user, newName) {
var newUser = user;
newUser.name = newName;
return newUser;
};
var user2 = changeName(user, "twojang");
if (user !== user2) console.log("유저 정보가 변경되었습니다.");
console.log(user.name, user2.name);
console.log(user === user2);
// twojang twojang
// true
//////////////////////////////////////////////////
// changeName 함수에서 user 객체의 단순 할당이 아닌
// 새로운 객체를 만들어서 name은 새로 할당하고
// gender는 user 객체의 gender를 할당하고 반환 (새로운 주소).
var user = {
name: "wonjang",
gender: "male",
};
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender,
};
};
var user2 = changeName(user, "twojang");
if (user !== user2) console.log("유저 정보가 변경되었습니다.");
console.log(user.name, user2.name);
console.log(user === user2);
// wonjang twojang
// false
위에는 객체의 프로퍼티가 2개 밖에 없어서 괜찮지만 만약 프로퍼티가 많아지면 새로운 객체를 생성하여 반환하는 과정이 엄청 길어지기 때문에 비효율적인 방법임.
해결방법) 얕은 복사
var copyObject = function (target) {
var result = {};
for (var prop in target) {
result[prop] = target[prop];
}
return result;
};
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);
for~in 반복문을 돌 때 프로퍼티로 객체가 있을 수 있기 때문에(중첩객체의 단순 할당 / 주소의 복사) 이 방법도 완벽한 복사는 못함.
해결방법) 깊은 복사
var copyObjectDeep = function (target) {
var result = {};
// target의 타입이 object이면서 null이 아닐 때
// 현재 함수 재귀적으로 호출
if (typeof target === "object" && target !== null) {
for (var prop in target) {
result[prop] = copyObjectDeep(target[prop]);
}
} else { // target의 타입이 object가 아닐 때 바로 할당
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);
console.log(obj2);
둘 다 '없음'을 의미하는 값.
undefined: js가 할당.
null: 개발자가 없는 값이라고 명시.
// 1. 변수의 값이 지정되지 않은 경우 (데이터 영역에 메모리 주소가 없는 경우)
var a;
console.log(a); // 값을 대입하지 않은 변수 접근
// 2. 객체나 배열에 접근할 때 해당 데이터가 존재하지 않는 경우
var obj = { a: 1 };
console.log(obj.b); // 존재하지 않는 property 접근
// 3. 함수가 반환문이 없는 경우
var func = function() {};
console.log(func()); // 반환 값이 없는 함수
!주의! null의 타입은 object (typeof null === object)
console.log(null == undefined); // true
console.log(null === undefined); // false
실행할 코드에 제공할 환경 정보들을 모아놓은 객체.
역할:
우리가 작성한 코드는 동일한 환경에 묶인 것 끼리 모여서 객체를 이루고 그 객체들은 콜 스택에 쌓임.
실행된 객체들은 콜 스택에서 하나씩 없어짐.
// ---- 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) → 코드종료
VariableEnvironment와 동일하지만 변경사항을 실시간으로 반영함.
담기는 항목은 완벽하게 동일하지만 스냅샷 유지여부가 다름.
VE: 스냅샷 유지.
LE: 스냅샷 유지 x. (실시간으로 변경사항을 계속해서 반영함)
변수 정보(record) 수집 과정을 이해하기 쉽게 설명한 '가상 개념'.
호이스팅 규칙:
법칙 1: 매개변수 및 변수는 선언부를 호이스팅함.
// 원래 코드
function a (x) {
var x = 1;
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
a(1);
// 매개변수 적용
function a () {
var x = 1;
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
a(1);
// 호이스팅 적용
function a () {
var x;
var x;
var x;
x = 1;
console.log(x);
console.log(x);
x = 2;
console.log(x);
}
a(1);
// 예상 실제
// 1 1
// undefined 1
// 2 2
법칙 2: 함수 선언은 전체를 호이스팅함.
// 원래 코드
function a () {
console.log(b);
var b = 'bbb';
console.log(b);
function b() {}
console.log(b);
}
a();
// 호이스팅 적용
function a () {
var b;
function b() {} // => var b = function b() {}
console.log(b);
b = 'bbb';
console.log(b);
console.log(b);
// 예상 실제
// undefined function b
// bbb bbb
// function b bbb
함수 정의 2가지 방식:
함수 선언문: 호이스팅에 의해 함수 전체가 위로 올라감.
함수 표현식: 호이스팅에 의해 함수 선언부는 위로 올라감.
함수를 함수 선언문으로 구현하면 호이스팅에 의해 함수 전체가 맨 위로 올라가기 때문에 문제가 발생할 수 있음.
예)
// 함수 선언문으로 정의
function sum(x, y) {
return x + y;
}
console.log(sum(1, 2));
console.log(sum("1", "2"));
function sum(x, y) {
return `${x} + ${y} = ${x + y}`;
}
console.log(sum(1, 2));
console.log(sum("1", "2"));
// 호이스팅 적용
function sum(x, y) {
return x + y;
}
function sum(x, y) {
return `${x} + ${y} = ${x + y}`;
}
console.log(sum(1, 2));
console.log(sum("1", "2"));
console.log(sum(1, 2));
console.log(sum("1", "2"));
// 예상 출력 // 실제 출력
// 3 1 + 2 = 3
// 12 1 + 2 = 12
// 1 + 2 = 3 1 + 2 = 3
// 1 + 2 = 12 1 + 2 = 12
///////////////////////////////////////////////
// 함수 표현식으로 정의
var getSum = (x, y) => x + y;
console.log(getSum(1, 2));
console.log(getSum("1", "2"));
var getSum = (x, y) => `${x} + ${y} = ${x + y}`;
console.log(getSum(1, 2));
console.log(getSum("1", "2"));
// 호이스팅 적용
var getSum;
var getSum;
getSum = (x, y) => x + y;
console.log(getSum(1, 2));
console.log(getSum("1", "2"));
getSum = (x, y) => `${x} + ${y} = ${x + y}`;
console.log(getSum(1, 2));
console.log(getSum("1", "2"));
// 예상 출력 // 실제 출력
// 3 3
// 12 12
// 1 + 2 = 3 1 + 2 = 3
// 1 + 2 = 12 1 + 2 = 12
outerEnvironmentReference
outer: 특정 실행 컨텍스트가 생성될 때의 환경 정보.
스코프: 식별자에 대한 유효범위.
스코프 체인:
// 전역 { A { B { C {} } } }
// A의 outer: 전역의 LE
// B의 outer: A의 LE
// C의 outer: B의 LE
var a = 1;
var outer = function () {
var inner = function () {
console.log(a);
var a = 3;
};
inner();
console.log(a);
};
outer();
console.log(a);
// inner()의 outer은 outer()의 LE.
// 하지만 inner()에서 사용된 a가 outer()에 존재하지 않기 때문에 undefined 출력.
// outer()의 outer은 전역의 LE.
// outer()에서 사용된 a는 전역에 존재하기 때문에 1 출력.
각각의 실행 컨텍스트는 LE 안에 record와 outer가 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있기 때문에 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.
상황에 따라 다른 의미를 가짐.
전역 공간에서의 this:
메소드로서 호출할 때 그 메소드 내부에서의 this:
var func = function (x) {
console.log(this, x);
};
// 호출의 주체가 없으므로 위 this는 global
func(1);
var obj = { method: func };
// obj가 method의 주체이므로 this는 obj
obj.method(2);
함수로서 호출할 때 그 함수 내부에서의 this:
콜백 함수 호출할 때 그 함수 내부에서의 this:
생성자 함수 내부에서의 this:
var obj1 = {
outer: function() {
console.log(this); // (1) outer
// AS-IS
var innerFunc1 = function() {
console.log(this); // (2) 전역객체
}
innerFunc1();
// TO-BE
var self = this;
var innerFunc2 = function() {
console.log(self); // (3) outer
};
innerFunc2();
}
};
// 메서드 호출 부분
obj1.outer();
일반 함수와 화살표 함수의 가장 큰 차이점: this binding 여부
var obj = {
outer: function() {
console.log(this); // (1) obj
var innerFunc = () => {
console.log(this); // (2) obj
};
innerFunc();
}
}
obj.outer();
호출하고자 하는 함수 끝에 .call()을 호출하고 첫 번째 매개변수로 this로 binding하고자 하는 객체를 전달.
// 1
var func = function (a, b, c) {
console.log(this, a, b, c);
};
// no binding
func(1, 2, 3);
// 명시적 binding
func.call({ x: 1 }, 4, 5, 6);
// 2
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
},
};
obj.method(2, 3);
obj.method.call({ a: 4 }, 5, 6);
call과 완전히 똑같은 기능인데 호출 함수의 매개변수들을 대괄호로 묶어줘야 함.
// 1
var func = function (a, b, c) {
console.log(this, a, b, c);
};
// no binding
func(1, 2, 3);
// 명시적 binding
func.apply({ x: 1 }, [4, 5, 6]);
// 2
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
},
};
obj.method(2, 3);
obj.method.apply({ a: 4 }, [5, 6]);
//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 call 또는 apply 메서드를 이용해 배열 메서드를 차용할 수 있어요.
var obj = {
0: "a",
1: "b",
2: "c",
length: 3,
};
Array.prototype.push.call(obj, "d");
console.log(obj); // { 0: 'a', 1: 'b', 2: 'c', 3: 'd', length: 4 }
var arr = Array.prototype.slice.call(obj);
console.log(arr); // [ 'a', 'b', 'c', 'd' ]
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
function Student(name, gender, school) {
Person.call(this, name, gender); // 여기서 this는 student 인스턴스!
this.school = school;
}
function Employee(name, gender, company) {
Person.apply(this, [name, gender]); // 여기서 this는 employee 인스턴스!
this.company = company;
}
var kd = new Student('길동', 'male', '서울대');
var ks = new Employee('길순', 'female', '삼성');
var numbers = [10, 20, 3, 26, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max, min);
// 근데 위보다 더 쉬운 방법은 spread operator의 활용
var max = Math.max(...numbers);
var min = Math.min(...numbers);
call, apply와 마찬가지로 this를 binding하는 함수.
차이점: 즉시 호출되지 않음.
목적:
1. 함수에 this를 미리 적용
// bind
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
// func에 this를 미리 binding해서 새로운 함수에 할당
// 새로운 함수의 this는 고정
var bindFunc1 = func.bind({ x: 1 });
bindFunc1(2, 3, 4, 5);
// 매개변수를 미리 지정 가능 (부분적으로)
var bindFunc2 = func.bind({ x: 1 }, 4, 5);
bindFunc2(6, 7);
모든 함수는 name 프로퍼티가 있는데 bind된 함수들은 name 프로퍼티가 bound라는 접두어가 붙음.
예)
console.log(func.name); // func
console.log(bindFunc1.name); // bound func
console.log(bindFunc2.name); // bound func
bind를 애초에 함수를 구현할 때 붙일 수 있음.
예)
var obj = {
outer: function() {
console.log(this);
var innerFunc = function () {
console.log(this);
}.bind(this); // innerFunc에 this를 결합한 새로운 함수를 할당
innerFunc();
}
};
obj.outer();