(이미지 출처 : https://velog.io/@imjkim49/자바스크립트-데이터-타입-정리)정적 타입 언어(C/C++,java)는 메모리의 낭비를 최소화하기 위해 데이터 타입별로 할당 메모리 영역을 정해놓았다. 숫자형 데이터를 저장할 경우 정해진 숫자만 허용하고 이상의 숫자를 입력하면 오류가 나거나 잘못된 값이 저장되므로 이를 대처하기 위해서는 사용자가 직접 변환해야하는(int) 번거로운 작업이 필요했다.
한편 메모리 용량이 과거보다 월등히 커진 상황에서 등장한 자바스크립트는 상대적으로 메모리 관리에 대한 압박에서 자유로워졌다. 때문에 메모리 공간을 좀 더 넉넉하게 할당했고 위와같은 번거로운 상황이 덜 발생하게 되었다.
/** 선언과 할당을 풀어 쓴 방식 */
var str;
str = 'test!';
/** 선언과 할당을 붙여 쓴 방식 */
var str = 'test!';
// 'abc'라는 값이 데이터영역의 @5002라는 주소에 들어갔다고 가정하자
var a = 'abc';
// 'def'라는 값이 @5002라는 주소에 추가되는 것이 아님
// @5003에 별도로 'abcdef'라는 값이 생기고 a라는 변수는 @5002 -> @5003
// 즉, "변수 a는 불변하다." 라고 할 수 있다.
// 이 때, @5002는 더 이상 사용되지 않기 때문에 가비지컬렉터의 수거 대상이 된다.
a = a + 'def';
// 참조형 데이터는 별도 저장공간(obj1을 위한 별도 공간)이 필요합니다!
var obj1 = {
a: 1,
b: 'bbb,
};
var a = 10; //기본형
var obj1 = { c: 10, d: 'ddd' }; //참조형
var b = a; //기본형
var obj2 = obj1; //참조형
b = 15;
obj2.c = 20;
기본형과 참조형의 변수 복사 시 주요한 절차의 차이점
// 기본형 변수 복사의 결과는 다른 값!
a !== b;
// 참조형 변수 복사의 결과는 같은 값!(원하지 않았던 결과)
obj1 === obj2;
// user라는 객체를 생성
var user = {
name: 'wonjang', // 최초에 이름은 wonjang 이다.
gender: 'male',
};
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 특징 : 객체의 프로퍼티(속성)에 접근해서 이름을 변경 -> 가변
var changeName = function (user, newName) { // 이름을 변경하는 함수, 'changeName'을 정의
var newUser = user; // 출력값 : 새로운 user 객체
newUser.name = newName;
return newUser;
};
var user2 = changeName(user, 'twojang');
// 아래 로직 수행 X
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // twojang twojang
console.log(user === user2); // true

// user 객체를 생성
var user = {
name: 'wonjang',
gender: 'male',
};
// 이름을 변경하는 함수 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티에 접근하는 것이 아니라, 아에 새로운 객체를 반환 -> 불변
var changeName = function (user, newName) {
return {
name: newName,
gender: user.gender,
};
};
// 변경한 user정보를 user2 변수에 할당
// 불변이기 때문에 user1은 영향 X
var user2 = changeName(user, 'twojang');
// 아래 로직이 수행
if (user !== user2) {
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // wonjang twojang
console.log(user === user2); // false 👍

var copyObject = function (target) {
var result = {};
// for ~ in 구문을 이용하여, 객체의 모든 프로퍼티에 접근가능
// 이 copyObject로 복사를 한 다음, 복사를 완료한 객체의 프로퍼티를 변경
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);
얕은 복사의 한계점 : 중첩된 객체에 대해서는 완벽한 복사가 불가능..하고, 중첩된 객체의 경우 참조형 데이터가 저장된 프로퍼티를 복사할 때, 주소값만 복사..
깊은 복사란?
내부의 모든 값들을 하나하나 다 찾아서 모두 복사하는 방법
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;
}
//결과 확인
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);
실행컨텍스트란 ?
자바스크립트의 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체를 뜻함
실행컨텍스트를 이해하기 위해서는 먼저 "콜 스택"에 대한 이해가 반드시 필요!!
콜 스택(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);

a. 현재 컨텍스트 내의 식별자 정보(record)를 갖고있음
1) var a = 3
b. 외부 환경 정보(=outer)를 갖고있음
a. VariableEnvironment와 동일하지만, 변경사항을 실시간으로 반영
//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
// <매개변수 적용 전>
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); // 예상) 1 → undefined → 2
}
a(1);
// <호이스팅 적용 후>-----------------------------------------------------------
function a () {
var x;
var x;
var x;
x = 1;
console.log(x);
console.log(x);
x = 2;
console.log(x); // 1, 1, 2
}
a(1);
예시)2
//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기
// <적용 전>
function a () {
console.log(b);
var b = 'bbb';
console.log(b);
function b() { }
console.log(b); // 예상) 에러(또는 undefined), ‘bbb’, b함수
}
a();
// <호이스팅 적용>------------------------------------------------------------
function a () {
var b; // 변수 선언부 호이스팅
function b() { } // 함수 선언은 전체를 호이스팅
console.log(b);
b = 'bbb'; // 변수의 할당부는 원래 자리에
console.log(b);
console.log(b); // b함수, ‘bbb’, ‘bbb’
}
a();
// <함수선언문을 함수 표현식으로 변경>--------------------------------------------
function a () {
var b; // 변수 선언부 호이스팅
var b = function b() { } // **함수 선언은 전체를 호이스팅**
console.log(b);
b = 'bbb'; // 변수의 할당부는 원래 자리에
console.log(b);
console.log(b);
}
a();
// 함수 선언문. 함수명 a가 곧 변수명
// function 정의부만 존재, 할당 명령이 없는 경우
function a () { /* ... */ }
a(); // 실행 ok
// 함수 표현식. 정의한 function을 별도 변수에 할당하는 경우
// (1) 익명함수표현식 : 변수명 b가 곧 변수명(일반적 case에요)
var b = function () { /* ... */ }
b(); // 실행 ok
// (2) 기명 함수 표현식 : 변수명은 c, 함수명은 d
// d()는 c() 안에서 재귀적으로 호출될 때만 사용 가능하므로 사용성에 대한 의문
var c = function d () { /* ... */ }
c(); // 실행 ok
d(); // 에러!
- 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
- 그 객체 안에는 3가지가 존재한다.
✓ VariableEnvironment
✓ LexicalEnvironment
✓ ThisBindings
- VE와 LE는 실행컨텍스트 생성 시점에 내용이 완전히 같고, 이후 스냅샷 유지 여부가 다르다.
- LE는 다음 2가지 정보를 가지고 있다.
✓ record(=environmentRecord) ← 이 record의 수집과정이 hoisting
✓ outer(=outerEnvironmentReference)
console.log(sum(1, 2));
console.log(multiply(3, 4));
function sum (a, b) { // 함수 선언문 sum
return a + b;
}
var multiply = function (a, b) { // 함수 표현식 multiply
return a + b;
}
// 함수 선언문은 전체를 hoisting
function sum (a, b) { // 함수 선언문 sum
return a + b;
}
// 변수는 선언부만 hoisting
var multiply;
console.log(sum(1, 2));
console.log(multiply(3, 4));
multiply = function (a, b) { // 변수의 할당부는 원래 자리
return a + b;
};
// 아래 코드를 여러분이 직접 call stack을 그려가며 scope 관점에서 변수에 접근해보세요!
var a = 1;
var outer = function() {
var inner = function() {
console.log(a); // undefined
var a = 3;
};
inner();
console.log(a); // outer 은 전역 영역 즉 1이 된다.
};
outer();
console.log(a); // 전역 영역 내부에서 해결한 1이 나옴
- 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
- 그 객체 안에는 3가지가 존재한다.
✓ VariableEnvironment
✓ LexicalEnvironment
✓ ThisBindings
런타임 환경?
여러분들이 javascript로 만들어놓은 프로그램이 구동중인 환경을 말하죠.
우리는 node 파일이름.js로 vscode 상에서 구동하고 있으니 node 환경이라고 할 수 있구요.
html 파일 안에 숨겨놓아서 크롬브라우저 등에서 연다고 한다면 브라우저 환경이라고 할 수 있겠네요.
// CASE1 : 함수
// 호출 주체를 명시할 수 없기 때문에 this는 전역 객체를 의미
var func = function (x) {
console.log(this, x);
};
func(1); // Window { ... } 1
// CASE2 : 메서드
// 호출 주체를 명시할 수 있기 때문에 this는 해당 객체(obj)를 의미
// obj는 곧 { method: f }를 의미하죠?
var obj = {
method: func,
};
obj.method(2); // { method: ƒ } 2
var obj = {
method: function (x) { console.log(this, x) }
};
obj.method(1); // { method: f } 1
obj['method'](2); // { method: f } 2
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); // obj1
var innerFunc = function() {
console.log(this); // 전역객체, obj2
}
innerFunc();
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod();
}
};
obj1.outer();
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();
// 별도 지정 없음 : 전역객체
setTimeout(function () { console.log(this) }, 300);
// 별도 지정 없음 : 전역객체
[1, 2, 3, 4, 5].forEach(function(x) {
console.log(this, x);
});
// addListener 안에서의 this는 항상 호출한 주체의 element를 return하도록 설계되었음
// 따라서 this는 button을 의미함
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a').addEventListener('click', function(e) {
console.log(this, e);
});
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
var func = function (a, b, c) {
console.log(this, a, b, c);
};
// no binding
func(1, 2, 3); // Window{ ... } 1 2 3
// 명시적 binding
// func 안에 this에는 {x: 1}이 binding돼요
func.call({ x: 1 }, 4, 5, 6}; // { x: 1 } 4 5 6
//예시
var func = function (a, b, c) {
console.log(this, a, b, c);
};
func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6
var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};
obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6
- 유새 배열의 조건에는 먼저 반드시 length가 필요하며, 이것이 없으면 유사배열 인식x
- index번호가 0번부터 시작해서 1씩증가해야한다 ! 안그러면 예상치 못한 결과 초래가능!
- 슬라이스slice() 함수는 배열로부터 특정 범위를 복사한 값들을 담고 있는 새로운 배열을 만드는데 사용한다.
첫번째 인자로 시작 인덱스(index), 두번쨰 인자로 종료 인덱스를 받으며, 시작 인덱스부터 종료 인덱스까지 값을
복사하여 반환한다.
//객체에는 배열 메서드를 직접 적용할 수 없어요.
//유사배열객체에는 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' ]
// 유사배열
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// 객체 -> 배열
var arr = Array.from(obj);
// 찍어보면 배열이 출력됩니다.
console.log(arr);
// student, Employee 모두 person 이다. name 과 gender 속성 모두 필요
// 따라서 student 와 Employee 인스턴스를 만들때 마다 세 가지 속성을 모두 각 생성자 함수에 넣기 보다는
// person 이라는 생성자 함수를 별도로 뺴는게 '구조화'에 좀 더 도움 !
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, 16, 45];
var max = min = numbers[0];
numbers.forEach(function(number) {
// 현재 돌아가는 숫자가 max값 보다 큰 경우
if (number > max) {
// max 값을 교체
max = number;
}
// 현재 돌아가는 숫자가 min값 보다 작은 경우
if (number < min) {
// min 값을 교체
min = number;
}
});
console.log(max, min);
//효율적임 !
var numbers = [10, 20, 3, 16, 45];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log("apply => ", max, min); // apply => 45, 3
// 펼치기 연산자(Spread Operation)를 통하면 더 간편하게 해결도 가능 (장벽을 뚫어버림)
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max min); // 45, 3
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
func(1, 2, 3, 4); // window객체
// 함수에 this 미리 적용
var bindFunc1 = func.bind({ x: 1 }); // 바로 호출되지는 않아요! 그 외에는 같아요.
bindFunc1(5, 6, 7, 8); // { x: 1 } 5 6 7 8
// 부분 적용 함수 구현
var bindFunc2 = func.bind({ x: 1 }, 4, 5); // 4와 5를 미리 적용
bindFunc2(6, 7); // { x: 1 } 4 5 6 7
bindFunc2(8, 9); // { x: 1 } 4 5 8 9
var func = function (a, b, c, d) {
console.log(this, a, b, c, d);
};
var bindFunc = func.bind({ x:1 }, 4, 5);
// func와 bindFunc의 name 프로퍼티의 차이를 살펴보세요!
console.log(func.name); // func
console.log(bindFunc.name); // bound func

var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
};
};
obj.outer();