TIL, 요즘 바닐라 자바스크립트 공부에 매진한다. 솔직히 오늘 공부는 에러를 맞닥뜨리기 보단, 생소한 개념과 용어들이 너무 많아, 내 두되가 못받아 드려 에러 투성이다. 오늘은 문제점 /시도/ 해결/ 알게된점 보단, 오늘 공부한 자바스크립트 헷갈리는 개념을 정리해 보고자 한다.
기본형과 참조형을 나누는 기준은 뭘까?
- 값의 저장 방식과 불변성 여부이다. ( 어떻게 저장되느냐, 어떻게 복제 되느냐)
복제 방식
기본형 : 값이 담긴 주소값을 바로 복제
참조형 : 값이 담긴 주소값들로 이루어진 묶음을 가리키는 주소값을 복제
불변성 여부
기본형: 불변성 ( 메모리 관점에서 볼때 불변하는 것을 뜻함)
참조형: 불변성을 띄지 않음
비트: 컴퓨터가 이해할 수 있는 가장 작은 단위 ( 0, 1로 이루어진 조각)
바이트: 0과 1만 표현하는 비트를 찾는게 부담스러워서 2^8 (비트 8개) 단위로 만들어버림
메모리: 바이트 단위로 구성되어있고, 모든 데이터는 바이트 단위의 식별자인 메모리 주소값을 가지고 있다.
8을 저장한다고 가정하자.
JS : let a = 8(8byte)
vs
JAVA
1. byte a = 8(1byte)
2. short a = 8(2byte)
3. int a = 8(4byte)
4. long a = 8(16byte)
java 나 c는 오래된 언어인데, 그 시절엔 지금처럼 빵빵한 메모리가 없었기에 데이터 타입 크기에 따라 다양하게 지정해 줬다고 한다! handling 할 요소들이 많아서 힘들 듯...
let testValue = 3
식별자 = 변수 명 / 변수 = 데이터
메모리를 기준으로 다시한번 생각해보는 두 가지 주요 개념
변수 vs 상수
1) 변수 : 변수 영역 메모리를 변경할 수 있음
2) 상수 : 변수 영역 메모리를 변경할 수 없음
불변하다 vs 불변하지 않다
1) 불변하다 : 데이터 영역 메모리를 변경할 수 없음
2) 불변하지 않다 : 데이터 영역 메모리를 변경할 수 있음
더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거하는 역할을 한다. 자바스크립트는 가비지 컬렉션 때문에 개발자가 명시적으로 메모리 관리 하지 않게 지원해준다. 자바스크립트 엔진에서 내부적으로 수행되며, 개발자는 가비지 컬렉션에 대한 직접적인 제어를 할 수 없다!
분명히 user 이름이랑 user2 (바뀐 이름) 이 같으면 안되는데! 코드를 보고 문제점을 찾아보자
// user 객체를 생성
var user = {
name: "wonjang",
gender: "male",
};
// 이름을 변경하는 함수, 'changeName'을 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티(속성)에 접근해서 이름을 변경 했음 -> 가변
var changeName = function(user, newUser) {
var newUser = user;
newUser.name = newUser;
return newUser;
};
// 변경한 user정보를 user2 변수에 할당하겠습니다.
// 가변이기 때문에 user도 영향을 받게 될거에요.
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 객체를 생성
var user = {
name: "wonjang",
gender: "male",
};
// 이름을 변경하는 함수, 'changeName'을 정의
// 입력값 : 변경대상 user 객체, 변경하고자 하는 이름
// 출력값 : 새로운 user 객체
// 특징 : 객체의 프로퍼티(속성)에 접근해서 이름을 변경 했음 -> 가변
var changeName = function(user, newUser) {
return {
name: newName,
gender: user.gender,
};
};
// 변경한 user정보를 user2 변수에 할당하겠습니다.
// 가변이기 때문에 user도 영향을 받게 될거에요.
var user2 = changeName(user, "twojang");
// 결국 아래 로직은 skip하게 될겁니다.
if (user !== user2) {
console.log("유저 정보가 변경되었습니다.");
}
console.log(user.name, user2.name); // twojang twojang
console.log(user === user2); // true
하지만 이 방법은 최선이 아니야... 속성값이 어마어마하게 많으면, return 값이 어마어마하게 많아지고, 하드코딩으로 매꿔야 하기 때문에 유지보수 면에서 너무 엉망이다 ㅠㅠ
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);
여기에도 문제점이 있다... 중첩된 객체는 복사해올 수 없다는 점.... 그래서 필요한게
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);
불변성을 유지할 수 있게 되었다!
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);
console.log(n === null);
실행컨텍스트가 뭐야?
실행할 코드에 제공할 환경 정보들을 모아놓은 "객체"다!
1.선언된 변수를 위로 끌어올리구 = 호이스팅(hoisting)
2.외부 환경 정보를 구성하구
3.this 값을 설정
이런 현상들 때문에 JS에서는 다른 언어랑은 다른 특징들이 나타난다!
실행 컨텍스트를 이해하기 위해서는, 콜 스택
에 대한 이해가 반드시 필요하다. 그 전에 “스택”
이라는 개념에 대해서 먼저 이해해보자
간단명료 하게! 스택은 후입 선출 / 큐는 선입 선출, 우린 스택 개념을 사용하기 때문에, 후입 선출 형태를 갖고 있다고 생각해 놓자!
동일 환경에 있는 코드를 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이것을 위에서 설명한 ‘스택’의 한 종류인 콜스택에 쌓아올린다. 가장 위에 쌓여있는 컨텍스트와 관련된 코드를 실행하는 방법으로 코드의 환경 및 순서를 보장할 수 있다!
var a = 3
var a
를 의미요약하자면 V/E는 record 와 outer를 갖고 있다. (식별자 와 외부환경 정보), 그리고 L/E랑 거의 같다
담기는 항목은 완벽하게 동일하다. 처음에 생성될땐 똑같지만, 시간이 지나면서 변경사항이 생겼을때 차이가 생김 ! 스냅샷 유지여부는 다음과 같이 다르다!
실행 컨텍스트는 생성할 때. VE에 정보를 먼저 담은 다음, 얘를 그대로 복사해서 LE를 만들고 그 이후엔 LE를 활용한다!
Record : 식별자 정보들이 저장(수집)되고. 기록된다
수집 대상 정보 : 함수에 지정된 매개변수 /식별자/ 함수 자체/ var로 선언된 변수/ 등
컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집
- 💡 순서대로 수집한다고 했지, 코드가 실행된다고 하지는 않음!!!
hoist : 끌어올리다.
🔥식별자정보를 맨 위로 끌어 올리는 것을 말한다 !-> record를 수집하는 과정
// 함수 선언문. 함수명 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)
outer의 역할을 한 마디로 정의하자면
스코프 체인이 가능토록 하는 것(외부 환경의 참조정보)라고 할 수 있다.
"외부 환경의 참조정보 라는 말에 집중"
스코프 : 식별자에 대한 유효범위
스코프 체인 : 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것
각각의 실행 컨텍스트는 LE 안에 record와 outer를 가지고 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있으니 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.
먼저 학습하는데 길을 잃지 말자
- 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
- 그 객체 안에는 3가지가 존재한다.
✓ VariableEnvironment
✓ LexicalEnvironment
✓ ThisBindings
런타임, 코드가 돌아가는 환경
전역 환경에서 this -> 노드(global 객체), 브라우저(window 객체) 이다.
함수 : this -> 전역 객체 (why? 호출의 주체를 명시할 수 없기 때문에)
메서드 : 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();
obj1 에 대한 this를 self에 저장 해놓고 콘솔에 self 를 찍음 -> 흐름을 유지하는 this를 찍는것과 같은 효과! 이런 방식으로 우회해줌!
AS-IS : 기존 것
TO-BE : 이후 것
ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제 때문에 화살표함수를 도입했다!!!
면접시⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
일반 함수와 화살표 함수의 가장 큰 차이점은 무엇인가요? 라고 물으면
-> this binding 여부가 가장 적절한 답안이 될것임!!!
콜백 함수도 함수기 때문에 this는 전역 객체를 참조하지만
콜백함수를 넘겨받은 함수에서 콜백 함수에 별도로 this를 지정한 경우는 예외적으로 그 대상을 참조하게 되어있다.
// 별도 지정 없음 : 전역객체
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
- 호출 주체인 함수를 즉시 실행하는 명령어
- call명령어를 사용하여, 첫 번째 매개변수에 this로 binding할 객체를 넣어주면 명시적으로 binding할 수 있음
// 명시적 this binding
//call
var func = function (a, b, c) {
console.log(this, a, b, c);
};
// no binding
// func(1, 2, 3); // 글로벌 객체 ~ 1 2 3 찍힐것임
//명시적 binding
func.call({x: 1},4, 5, 6); // {x : 1} 4 5 6 찍힘 (명시적 바인딩)
call과 비슷하지만, this에 binding할 객체는 똑같이 넣어주고 나머지 부분만 배열 형태로 넘겨준다.
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 *이부분
물론 this binding을 위해 call, apply method를 사용하기도 하지만 더 유용한 측면도 있다.
유사배열객체(array-like-object)에 배열 메서드를 적용
사실, call/apply를 통해 this binding을 하는 것이 아니라
객체 → 배열
로의 형 변환 만을 위해서도 쓸 수 있지만 원래 의도와는 거리가 먼 방법이라 할 수 있다 ES6는 이를 보안해서!!!
Array.from
이라는 방법을 제시해 객체를 배열로 전환해줌// 유사배열
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
// 객체 -> 배열
var arr = Array.from(obj);
// 찍어보면 배열이 출력됩니다.
console.log(arr);
function Student(name, gender, school) {
this.name = name;
this.gender = gender;
this.school = school;
};
function Employee(name, gender, school) {
this.name = name;
this.gender = gender;
this.company = company;
};
var kd = new Student("길동","male","서울대")
var ks = new Student("길순","female","삼성")
중복되는 인자 name, gender
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', '삼성');
Student, Employee 모두 Person.name과 gender 속성 모두 필요한데 그러니 Student와 Employee 인스턴스를 만들 때 마다 세 가지 속성을 모두 각 생성자 함수에 넣기 보다는 Person이라는 생성자 함수를 별도로 빼는게 ‘구조화’에 도움이 더 되니 이렇게 사용해 보자😉
//비효율
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(max, min);
// 펼치기 연산자(Spread Operation)를 통하면 더 간편하게 해결도 가능해요
const numbers = [10, 20, 3, 16, 45];
const max = Math.max(...numbers);
const min = Math.min(...numbers);
console.log(max min);
내용이 머릿속에 아웃라인만 그려진 느낌, 입밖으로 뱉기 힘든데, 일단 복습해 보자,
다음 TIL에도 이어질 예정