유튜버 코딩앙마님의 자바스크립트 중급 강좌를 보고 정리한 내용입니다.
https://www.youtube.com/watch?v=4_WLS9Lj6n4&list=PLu8qrPjYh1hcuFnkwiaEDb578Jklc1EWh&index=1&t=639s
var과 let의 차이점
var은 재선언이 가능하지만 let은 불가능하다.
var name = 'mike';
var name = 'snake'; //가능
let name = 'mike';
let name = 'snake'; //에러
hosting
:스코프 내부 어디서든 변수 선언은 최상위에 선언된 것처럼 행동
var은 선언하기 전에 사용할 수 있다.
console.log(name); //undefined
var name = 'mike';
이렇게 var을 선언하기 전에 호출해도 에러가 나지 않지만 값이 할당되어 있진 않다. 왜냐하면
var name; //name이 선언
console.log(name); //undefined
name = 'mike'; //name에 값이 할당
var hoisting 되어 이렇게 동작하기 때문이다. var 변수 선언이 최상위로 올라가게 된다. 하지만 변수에 값이 할당되는 것은 세 번째 줄이기 때문에 undefined가 뜬다. 즉 변수 선언은 호이스팅되지만 할당은 호이스팅되지 않는다.
let과 const도 호이스팅이 안 되는 것은 아니다. let과 const 역시 var처럼 호이스팅된다.
console.log(name);//Reference Error(참조에러)
let name = 'mike';
하지만 에러가 나는 이유는 Temporal Dead Zone(TDZ) 때문이다.
TDZ란?
TDZ란 변수 선언되기 전의 영역으로 선언 전에 변수에 접근하는 것을 금지한다. 이는 코드를 예측 가능하게 하고 잠재적인 버그를 줄일 수 있다.
let age = 30;
function showAge(){
console.log(age); //Temoral Dead Zone
let age = 20;
}
showAge();
위의 코드는 에러가 발생한다. 호이스팅은 스코프 내에서 이루어지는데 현재 showAge 함수가 하나의 스코프이다. 따라서 함수 안에서 let이 호이스팅되었고 변수가 선언되기 전인 console.log(age) 부분은 TDZ이기 때문에 에러가 난다. 만약 호이스팅이 되지 않는다면 age = 30;이 적용되어 출력되지만 호이스팅되었기 때문에 에러가 난다.
변수의 생성과정
1. 선언단계
2. 초기화 단계
3. 할당 단계
var
1. 선언 및 초기화 단계(초기화: undefined를 할당해주는 단계)
2. 할당 단계
→var은 선언과 초기화가 동시에 이루어진다.
let
1. 선언 단계
2. 초기화 단계
3. 할당 단계
→let은 선언과 초기화가 따로 이루어진다. 따라서 let 선언은 호이스팅되지만 초기화는 실제 코드 부분에서 이루어지기 때문에 에러가 나는 것이다.
const
1. 선언+초기화+할당
→선언, 초기화, 할당이 동시에 일어난다.
let name;
name = 'Mike';
var age;
age = 30;
const gender;
gender = 'male'; //에러 발생
위 코드는 const 부분에서 에러가 발생한다.
선언과 동시에 할당이 이루어지지 않았기 때문.
var
:함수 스코프
함수 내에서 선언된 변수만 지역변수가 된다.
예를 들어 if문 안에서 선언한 var 변수는 if 문 밖에서 접근이 가능하다.
const age = 30;
if(age>19){
var txt = '성인';
}
console.log(txt); //'성인'
let, const
: 블록 스코프
함수, if문, for문, while문, try/catch문 등 블록(중괄호) 내에서 선언된 변수는 그 블록 스코프 내에서만 유효하며 외부에서는 접근할 수 없는 것
ex)
function add(){
//Block-level Scope
}
if(){
//Block-level Scope
}
for(let i=0; i<10; i++){
//Block-level Scope
}
생성자 함수
function User(name, age){ //첫 글자는 대문자로
this.name = name;
this.age = age;
}
let user1 = new User('Mike', 30); //new 연산자를 사용해서 호출
let user2 = new User('Jane', 22);
let user3 = new User('Tom', 17);
이러한 생성자 함수가 실제로 어떻게 동작하는지 알아보자.
function User(name, age){
this = {}
this.name = name;
this.age = age;
return this;
}
new 함수명 ();
new 함수명으로 호출이 되면 새로운 객체를 만들고 그 객체 안에 name과 age를 넣은 다음 this를 리턴하는 것이다.
객체 메소드
Computed property(계산된 프로퍼티)
//1.
let a = 'age';
const User = {
name: 'Mike',
age : 30
}
//2.
let a = 'age';
const User = {
name: 'Mike',
[a] : 30 //age: 30
}
1번 방법과 2번 방법의 결과는 같다. [a]는 a라는 문자열이 아니라 변수 a의 할당된 값이 들어간다.
function makeObj(key, val){
return{
[key]: val
};
}
const obj = makeObj("성별", "male");
console.log(obj);
이렇게 어떤 값이 key 값으로 올지 모를 때 유용하게 사용할 수 있다.
Methods
▷Object.assign() : 객체 복제
const User = {
name: 'Mike',
age : 30
}
const CloneUser = user; // X
이렇게 하면 객체가 복제되는 것일까? 아니다. 객체의 이름에는 객체 자체가 저장되어있는 것이 아니라 객체가 저장되어있는 레퍼토리의 주소가 저장되어있다. 따라서 저렇게 복제를 하면 객체가 복제가 되는 것이 아니라 객체가 담긴 주소가 복제된다. 즉,
이런 상태로 저장되어있는 것이기 때문에 CloneUser에서 이름을 CloneUser.name = 'nick'으로 이름을 바꾸면 User 객체의 name도 바뀐다.
따라서 객체를 복제하려면,
const newUser = object.assing({}, user);
이렇게 메소드를 이용해서 복제해야 한다. 여기서 {} 빈 개체는 초기값으로 두 번째 매개변수의 객체가 이 초기값으로 들어가게 된다.
초기값에 따라 객체의 property를 추가할 수 있다.
Object.assign({gender: 'male'}, user);
이렇게 초기값에 gender property를 주게 되면
{gender: 'male'} + {name: 'mike', age: '30'} = {gender: 'male', name: 'mike', age: '30'} 이렇게 3개의 property를 가지게 된다.
만약 병합을 하는데 키가 같다면?
Object.assign({ name: 'nick'}, user);
초기값과 복제하려는 객체의 key가 같다면 덮어쓰게 된다. 따라서
{name: 'mike', age: '30'} 이렇게 복제된다.
여러 개의 객체를 합쳐서 복제할 수도 있다.
const user = {
name: 'Mike'
}
const info1 = {
age: 30
}
const info2 = {
gender : 'male'
}
Object.assign(user, info1, info2);
▷Object.keys(): 객체의 키를 배열로 변환
const user = {
name: 'Mike',
age: 30,
gender: 'male'
}
Object.keys(user);
//["name", "age", "gender"]
▷Object.values(): 객체의 값을 배열로 반환
const user = {
name: 'Mike',
age: 30,
gender: 'male'
}
Object.values(user);
//["Mike", 30, "male"]
▷Object.entries(): 객체의 키/값 배열로 반환
const user = {
name: 'Mike',
age: 30,
gender: 'male'
}
)Object.entries(user);
// [["name", "Mike"], ["age", 30], ["gender", "male"]]
▷Object.fromEntries(): 이중 배열의 키/값 배열을 객체로 변환
const arr =
[
["name", "Mike"],
["age", 30],
["gender", "male"]
];
Object. formEntries(arr);
//{name: 'Mike', age:30, gender: 'male'}
심볼(Symbol)
▷property key: 문자형
:객체 프로퍼티는 기본적으로 문자형이다.
const obj = {
1: '1입니다.',
flase: '거짓'
}
Object.keys(obj); //["1", "false"];
obj['1'] //"1입니다."
obj['false'] "거짓"
key를 메소드를 이용해서 배열로 반환될 때 문자형으로 반환되고, value에 접근할 때도 문자형으로 접근이 가능하다.
이렇게 객체 property로 사용할 수 있는 것은 문자형 말고도 심볼이 있다.
▷property key: 심볼형
const a = Symbol(); //new를 붙이지 않는다.
const b = Symbol();
a === b; //거짓
심볼은 유일한 식별자를 만들 때 사용하여 유일성이 보장된다.
const id = Symbol('id');
const id2 = Symbol('id');
Symbol를 만들 때 이렇게 괄호 안에 설명을 넣을 수도 있다. 디버깅할 때 편하기 때문에 설명을 넣는 것이 좋은데 이 문자열은 심볼 생성에는 아무런 영향도 미치지 않는다.
위의 id, id2와 같이 똑같은 설명을 넣었을 땐 어떻게 될까?
이렇게 똑같은 형태로 출력되지만
같은 값은 아니라는 것을 알 수 있다.
이를 객체의 key 값으로 사용해보자.
const id = Symbol('id');
const user = {
name: 'Mike',
age: 30,
[id] : 'myid'
}
//user > {name: "Mike", age: 30, Symbol(id): "myid"};
이렇게 심볼을 키 값으로 넣으면 정상적으로 출력된다.
const id = Symbol('id');
const user = {
name: 'Mike',
age: 30,
[id] : 'myid'
}
Object.keys(user); //["name", "age"]
이렇게 키 값을 배열로 반환하면 심볼이 나오지 않는 것을 볼 수 있다. 이 외에도 values, entries와 같은 메소드는 키가 심볼형인 값은 건너뛴다.
또한 for in문에서도 건너뛴다.
이러한 심볼 값은 특정 개체의 원본 데이터를 건드리지 않고 값을 추가할 때 사용한다.
const user = {
name: 'Mike',
age: 30
}
const id = Symbol('id');
user[id] = 'myid';
이렇게 심볼은 이름이 같더라도 모두 다른 존재이다.
그런데 전역 변수처럼 이름이 같으면 같은 객체를 가리켜야 할 때가 있다.
이럴 때 사용하는 것이 전역 심볼이다.
▷Symbol.for(): 전역 심볼
-하나의 심볼만 보장받을 수 있다.
-없으면 심볼을 만들고 있으면 있는 심볼을 가져온다.
-symbol 함수는 매번 다른 Symbol 값을 생성하지만 Symbol.for 메소드는 하나를 생성한 뒤 키를 통해 같은 Symbol을 공유
const id1 = Symbol.for('id');
const id2 = Symbol.for('id');
id1 === id2; //true
Symbol에서는 false였지만 Symbol.for에서는 true가 된다.
Symbol.keyFor()을 사용해서 변수 이름을 넣어주면 설정했던 key값을 보여준다.
Symbol.keyFor(id1) //"id"
전역 심볼이 아닌 심볼은 keyFor을 사용할 수 없다. 대신 description으로 key값을 보여줄 수 있다.
const id = Symbol('id 입니다.');
id.description; //"id 입니다."
심볼을 완전히 숨길 수는 없는데 숨겨진 Symbo key를 보는 방법은
Object.getOwnPropertySymbols();를 통해 볼 수 있다.
const id = Symbol('id');
const user = {
name: 'Mike';
age: 30,
[id]: 'myid'
}
Object.getOwnPropertySymbols(user); //[Symbol(id)]
Reflect.ownKeys(user); //["name", "age", Symbol(id)]
또 Reflect.ownKeys를 사용하면 심볼을 포함한 모든 key 값을 볼 수 있다.
#심볼 사용 예시
//다른 개발자가 만들어 놓은 객체
const user = {
name: "Mike",
age: 30
}
//내가 작업
//사용자가 접속하면 보는 메세지
for(let key in user){
console.log(`His ${key} is ${user[key]}.`);
}
이런 상황일 때 내가 객체의 값을 추가하면
//다른 개발자가 만들어 놓은 객체
const user = {
name: "Mike",
age: 30
}
//내가 작업
user.showNmae = function (){};
//사용자가 접속하면 보는 메세지
for(let key in user){
console.log(`His ${key} is ${user[key]}.`);
}
이렇게 사용자가 이상한 화면을 보게 된다. 따라서 내가 추가하고 싶은 내용은 심볼로 넣으면 된다.
//다른 개발자가 만들어 놓은 객체
const user = {
name: "Mike",
age: 30
}
//내가 작업
const showName = Symbol('show name');
user[showName] = function (){
console.log(this.name);
}
//사용자가 접속하면 보는 메세지
for(let key in user){
console.log(`His ${key} is ${user[key]}.`);
}
이렇게 심볼은 for in문에서 찾을 수 없다.
여기서 내가 추가한 심볼 값을 확인하고 싶다면
user[showName]();
이렇게 정상적으로 작동하는 것을 확인할 수 있다.
이렇게 심볼을 사용하면 다른 사람이 만들어 놓은 객체를 이용할 때 변수의 이름이 겹치거나 덮어쓸 격정을 하지 않아도 되고 원본 객체에 영향을 미치지도 않는다.
'개발 공부 > JavaScript' 카테고리의 다른 글
JavaScript 이론 정리 #4 [구조 분해 할당, 나머지 매개 변수, 전개구문] (2) | 2022.08.27 |
---|---|
JavaScript 이론 정리 #3 [배열 메소드] (0) | 2022.08.17 |
JavaScript 이론 정리 #2 [number 메소드, Math 메소드, string 메소드] (0) | 2022.08.16 |
자바스크립트로 이미지 슬라이드 쇼 만들기 (0) | 2022.07.19 |
자바스크립트로 계산기 구현하기 (0) | 2022.07.12 |