참고: https://youtu.be/4_WLS9Lj6n4
✔️ 변수
var는 선언하기 전에 사용할 수 있다.
console.log(name); // undefined
var name = "Mike";
var로 선언한 변수는 호이스팅(hoisting)되어 사용할 수 있다.
선언은 호이스팅되지만, 할당은 호이스팅되지 않기 때문에
console에서는 undefined를 반환한다.
❗호이스팅(hoisting)?
: 스코프 내부 어디서든 변수 선언은 최상위에 선언된 것처럼 행
let, const도 호이스팅되지만, TDZ(Temporal Dead Zone)에 의해
let name = "Mike" 라고 선언하게 되면 ReferenceError를 발생시킨다.
TDZ 영역에 있는 변수들은 사용할 수 없다.
이에 의해서 let과 const는 TDZ에 영향을 받는다고 할 수 있다.
(할당하기 전에 사용 X)
이러한 TDZ의 역할은 코드를 예측 가능하게 하고,
코드의 잠재적인 버그를 줄일 수 있다.
let age = 30;
function showAge(){
console.log(age);
}
showAge();
// 위 코드는 문제가 없지만,
let age = 30;
function showAge(){
console.log(age);
let age = 20;
}
showAge();
// 위 코드는 문제가 발생한다.
호이스팅은 scope 단위로 일어나기 때문에,
밑에 해당하는 코드의 스코프는 function 내부이고,
console.log(age); 에 해당하는 부분이 TDZ 영역이 되기 때문에
해당 코드에 문제가 발생하게 된다.
[ 변수의 생성과정 ]
1) 선언 단계
2) 초기화 단계
- 초기화란 undefined를 할당해주는 단계
3) 할당 단계
var는 선언과 초기화가 동시에 된다.
let은 3가지 단계가 모두 분리된다.
const는 선언과 할당이 동시에 되어야한다.
이유는 let과 var는 값을 바꿀 수 있기 때문이다.
[ 스코프 ]
var : 함수 스코프(function-scoped)
let, const : 블록 스코프(block-scoped)
❗블록 스코프란?
- 모든 코드 블록에서 선언된 변수는 코드 블록 내에서만 유효하며,
외부에서는 접근할 수 없다. 즉, 코드 내부에서 선언한 변수는 지역 변수
- 함수, if문, for문, while문, try/catch문 등
const age = 30;
if(age>19){
var txt = '성인';
}
console.log(txt); // 성인
// 해당 var는 함수 스코프
// if 코드 내의 let과 const는 블록 내에서만 사용 가능
// 이를 블록 스코프
function add(num1, num2){
var result = num1 + num2;
}
add(2,3);
console.log(result); // ReferenceError
// 유일하게 벗어날 수 없는 var의 스코프는 함수
✔️ 생성자 함수
// 객체 리터럴
let user = {
name : 'Mike',
age : 30,
}
// 생성자 함수
// 생성자 함수는 대부분 첫 글자는 대문자로
function User(name, age){
this.name = name;
this.age = age;
}
// new 연산자를 사용하여 호출
let user1 = new User('Mike', 30); // User {name: "Mike", age: 30}
let user2 = new User('Jane', 22); // User {name: "Jane", age: 22}
let user3 = new User('Tom', 17); // User {name: "Tom", age: 17}
new 함수명()을 실행하게 되면,
생성자 함수 내부에서 this = {} 빈 객체를 만들고 this에 할당한다.
this에 property를 추가하고 마지막 return this; 처럼 반환한다.
이것이 new 연산자를 사용하여 호출되는 방식이다.
그래서 생성자 함수를 사용할 때는 일반 함수와의 구분을 위해
첫 글자를 대문자로 사용하는 것이 관례이다.
function User(name, age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
let user5 = new User('Han', 40);
user5.sayName(); // 'Han'
해당 console.log의 this는 user5가 되고,
user5.name은 User의 첫 번째 인수인 'Han'이 되기 때문에
sayName 메소드를 실행하면 콘솔에 이름이 찍히게 된다.
만약 new 연산자를 사용하지 않게 되면, 반환되는 것 없이
그냥 함수가 실행되기 때문에 undefined가 반환된다.
✔️ 객체 - 메소드(methods) / 계산된 프로퍼티(Computed property)
// Computed property (계산된 프로퍼티)
let a = 'age';
const user = {
name : 'Mike',
[a] : 30, // age : 30,
}
const user = {
[1 + 4] : 5,
["안녕"+"하세요"] : "Hello",
} // {5: 5, 안녕하세요: "Hello"}
위와 같이 선언한 변수를 프로퍼티에 사용하거나,
프로퍼티에 연산을 사용해도 객체에 알맞게 나오는 것을
계산된 프로퍼티라고 말한다.
// Object.assign() : 객체 복제
const user = {
name : 'Mike',
age : 30,
}
const cloneUser = user;
// 해당 코드로 복제되지 않는다.
// 그 이유는 user는 객체가 아닌 객체가 저장된
// 메모리 주소인 객체의 참조값이 저장되기 때문이다.
const newUser = Object.assign({}, user);
// 이렇게 사용하면 객체를 복제할 수 있다.
// user가 초기값인 빈 객체에 병합되므로 복제되는 것이다.
const newUser = Object.assign({ gender : 'male' }, user);
// 동일하지 않는 객체의 키 값이 존재한다면 추가
// {gender: "male", name: "Mike", age: 30}
const newUser = Object.assign({ name : 'Tom' }, user);
// 동일한 객체의 키 값이 존재한다면 덮어씀
// {name: "Mike", age: 30}
const user = {
name : 'Mike',
}
const info1 = {
age : 30,
}
const info2 = {
gender : 'Male',
}
Object.assing(user, info1, info2)
// 해당 코드처럼 사용하게 되면 info1과 info2가
// user에 병합된 객체를 사용할 수 있다.
// Object.keys() : 키 배열 반환
const user = {
name : 'Mike',
age : 30,
gender : 'male',
}
Object.keys(user); // ["name", "age", "gender"]
// Object.values() : 값 배열 반환
Object.values(user); // ["Mike", 30, "male"]
// Object.entries() : 키/값 배열 반환
Object.entries(user); // [["name", "Mike"], ["age", 30], ["gender", "male"]]
// Object.fromEntries() : 키/값 배열을 객체로
const arr = [
["name", "Mike"],
["age", 30],
["gender", "male"]
];
Object.fromEntries(arr); // {name : 'Mike', age : 30, gender : 'male'}
✔️ 심볼(Symbol)
// property key : 문자형
const obj = {
1 : '1입니다',
false : '거짓',
}
Object.keys(obj); // ["1", "false"]
obj['1'] // "1입니다"
obj['false'] // "거짓"
이처럼 객체의 프로퍼티 키는 문자형으로 가능하고,
하나 더 가능한 것이 Symbol이다.
Symbol은 유일한 식별자를 만들 때 사용한다.
const a = Symbol(); // new를 붙이지 않는다.
const b = Symbol();
console.log(a); // Symbol()
console.log(b); // Symbol()
console.log(a === b); // false
console.log(a == b); // false
이러한 Symbol은 유일성을 보장하고,
Symbol('id') 처럼 설명으로 id를 설정할 수 있는데
이는 디버깅할 때 편한 수단으로 작용할 수 있다.
// property key : 심볼형
const id = Symbol('id');
const user = {
name : 'Mike',
age : 30,
[id] : 'myid'
}
console.log(user) // {name: "Mike", age: 30, Symbol(id): "myid"}
console.log(user[id]) // "myid"
Object.keys(user); // ["name", "age"]
Object.values(user); // ["Mike", 30]
Object.entries(user); // [Array(2), Array(2)]
Object.getOwnPropertySymbols(user); // [Symbol(id)]
Reflect.ownKeys(user); // ["name", "age", Symbol(id)]
해당 객체의 메소드들을 사용하면 Symbol을 건너띄는 것을 확인할 수 있다.
이러한 Symbol은 특정 객체의 원본 데이터를 건드리지 않고
속성을 추가하는 것에 사용하게 된다.
심볼을 포함한 모든 객체를 확인하거나 심볼을 확인하고 싶을 때는
맨 아래 두 줄의 코드처럼 사용할 수 있다.
전역 변수처럼 이름이 같으면 같은 객체를 가리켜야할 때는
Symbol.for()인 전역 심볼을 사용하면 된다.
[ Symbol.for() ]
- 하나의 심볼만 보장받을 수 있다.
- 없으면 만들고, 있으면 가져온다.
- Symbol 함수는 매번 다른 Symbol 값을 생성하지만,
Symbol.for 메소드는 하나를 생성한 뒤 키를 통해 같은 Symbol을 공유한다.
const id1 = Symbol.for('id');
const id2 = Symbol.for('id');
console.log(id1 === id2); // true
Symbol.keyFor(id1) // "id"
다음처럼 전역 심볼을 사용할 수 있고,
keyFor 메소드를 통해 심볼의 이름을 얻을 수 있다.
const id = Symbol('id입니다');
id.description; // "id입니다"
전역 심볼이 아닌 것은 다음 description 메소드를 활용해
심볼의 이름을 얻을 수 있다.
// 다른 개발자가 만든 객체
const user = {
name: "Mike",
age: 30,
}
// 내가 작업할 프로퍼티
const showName = Symbol("show name");
user[showName] = function () {
console.log(this.name);
}
user[showName]();
// 출력
// Mike
// 기존 작업된 코드
for (let key in user){
console.log(`Hist ${key} is ${user[key]}.`);
}
// 출력
// His name is Mike.
// His age is 30.
위에 나오는 코드처럼 기존 원본 데이터를 건들지 않고,
프로퍼티를 추가하여 사용할 수 있다는 장점이 있다.
화이팅 💪
'개발(dev) > js' 카테고리의 다른 글
[javascript] 클로저 (Closure) (0) | 2023.05.07 |
---|---|
[js] javascript 기초 (2) (0) | 2023.01.21 |
[js] javascript 기초 (1) (0) | 2023.01.20 |
JavaScript 공부. 01 (0) | 2021.09.29 |