이 글은 [모던 JavaScript 튜토리얼]을 공부하며 정리한 글입니다.
👉 기본 환경
- Language: JavaScript
- IDE: VS code
객체(Object)
- 중괄호 {…} 선언
- 중괄호 안에 ‘키(key): 값(value)’ 쌍으로 구성된 프로퍼티(property)로 구성
- 키(이름): 문자형, 값: 모든 자료형
- 키 값에 예약어 사용 가능
- 숫자 입력 시,
- 자동으로 문자로 형변환
- 자동으로 key가 오름차순 정렬(오름차순 정렬을 원하지 않을 경우에는 문자로 취급되도록 " " 등 추가)
* 객체 생성
1
2
3
|
let user = new Object(); // '객체 생성자'
let user = {}; // '객체 리터럴'
|
⭐ 객체 리터럴이 주로 사용됨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
let user = {
name: "John",
age: 30,
"likes birds": true, // 마지막 , 생략 가능
// 띄어쓰기 시, "" 사용
};
// key값에 띄어쓰기가 없을 경우,
// set
user.isAdmin = true;
// get
user.isAdmin;
// delete
delete user.age;
// key값에 띄어쓰기가 있을 경우,
// set
user["likes birds"] = true;
// get
alert(user["likes birds"]); // true
// delete
delete user["likes birds"];
|
⭐ Const 상수는 수정하지 못하지만, Const 객체의 property는 수정 가능
* 점 표기법과 대괄호 표기법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// 점 표기법
user.likes birds = true // error
let user = {
name: "John",
age: 30
};
let key = "name";
alert( user.key ) // undefined
// 대괄호 표기법
user["likes birds"] = true;
let user = {
name: "John",
age: 30
};
let key = prompt("사용자의 어떤 정보를 얻고 싶으신가요?", "name");
// 변수로 접근
alert( user[key] ); // John
|
* 계산된 Property
- 객체를 만들 때 객체 리터럴 안의 프로퍼티 키가 대괄호로 둘러싸여 있는 경우
1
2
3
4
5
6
7
8
|
let fruit = prompt("어떤 과일을 구매하시겠습니까?", "apple");
let bag = {
[fruit]: 5, // 변수 fruit에 프로퍼티 이름을 동적으로 할당
};
alert( bag.apple ); // fruit에 "apple" 할당 시, 5 출력
|
* 단축 Property
1
2
3
4
5
6
7
8
|
function makeUser(name, age) {
return {
name, // name: name 과 같음
age, // age: age 와 같음
// ...
};
}
|
* 존재하는 Property인지 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// undefined
let user = {};
alert( user.noSuchProperty === undefined ); // '프로퍼티가 존재하지 않음'
let obj = {
test: undefined
};
alert( obj.test === undefined ); // value: undefined와 존재하지 않는 undefined가 구분되지 않음
// in operator
let user = { name: "John", age: 30 };
alert( "age" in user ); // true
alert( "blabla" in user ); // false
|
* for .. in 반복문
1
2
3
4
5
6
7
8
9
10
11
12
|
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
alert( key ); // name, age, isAdmin
// 키에 해당하는 값
alert( user[key] ); // John, 30, true
}
|
2. 참조에 의한 객체 복사
* Object.assgin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// Object.assign
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
Object.assign(user, permissions1, permissions2);
// user = { name: "John", canView: true, canEdit: true }
// permissions1과 permissions2의 프로퍼티를 user로 복사
// 목표 객체(user)에 동일한 이름을 가진 프로퍼티가 있는 경우
let user = { name: "John" };
Object.assign(user, { name: "Pete" });
alert(user.name);
// user = { name: "Pete" }
// 기존 값이 덮어씌워짐
|
🚨 Object.assign()은 중첩 객체 복사를 하지 못함
+ 중첩 객체 복사 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
// 1. lodash library
let deep = _.cloneDeep(user);
console.log(user.sizes === deep.sizes); // false
// 2. for .. in
let clone = {};
for (let key in user) {
if(typeof user[key] == 'object'){
clone[key] = Object.assign({}, user.sizes);
continue;
}
clone[key] = user[key];
}
console.log(user.sizes === clone.sizes); // false
|
3. 가비지 컬렉션
- 객체는 도달 가능한 상태일 때 메모리에 남음
* 참조 복사
1
2
3
4
5
6
7
8
9
|
let user = {
name: "John"
};
let admin = user;
user = null;
console.log(admin.name);
|
🚨 user.name에 접근하여 직접 값을 변경하여 admin에도 반영이 되는 것과는 다른 개념
- name: "John" 대신 null을입력한 것이 X
- user의 화살표를 제거한 것
4. 메서드와 this
* 객체는 사용자(user), 주문(order) 등과 같이 실제 존재하는 개체(entity)를 표현하고자 할 때 생성
1
2
3
4
5
|
let user = {
name: "John",
age: 30
};
|
* 객체의 프로퍼티에 함수를 할당(= 메서드)해 객체에게 행동할 수 있는 능력을 부여
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
// 표현식 1
user.sayHi = function() {
alert("안녕하세요!");
};
user.sayHi(); // 안녕하세요!
// 표현식 2
// 함수 선언
function sayHi() {
alert("안녕하세요!");
};
// 선언된 함수를 메서드로 등록
user.sayHi = sayHi;
user.sayHi(); // 안녕하세요!
// 표현식 3
user = {
sayHi: function() {
alert("Hello");
}
};
user.sayHi(); // 안녕하세요!
// 표현식 4
user = {
sayHi() {
alert("Hello");
}
};
|
* this
- 현재 객체
- ‘점 앞의’ 객체가 무엇인가에 따라 ‘자유롭게’ 결정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
let user = {
name: "John",
age: 30,
sayHi() {
alert( user.name ); // Error: Cannot read property 'name' of null
}
};
let admin = user;
user = null;
admin.sayHi();
// sayHi()가 user의 name을 참조하면서 오류 발생
|
🚨 this.name으로 선언 시, admin의 name을 참조하게 되므로 오류가 발생하지 않음
* 화살표 함수
- 자신만의 this를 가지지 않음
- 화살표 함수 안에서 this를 사용하면, 외부에서 this 값을 가져옴
1
2
3
4
5
6
7
8
9
10
|
let user = {
firstName: "보라",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // 보라
|
* 객체 리터럴에서 'this' 사용하기
1
2
3
4
5
6
7
8
9
10
11
12
|
// Case1
function makeUser() {
return {
name: "John",
ref: this
};
};
let user = makeUser();
alert( user.ref.name ); // Error: Cannot read property 'name' of undefined
|
⭐ Undefined Error 발생 이유
- 객체의 메서드로써 호출된 게 아니라 함수로써 호출되었기 때문
- ref 속성은 함수 내에서 this로 설정되지 않고, 전역 객체를 참조
- 일반적으로 브라우저 환경에서, 전역 객체는 window 객체
- window.name은 브라우저 창의 이름을 나타내는 속성으로, 일반적으로 초기에는 빈 문자열로 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function makeUser() {
return {
name: "John",
ref() {
return this;
}
};
};
let user = makeUser();
alert( user.ref().name ); // John
|
- ref 속성은 메서드로 정의되었으며, 함수 내부에서 this는 해당 메서드를 호출한 객체
⭐ 1번에서는 메서드로 정의되지 않은 ref이므로 this가 해당 메서드를 호출한 객체를 가르키게 되는게 아니라, 전역 객체를 가리키게 되고
⭐ 2번에서는 메서드로 정의된 ref에서 this는 해당 메서드를 호출한 객체를 가르키게 되므로 user 객체가 가르키는 객체가 됨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
let calculator = {
sum() {
return this.a + this.b;
},
mul() {
return this.a * this.b;
},
read() {
this.a = +prompt('첫 번째 값:', 0);
this.b = +prompt('두 번째 값:', 0);
}
};
calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );
|
* this를 활용하여 변수를 선언하지 않고도 계산 수행 가능
* prompt에 + 선언
- 사용자 입력 값을 숫자로 변환
⭐ 객체 리터럴 내에서 속성을 선언하고 값을 할당하지 않으면 JavaScript는 자동으로 해당 속성을 undefined로 초기화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
// * 일반
let ladder = {
step: 0,
up() {
this.step++;
},
down() {
this.step--;
},
showStep: function() { // 사다리에서 몇 번째 단에 올라와 있는지 보여줌
alert( this.step );
}
};
ladder.up();
ladder.up();
ladder.down();
ladder.showStep(); // 1
// * Chaining
let ladder = {
step: 0,
up() {
this.step++;
return this;
},
down() {
this.step--;
return this;
},
showStep() {
alert( this.step );
return this;
}
}
ladder
.up()
.up()
.down()
.up()
.down()
.showStep(); // 1
|
⭐ 객체 반환을 통해서 메서드 체이닝
5. new 연산자와 생성자 함수
⭐ 유사한 객체를 여러 개 만들 때 유용
*함수 이름의 첫 글자는 대문자로 시작
- 일반 함수와 동일한 형태이지만, 구분을 위해 첫 글자를 대문자로 작성
*반드시 new 연산자를 붙여 실행
- new와 함께 호출하면 내부에서 this가 암시적으로 만들어지고, 마지막엔 this가 반환
1
2
3
4
5
6
7
8
9
10
|
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("보라");
alert(user.name); // 보라
alert(user.isAdmin); // false
|
- 빈 객체를 만들어 this에 할당
- 함수 본문
- this에 새로운 프로퍼티를 추가해 this를 수정
- this를 반환
1
2
3
4
5
|
let user = {
name: "보라",
isAdmin: false
};
|
* 반환해야 할 것들은 모두 this에 저장되고, this는 자동으로 반환되기 때문에 반환문을 명시적으로 써 줄 필요가 없음
- return 객체: this 대신 객체 반환
- return 원시형: return문 무시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function BigUser() {
this.name = "원숭이";
return { name: "고릴라" }; // <-- this가 아닌 새로운 객체 반환
}
alert( new BigUser().name ); // 고릴라
function SmallUser() {
this.name = "원숭이";
return 2; // <-- this 반환
}
alert( new SmallUser().name ); // 원숭이
|
⭐ return문이 있는 생성자 함수는 거의 없음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
function Calculator(){
this.read = function(){
this.a = +prompt('Enter a number.', '0');
this.b = +prompt('Enter a number.', '0');
},
this.sum = function (){
return this.a + this.b;
},
this.mul = function (){
return this.a * this.b;
}
}
let calculator = new Calculator();
calculator.read();
alert("Sum=" + calculator.sum());
alert("Mul=" + calculator.mul());
|
* 생성자 함수 내부에서 this를 사용 시, 그 this는 새로 생성되는 객체 인스턴스를 가리킴
* 생성자 함수에서 메서드를 정의할 때 this.read = function() {}와 같이 작성하는 이유
- 생성된 각 객체 인스턴스가 자신만의 'read' 메서드를 가지게 됨
- 즉, 'read' 메서드는 해당 객체에 속한 프로퍼티가 되어 해당 객체의 컨텍스트(this)에서 접근하고 호출할 수 있습니다.
- 반대로, 만약 this 없이 그냥 function read() {}와 같이 작성한다면, 'read' 함수는 생성자 함수의 로컬 함수가 되어 외부에서 접근할 수 없음
* 따라서, 각각의 Calculator 인스턴스가 자신만의 'read', 'sum', 'mul' 메서드를 가지고 이들 메서드가 해당 인스턴스에 속한 데이터('a', 'b')에 접근할 수 있도록 하기 위해, 메서드 정의에 this.methodName = function() {} 형태로 작성해야 함
1
2
3
4
5
6
7
8
|
function Accumulator(startNum){
this.number = startNum;
this.read = function(){
this.number += +prompt('Enter a number');
}
this.value = this.number;
}
|
* value가 startNum으로 고정된 이유
- this.value = number는 객체 생성 시, 1번만 실행되므로 이미 설정된 value의 값은 변경되지 않음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function Accumulator(startNum) {
this.number = startNum;
this.read = function () {
this.number += +prompt('Enter a number');
};
this.value = function () {
return this.number;
};
}
let accumulator = new Accumulator(1); // 최초값: 1
accumulator.read(); // 사용자가 입력한 값을 더해줌
accumulator.read(); // 사용자가 입력한 값을 더해줌
alert(accumulator.value()); // 최초값과 사용자가 입력한 모든 값을 더해 출력함
|
* value를 메서드로 만들어 현재까지 누적된 값을 반환하게 할 수 있도록 함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function Accumulator(startNum) {
this.value = startNum;
this.read = function () {
this.value += +prompt('Enter a number');
};
}
let accumulator = new Accumulator(1); // 최초값: 1
accumulator.read(); // 사용자가 입력한 값을 더해줌
accumulator.read(); // 사용자가 입력한 값을 더해줌
alert(accumulator.value); // 최초값과 사용자가 입력한 모든 값을 더해 출력함
|
* value 변수를 직접 변경
6. 옵셔널 체이닝 '?.'
* Optional Chaining
- ?.은 ?.의 왼쪽 평가 대상이 undefined나 null이면 평가를 멈추고 undefined를 반환
⭐ ?.는 존재하지 않아도 괜찮은 대상에만 사용
- 논리상 user는 반드시 있어야 하는데 address는 필수값이 아닐 경우, user.address?.street를 사용하는 것이 바람직
- 꼭 있어야 하는 값인데 없는 경우에 ?.을 사용하면 프로그래밍 에러를 쉽게 찾을 수 없으므로 유의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// key: 값
let user = null;
alert( user?.address ); // undefined
// key: 함수
let user1 = {
admin() {
alert("관리자 계정입니다.");
}
}
let user2 = {};
user1.admin?.(); // 관리자 계정입니다.
user2.admin?.(); // 평가가 멈춤
|
🚨 ?.은 읽기나 삭제하기에는 사용할 수 있지만 쓰기에는 사용할 수 없음
1
2
3
4
5
6
7
8
9
10
11
12
|
// 검색
let user = {}; // 주소 정보가 없는 사용자
alert( user?.address?.street ); // undefined,
// 삭제
delete user?.name; // user가 존재하면 user.name을 삭제
// 삽입
user?.name = "Violet"; // undefined = "Violet"으로 SyntaxError 발생
|
7. 심볼형
* 유일한 식별자(unique identifier)를 만들고 싶을 때 사용
- 심볼형 값은 다른 자료형으로 암시적 형 변환(자동 형 변환)되지 않음
* 심볼 설명
1
2
3
|
// 심볼 id에는 "id"라는 설명 추가
let id = Symbol("id");
|
* 숨김 프로퍼티
- 외부 코드에서 접근이 불가능하고 값도 덮어쓸 수 없는 프로퍼티
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 서드파티 코드에서 가져온 객체
let user = { name: "John" };
user.id = "스크립트 id 값";
// 문자열 "id"를 사용해 식별자 생성
user.id = "제3 스크립트 id 값"
// 덮어쓰기되어 id 식별자가 무의미해짐
let id1 = Symbol("id");
// 심볼을 사용하면 서드파티 코드가 모르게 user에 식별자를 부여할 수 있음
user[id1] = "스크립트 id 값";
// 심볼은 유일성이 보장되므로 우리가 만든 식별자와 제3의 스크립트에서 만든 식별자의 이름이 동일하더라도 충돌하지 않음
let id2 = Symbol("id");
user[id2] = "제3 스크립트 id 값";
alert(id1 == id2); // false
|
* 심볼 배제와 포함
- for...in 반복문
- Object.keys()
- Object.assign()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
// for...in 반복문
for (let key in user) alert(key);
// name과 age만 출력되고, 심볼은 출력되지 않음
alert( "직접 접근한 값: " + user[id] );
// 심볼로 직접 접근하면 작동
// Object.keys(user)
alert(Object.keys(user));
// name과 age만 출력되고, 심볼은 출력되지 않음
// Object.assign(user)
let clone = Object.assign({}, user);
alert( clone[id] ); // 123
// 키가 심볼인 프로퍼티를 배제하지 않고 객체 내 모든 프로퍼티를 복사
|
* 전역 심볼
- 전역 심볼 레지스트리 안에 있는 심볼은 전역 심볼
- 전역 심볼 레지스트리 안에 심볼을 만들고 해당 심볼에 접근하면, 이름이 같은 경우 항상 동일한 심볼을 반환
- 조건에 맞는 심볼이 레지스트리 안에 없으면 새로운 심볼 Symbol(key)을 만들고 레지스트리 안에 저장
- 이름이 같은 심볼이 같은 개체를 가리키길 원하는 경우 사용
- 애플리케이션에서 광범위하게 사용해야 하는 심볼이라면 전역 심볼을 사용
1
2
3
4
5
6
7
8
9
|
// 전역 레지스트리에서 심볼을 검색
let id = Symbol.for("id");
// 심볼이 존재하지 않으면 새로운 심볼 생성
let idAgain = Symbol.for("id");
// 동일한 이름을 이용해 심볼을 다시 검색
alert( id === idAgain ); // true
|
* Symbol.for() && Symbol.keyFor()
1
2
3
4
5
6
7
8
|
// 이름을 이용해 심볼을 찾음
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// 심볼을 이용해 이름을 얻음
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
|
🚨 검색 범위가 전역 심볼 레지스트리이기 때문에 전역 심볼이 아닌 심볼에는 사용할 수 없음
전역 심볼이 아닌 인자가 넘어오면 Symbol.keyFor는 undefined를 반환
⭐ 전역 심볼이 아닌 모든 심볼은 description 프로퍼티 사용
일반 심볼에서 이름을 얻고 싶으면 description 프로퍼티 사용
1
2
3
4
5
6
7
8
9
|
let globalSymbol = Symbol.for("gloName");
let localSymbol = Symbol("locName");
alert(Symbol.keyFor(globalSymbol)); // gloName, 전역 심볼
alert(Symbol.keyFor(localSymbol)); // undefined, 전역 심볼이 아님
alert(localSymbol.description); // locName
alert(globalSymbol.description); // gloName
|
cf. 심볼 조회 메서드
내장 메서드 Object.getOwnPropertySymbols(obj)를 사용하면 모든 심볼을 볼 수 있고,
메서드 Reflect.ownKeys(obj)는 심볼형 키를 포함한 객체의 모든 키를 반환
* 그러나, 대부분의 라이브러리, 내장 함수 등은 이런 메서드를 사용하지 않음
'JavaScript > JavaScript' 카테고리의 다른 글
[JAVASCRIPT.INFO] 자료구조와 자료형 (0) | 2023.10.23 |
---|---|
[JAVASCRIPT.INFO] 코드 품질 (0) | 2023.10.15 |
[JAVASCRIPT.INFO] 자바스크립트 기본 (1) | 2023.10.11 |
[ReactJS_Complete] Useful JavaScript Function (0) | 2023.05.25 |
[ReactJS_Complete] Destructuring (0) | 2023.05.24 |