1. 변수
- let, const, var
- var, let 비슷함
var
- 한 번 선언된 변수를 다시 선언할 수 있음
- 선언 전 사용 가능 : 호이스팅으로 인해 선언이 최상위로 끌어올려지기 때문
- 선언은 호이스팅 되지만 할당은 호이스팅 되기 않기 때문에 선언 후 할당 전까지는 undefined 상태임
호이스팅
: 스코프 내부 어디서든 변수 선언은 최상위에 선언된 것 처럼 행동
let
- 재선언 불가
- let도 호이스팅 되지만 Temporal Dead Zone(TDZ) 때문에 undefined가 아니라 에러가 발생함
TDZ
- let, const는 TDZ 영역을 갖는데, TDZ 영향을 받기 때문에 할당 하기 전에는 사용할 수 없음
- 이는 코드를 예측하고 잠재적인 버그를 줄일 수 있음
변수의 생성 과정
- 선언 단계
- 초기화 단계
- 할당 단계
var의 생성 과정
- 선언 및 초기화 단계
- 할당 단계
- 선언과 초기화가 동시에 일어남
- 그래서 할당 전에 호출하면 에러가 아닌 undefined가 나옴
let의 생성 과정
- 선언 단계
- 초기화 단계
- 할당 단계
- 선언과 초기화가 분리되어 진행됨
- 그래서 할당 전에 호출하면 에러가 발생함
- 호이스팅 되면서 선언 단계가 이루어지지만, 초기화 단계는 실제 코드에 도달했을때 이루어지기 때문에 레퍼런스 에러가 발생함
const의 생성 과정
- 선언 + 초기화 + 할당 동시에 일어남
const gender;
- 위 코드처럼 선언하면서 할당을 같이 안해주면 에러가 발생함
스코프
var
- 함수 스코프 : 함수 내에서 선언된 변수가 지역변수가 됨
let, const
- 블록 스코프 : 모든 코드 블록에서 선언된 변수는 코드 블록 내에서만 유효하며, 외부에서는 접근할 수 없음
- 코드 블록 내부에서 선언한 변수 -> 지역변수
- 코드 블록 : 함수, if문, for문, while문, try-catch문
스코프 예시
const age = 28;
if(age>19){
var txt = "성인";
}
console.log(txt); // 성인
- 위 코드에서 var를 사용하면 if문 안에서 선언된 변수도 if문 밖에서 사용 가능 (함수 스코프이기 때문)
- 하지만 let, const는 블록 내부에서 선언된 변수는 블록 내부에서만 유효하기 때문에 오류 발생 (블록 스코프이기 때문)
함수 스코프 예시
function add(num1, num2){
var result = num1 + num2;
}
add(2,3);
console.log(result); // 에러
- var도 함수 밖을 벗어나면 사용할 수 없음
- 유알하게 벗어날 수 없는 스코프가 함수라고 생각하기
정리
- var보다 let, const 사용 권장
- 예측 가능한 결과를 내고 버그를 줄일 수 있기 때문
2. 생성자 함수
객체 리터럴
let user = {
name: "hyebeen",
age: "28",
}
- 이렇게 객체를 만드는 것을 객체 리터럴이라고 함
- 비슷한 객체를 여러 개 만드는 상황이 많이 발생함
- EX. 회원, 상품 등
- 그럴 때 사용 가능한 것이 생성자 함수임
생성자 함수
- 보통 첫 글자를 대문자로 해서 함수를 만들어줌
function User(name, age){
this.name = name;
this.age = age;
}
let user1 = new User('hyebeen', 28);
let user2 = new User('Jeong', 30);
let user3 = new User('Lee', 20);
- User라는 함수를 만들어서 이름, 나이를 매개변수로 받은 후 this에 넣어줌
- new 연산자를 사용해서 함수를 호출함
- 각각 다른 변수명으로 3개를 연달아 호출함
- 간편하게 비슷한 객체 3개를 만듦
- 생성자 함수는 붕어빵 틀이나 와플 팬이라고 생각하기
- 필요한 재료들을 넣고 찍어냄
- 위 코드에서 필요한 재료들은 name, age였음
- 생성되는 객체들은 와플임
생성자 함수 동작 과정
function User(name, age){
// this = {}
this.name = name;
this.age = age;
// return this;
}
new 함수명();
- new 함수명()을 실행하면 아래와 같은 과정을 거침
- 빈 객체 만들고 this에 할당
- 함수 본문을 실행하면서 this에 프로퍼티들을 추가함
- 마지막으로 this를 반환함
- 실제로 this = {} , return this;는 코드에 없음
- new를 붙여서 실행하는 순간 저 방식으로 알고리즘이 동작함
- 이렇게 객체를 만들면 하나씩 객체를 쓰는 것 보다 훨씬 바르고, 일관성이 있게 객체 생성 가능
- 어떤 함수라도 new를 붙여서 실행하면 저 알고리즘이 동작함
- 그래서 생성자 함수는 첫 글자를 대문자로 하는게 관례임
메소드 추가하기
function User(name, age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
let user5 = new User("Hee", 50);
user5.sayName() // "Hee"
- this로 할당된 객체에 sayName 메소드를 추가하고 user를 하나 더 생성함
- user5.sayName()을 호출했을 때 this는 .앞의 user5가 됨
- 그래서 this.name은 user5의 name이 됨
- new를 안붙이면 일반 함수가 되기 때문에 생성자 함수는 꼭 new를 붙여줘야 함
정리
- 생성자 함수는 비슷한 객체를 여러개 만들 때 유용하게 사용 가능
3. 객체 메소드(Object methods), 계산된 프로퍼티(Computed property)
Computed property
let a = 'age';
const user = {
name : "hyebeen",
age : 28
}
- 위 코드를 아래처럼 age 대신 [a] 이렇게 사용 가능함
let a = 'age';
const user = {
name : "hyebeen",
[a] : 28
}
- 대괄호로 묶어주면 a 라는 문자열이 아니라 변수 a에 할당된 값이 들어감
- 이를 computed property라고 부름
const user = {
[1 + 4] : 5,
["안녕" + "하세요"] : "Hello"
}
user
// {5: 5, 안녕하세요: "Hello"}
다른 예시
function makeObj(key, val) {
return {
[key]: val,
};
}
const obj = makeObj("나이", 28);
console.log(obj);
// {나이: 28}
- **어떤게 key가 될지 모르는 객체를 만들 때 유용함**
#### 객체에서 사용 가능한 Methods
> **Object.assign() : 객체 복제**
```JS
const user = {
name : "hyebeen",
age : 28
}
- 객체를 복제하고 싶다고 아래와 같이 복제하면 안됨
const cloneUser = user;
- user 변수에는 객체 자체가 들어가 있는게 아니라 객체가 저장된 메모리 주소인 객체에 대한 참조값이 저장됨
- 그래서 위처럼 복사하면 객체가 복사되면서 들어가는게 아니라 그 참조값만 복사됨
- 하나의 객체에 두 변수가 접근하고 있는 것임
- 동일하게 복제하려면 Object.assigh() 메소드를 써야 함
const newUser = Object.assign({}, user);
- 첫 번째 매개변수인 {} 빈 객체는 초기값이고, 두 번째 매개변수로 들어온 객체들이 초기값에 병합됨
- 이렇게 하면 빈 객체에 user가 병합되기 때문에 복제가 됨
- {} + {name : "heybeen", age : 28} = {name : "heybeen", age : 28}
- 이름을 바꿔도 user는 변함이 없음
- 같은 객체가 아니기 때문
const newUser = Object.assign({}, user);
newUser.name = 'Tom';
console.log(user.name); // "hyebeen"
const newUser = Object.assign({ gender:'male' }, user);
- 이렇게 하면 성별값만 있는 객체가 user를 병합하는 것이고,
총 3개의 프로퍼티를 가지게 됨
- { gender:'male' } + {name : "hyebeen", age : 28} = {gender: 'male', name : "hyebeen", age : 28}
- 만약 병합을 하는데 key가 같다면? -> 덮어쓰게 됨
Object.assign({ name: 'Tom' }, user);
- { name :'Tom' } + {name : "hyebeen", age : 28} = {name : "hyebeen", age : 28}
- 먼저 있던 name: 'Tom'이 새로 병합된 객체의 name값인 'hyebeen'이 덮혀버림
const user = {
name : 'Mike'
}
const info1 = {
age : 30
}
const info2 = {
gender : 'male'
}
Objcet.assign(user, info1, info2)
- 이렇게 하면 user 객체에 info1이 먼저 병합되고, 그 다음에 info2가 병합된 객체에 병합됨
Object.keys() : 키 배열 반환
- 객체 프로퍼티의 key를 배열로 반환함
- 객체를 Object.keys의 인수로 전달하면 됨
- 그럼 key들이 배열로 만들어져 반환됨
const user = {
name : "hyebeen",
age : 28,
gender: 'female',
}
Object.keys(user);
// ["name", "age", "gender"]
Object.values() : 값 배열 반환
- 반대로 값들만 반환하고 싶다면 Object.values() 사용
- 객체의 value에 해당하는 값들만 배열로 반환됨
const user = {
name : "hyebeen",
age : 28,
gender: 'female',
}
Object.values(user);
// ["hyebeen", 28, female]
Object.entries() : 키/값 배열 반환
- key, value 모두 배열로 반환 받고 싶을 때 사용
- 키와 값이 쌍으로 묶여서 배열로 반환됨
- 배열 안에 값, 키가 들어있는 배열 3개가 들어있음
const user = {
name : "hyebeen",
age : 28,
gender: 'female',
}
Object.entries(user);
// [["name","hyebeen"],["age", 28],["gender", female]]
Object.fromEntries() : 키/값 배열을 객체로
- Object.entries()와 반대 개념
- 키,값을 쌍으로 묶은 배열들을 넣어주면 객체로 만들어줌
const arr = [
["name","hyebeen"],
["age", 28],
["gender", female]
];
Object.fromEntries(arr);
// {name : "hyebeen", age : 28, gender: 'female',}
4. 심볼(Symbol)
property key : 문자형
const obj = {
1: '1입니다.',
false: '거짓'
}
Object.keys(obj); // ["1", "false"]
obj['1'] // '1입니다.'
obj['false'] // '거짓'
- 이렇게 숫자형, boolean형으로 만들어도 Object.keys()로 가져와보면 문자형으로 반환됨
- 실제로 접근할떄도 문자'1'이나 문자'false'로 접근 가능
- 이렇게 객체 프로퍼티 키는 문자형으로 가능하고, 문자형 이외에 심볼형도 가능함
Symbol
심볼 만드는 법
const a = Symbol(); // new를 붙이지 않음
const a = Symbol(); // new를 붙이지 않음
const b = Symbol();
console.log(a)
Symbol()
console.log(b)
Symbol()
- 생긴건 똑같은데 일치연산자로 확인해보면 false가 나옴
a === b; // false
a == b; // false
Symbol형 특징
유일성 보장
const id = Symbol('id');
- 전체 코드 중 딱 하나임
- 심볼로 만들 때 설명을 붙여줄 수 있음 (디버깅 시 편리함)
- 방법은 문자열을 전달해주면 되는데, 이 문자열은 심볼 생성에는 어떠한 영향도 주지 않음
const id = Symbol('id');
const id2 = Symbol('id');
- 이렇게 똑같이 생긴 심볼을 생성해서 보면
생김새는 같지만 다르다고 나옴
id === id2 // false
id == id2 // false
const id = Symbol('id');
const user = {
name : "hyebeen",
age : 28,
[id] : 'myid'
}
user
// {name: "hyebeen", age: 28, Symbol(id): "myid"}
usre[id]
// "myid"
Object.keys(user); // ["name", "age"]
- Object.keys, Object.values() 와 같은 메소드들은 key가 심볼인 프로퍼티는 건너 뜀
- for(let a in user){} 처럼 for-in을 써도 심볼은 건너 뜀
- 그렇다면 심볼은 어디에서 사용할까?
Symbol 사용 상황
- 특정 객체의 원본 데이터는 건들지 않고 속성 추가 가능
- 다른 사람들이 만들어 놓은 객체에 자신만의 속성을 추가해서 덮어버리는 경우가 발생하면 안됨
Symbol.for() : 전역 심볼
- 심볼은 이름이 같더라도 모두 다름
- 가끔 전역변수처럼 이름이 같으면 같은 객체를 가리켜야 될 때가 있음
- 그 때 Symbol.for() 사용하면 됨
- 전역 심볼이라 부르고, 코드 어디에서나 사용 가능
- Symbol.for()을 사용하면?
- 하나의 심볼만 보장받을 수 있음
- 없으면 만들고, 있으면 가져오기 때문
- Symbol 함수는 매번 다른 Symbol 값을 생성하지만,
Symbol.for 메소드는 하나를 생성한 뒤 키를 통해 같은 Symbol을 공유함
const id1 = Symbol.for('id');
const id2 = Symbol.for('id');
id1 === id2; // true
Symbol.keyFor(id1) // 'id'
- 이름을 얻고 싶다면 Symbol.keyFor을 사용하면 됨
- 변수를 넣어주면 생성할 때 지었던 이름을 알려줌
description
- 전역 심볼이 아닌 심볼은 keyFor을 사용할 수 없는 대신description으로 이름을 알 수 있음
const id = Symbol('id 입니다.');
id.description; // 'id 입니다.'
숨겨진 Symbol key 보는 법
- 심볼은 완전히 숨길 수 있는 방법은 없음
- Object.getOwnPropertySymbols()를 사용하면 심볼들만 볼 수 있음
const id = Symbol('id');
const user = {
name : "hyebeen",
age : 28,
[id] : 'myid'
}
Object.getOwnPropertySymbols(user); // [Symbol(id)]
Reflect.ownKeys(user); // ["name", "age", Symbol(id)]
- Reflect.ownKeys() 을 사용하면 심볼형 key를 포함한 객체의 모든 key를 보여줌
- 하지만 대부분의 라이브러리, 내장함수 들은 이런 메서드들을 사용하지 않음
- 유일한 프로퍼티를 주고 싶을 때 심볼을 사용하면 됨
참고