[딥 다이브 스터디] 24.01.04 - 객체 완벽 정리

객체를 완벽하게 정리해보고자 했다. 보시는 분들이 객체를 알아가실 수 있도록...
Jan 04, 2024
[딥 다이브 스터디] 24.01.04 - 객체 완벽 정리
객체란? 진짜 중요하다고 생각한다.
이글을 읽는 분들은 객체라는 놈을 마스터하길 빌며 작성해본다. 다소 길 수 있다…
 
결국 자바스크립트 === 객체 // true 라고 생각할 정도인데 자 이제 객체를 진지하게 공부해보자.
 
자바스크립트 객체(object) 기반의 스크립트 언어이며 자바스크립트를 이루고 있는 거의 모든 것이 객체이다.
 
우리가 앞에서 봐왔던 원시 타입(Primitives)을 제외한 나머지 값들(함수, 배열, 정규표현식 등)은 모두 객체이다.
 
자바스크립트의 객체는 키(key)와 값(value)으로 구성된 프로퍼티들의 집합이다.

프로퍼티란?

→ 프로퍼티란 기본적으로 어떤 값을 나타낸다. 그런데? 이 값이 다른 값과 연관이되어 있을 때 property라고 부른다.
→ 프로퍼티란 속성이란 뜻으로 자바스크립트에서 객체 내부의 속성을 의미한다.
 
자바스크립트의 함수는 일급 객체이므로 값으로 취급할 수 있다. 따라서 프로퍼티 값으로 함수를 사용할 수도 있으며 프로퍼티 값이 함수일 경우, 일반 함수와 구분하기 위해 메소드라고 부른다.
 
객체는 데이터(프로퍼티)와 그 데이터에 관련되는 동작(메소드)을 모두 포함할 수 있기 때문에 데이터와 동작을 하나의 단위로 구조화할 수 있어 유용하다.

객체 생성 방법

자바와 같은 클래스 기반 객체 지향 언어는 클래스를 사전에 정의하고 필요한 시점에 new 연산자를 사용하여 인스턴스를 생성하는 방식으로 객체를 생성한다.
자바스크립트는 프로토타입 기반 객체 지향 언어로서 클래스라는 개념이 없고 별도의 객체 생성 방법이 존재한다. 하지만! ES6에서 클래스가 도입되었다.

문법적 설탕(Syntactic sugar)

JS외에 프로그래밍 언어 전반적으로 사용되는 개념, 달달한 이름에 걸맞게 읽는 사람 또는 작성하는 사람이 편하게 디자인 된 문법이라는 뜻을 갖고 있다.
// 변수 단축 선언 let a, b = 1, c; // undefined, 1, undefined // 단축 평가 값 const str = "some text"; const result1 = str || "default value"; // "some text"; // 널 병합 연산자 let foo = 0; const result1 = foo || "default Value"; // "default Value" const result2 = foo ?? "default Value"; // 0 // 삼항 조건 연산자 condition ? exprIfTrue : exprIFalse; // 단축 속성명 const a = 1, b = 2, c = 3; const obj1 = { a: a, b: b, c: c, }; const obj2 = { a, b, c }; // 1, 2, 3 // 전개 구문 const Cat = { name: "ari", age: 11, leg: 4, hobby: "sleep", }; const newCat = { ...Cat }; //{name: "ari", age: 11, leg: 4, hobby: "sleep"} // 나머지 매개변수 function fn1(name, age, ...all) { // name = 'youn' // age = 5 // all = ['happy','cool','nice']; } fn1("youn", 5, "happy", "cool", "nice"); // 기본값 매개변수 function fn1 (name = "Youn", age = 5) { console.log(`${name} is ${age} years old`); } fn1(); // Youn is 5 years old fn1("yeochan", 10); // yeochan is 10 years old // 템플릿 리터럴 const hi = "Good Morning"; console.log(`${hi}`); // Good Morning console.log(`${3 + 2}`); // 5 // String to Number /* parseFloat() 함수는 주어진 값을 필요한 경우 문자열로 변환한 후 부동소수점 실수로 파싱해 반환합니다. */ parseFloat("100.1") + // 100.1 "100.1"; // 100.1 parseFloat("100.1") === + "100.1"; // true // Double Bitwise Not (이중 비트연산자 NOT) ~~null; // => 0 ~~undefined; // => 0 ~~true; // => 1 ~~1.2543; // => 1 ~~4.9; // => 4 ~~-2.999; // => -2 // Object Destructuring (구조 분해 할당) let [a, b] = ["2", "3", "4"]; // a: 2, b: 3 let [c, d, ...e] = ["1", "2", "3", "4", "5"]; // c: 1, d: 2, e: [3, 4, 5]
위 예제는 꼭 읽어보길 바란다 vue던 리엑트던 코드를 읽기 위해선 무조건 알아야하는 문법들이다.

객체 리터럴

가장 일반적인 자바스크립트의 객체 생성 방식이다. 클래스 기반 객체 지향 언어와 비교할 때 매우 간편하게 객체를 생성할 수 있다.
중괄호 ({})를 사용해서 객체를 생성하는데 {} 내에 1개 이상의 프로퍼티를 기술하면 해당 프로퍼티가 추가된 객체를 생성할 수 있다.
이때 {} 내에 아무것도 기술하지 않으면 빈 객체가 생성된다.
let emptyObject = {}; log(typeof emptyObject); // object let person = { name: 'youn', sayHello: funtion() { log('Hi my name is' + this.name); } }; log(typeof person); // object log(person); // {name: 'youn', sayHello: f} person.sayHello(); // Hi my name is youn

Object 생성자 함수

new 연산자와 Object 생성자 함수를 호출하여 빈 객체를 생성할 수 있다.
빈 객체를 생성 이후 프로퍼티 또는 메소드를 추가하여 객체를 완성하는 방법이다.
생성자(constructor)함수란 new 키워드와 함께 객체를 생성하고 초기화하는 함수를 말한다.
생성자 함수를 통해 생성된 객체를 인스턴스(instance)라 한다.
자바스크립트는 Object 생성자 함수 이외에도 String, Number, Boolean, Array, Date, RegExp 등의 빌트인 생성자 함수를 제공한다.
일반 함수와 생성자 함수를 구분하기 위해 생성자 함수의 이름은 파스칼 케이스(PascalCase)를 사용하는 것이 일반적이다.
// 빈 객체의 생성 var person = new Object(); // 프로퍼티 추가 person.name = 'youn'; person.sayHello = function () { console.log('Hi! My name is ' + this.name); }; console.log(typeof person); // object console.log(person); // {name: "youn", sayHello: ƒ} person.sayHello(); // Hi! My name is youn

생성자 함수

객체 리터럴 방식Object 생성자 함수 방식으로 객체를 생성하는 것은 프로퍼티 값만 다른 여러 개의 객체를 생성할 때 불편하다.
var person1 = { name: 'youn', gender: 'male', sayHello: function () { console.log('Hi! My name is ' + this.name); } }; var person2 = { name: 'yeochan', gender: 'female', sayHello: function () { console.log('Hi! My name is ' + this.name); } };
위의 예제를 보면 완전 같은 동일한 프로퍼티를 갖는 객체 임에도 매번 같은 프로퍼티를 기술해야한다. 그럼 이걸 어떻게할 수 있을까? 그건 생성자 함수를 이용하면 된다. 마치 객체를 생성하기 위한 템플릿 (class)처럼 사용하여 프로퍼티가 동일한 객체 여러 개를 간편하게 생성할 수 있다.
// 생성자 함수 function Person(name, gender) { this.name = name; this.gender = gender; this.sayHello = function(){ console.log('Hi! My name is ' + this.name); }; } // 인스턴스의 생성 var person1 = new Person('youn', 'male'); var person2 = new Person('yeochan', 'female');
위 와 완전히 같은 결과를 낼 수 있다. 위의 예제처럼 생성자 함수를 통해 함수를 이용해서 찍어낼 수 있다.

this?

자바스크립트를 하는데 this를 모른다? 그건 얼토당토 없는 이야기..
this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다.
자바스크립트의 this는 함수를 호출할 때, 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.

객체 프로퍼티 접근

프로퍼티 키

프로퍼티 키는 일반적으로 문자열 (빈 문자열 포함)을 지정한다.
프로퍼티 키에 문자열이나 symbol 값 이외의 값을 지정하면 암묵적으로 타입이 변환되어 문자열이된다. 또한 문자열 타입의 값으로 수렴될 수 있는 표현식도 가능하다.
프로퍼티 키는 문자열이므로 따옴표(‘’ 또는 ““)를 사용한다. 
하지만 자바스크립트에서 사용 가능한 유효한 이름인 경우, 따옴표를 생략할 수 있다. 반대로 말하면 자바스크립트에서 사용 가능한 유효한 이름이 아닌 경우, 반드시 따옴표를 사용하여야 한다.
/* 예약어를 사용하면 안된다. 예약어를 프로퍼티 키로 사용하여도 에러가 발생하지는 않는다. 하지만 예상치 못한 에러가 발생할 수 있으므로 예약어를 프로퍼티 키로 사용해서는 안된다. */ let pre = { function: 0 // function은 예약어이다. } // '-' 연산자가 있는 표현식이라 에러가 발생 let nonono = { first-name: 'yeochan', // SyntaxError: Unexpected token - } /* 표현식을 프로퍼티 키로 사용하려면 키로 사용할 표현식을 대괄호로 묶어야 한다. 이때 자바스크립트 엔진은 표현식을 평가하기 위해 식별자 first를 찾을 것이고 이때 ReferenceError가 발생한다. */ let nonono = { [first-name]: 'yeochan', // ReferenceError: first is not defined }

프로퍼티 값 읽기

var person = { 'first-name': 'yeochan', 'last-name': 'youn', gender: 'male', 1: 10 }; console.log(person); console.log(person.first-name); // NaN: undefined-undefined console.log(person[first-name]); // ReferenceError: first is not defined console.log(person['first-name']); // 'yeochan' console.log(person.gender); // 'male' console.log(person[gender]); // ReferenceError: gender is not defined console.log(person['gender']); // 'male' console.log(person['1']); // 10 console.log(person[1]); // 10 : person[1] -> person['1'] console.log(person.1); // SyntaxError
여기서 보면 [] 대괄호를 사용해서 읽는다. console.log(person['first-name']); // 'yeochan' 이 부분이 좋은 예가 되는데 person이라는 변수에 접근해 [] 대괄호 안에 ‘’ 문자열에 접근하여 값을 읽을 수 있다.
var person = { 'first-name': 'yeochan', 'last-name': 'youn', gender: 'male', 1: 10 }; log(person.age); // undefined
만약 객체 내에 없는 프로퍼티를 참조하면 undefined를 반환하게된다.

프로퍼티 값 갱신

객체가 소유하고 있는 프로퍼티에 새로운 값을 할당하면? → 프로퍼티 값은 갱신된다.
let person = { 'first-name': 'yeochan', 'last-name': 'youn', gender: 'male', 1: 10 }; person['first-name'] = 'Kim'; console.log(person['first-name'] ); // 'Kim'
나의 이름이 김여찬이 되었다.

프로퍼티 동적 생성

let person = { 'first-name': 'yeochan', 'last-name': 'youn', gender: 'male', 1: 10 }; person.age = 20; log(person.age); // 20
age가 객체 내부에 생성되게 된다. 이게 바로 동적 생성
그럼 객체는 어떻게 생겼을까?
let person = { 'first-name': 'yeochan', 'last-name': 'youn', gender: 'male', 1: 10, age: 20, };
위 처럼 동적으로 추가 된 age가 추가된 모양이다.

프로퍼티 삭제

delete 연산자를 사용하면 객체의 프로퍼티를 삭제할 수 있다.
이때 피연산자는 프로퍼티 키이어야 한다.
let person = { 'first-name': 'yeochan', 'last-name': 'youn', }; delete person.age; console.log(person.age); // undefined delete person; console.log(person); // Object {first-name: 'yeochan', last-name: 'youn'} delete person["first-name"]; log(person); // { 'last-name': 'youn' }

프로퍼티의 축약 표현

객체 리터럴의 프로퍼티는 프로퍼티의 키(key)와 값(value)으로 구성되어 있다.
프로퍼티에 값은 식별자에 할당된 표현식일 수도 있다.
ES6에서는 프로퍼티의 키와 변수의 이름이 동일한 이름일 때 프로퍼티 키를 생략하고 사용이 가능하다. 예시를 통해 알아보자
// ES5 let x = 1, y = 2; let obj = { x: x, y: y, } // ES6 let x = 1, y = 2; const obj = { x, y };
어렵게 생각할 필요가 없다. 그냥 딱 하나다. 축약되었다. 더 간편해졌다. 이다.

계산된 프로퍼티 이름(computed property name)

문자열 또는 문자열로 타입 변환할 수 있는 값으로 평가되는 표현식을 사용해 프로퍼티 키를 동적으로 생성할 수 있다.
단, 프로퍼티 키를 대괄호([])로 묶어야 한다. 이를 계산된 프로퍼티 이름이라 한다.
// ES5 let obj = {}; obj[prefix + '' + ++i] = i; obj[prefix + '' + ++i] = i; obj[prefix + '' + ++i] = i; log(obj); // { prop-1: 1, prop-2: 2, prop-3: 3 } let obj = { [`${prefix} + '' + ${++i}`]: i; [`${prefix} + '' + ${++i}`]: i; [`${prefix} + '' + ${++i}`]: i; } log(obj); // { prop-1: 1, prop-2: 2, prop-3: 3 } // -------------------------------------------------------- let counter = 0; const obj = { [counter++]: counter++, [counter++]: counter++, }; console.log(obj); // {"0": 1, "2": 3}
구조분해 할당과 세트로 묶일 수 있으며, React, Vue에서도 매우 유용하게 사용된다.
객체의 속성명을 동적으로 결정할 땐 [] 대괄호 안에 넣어, 계산된 속성명을 사용하면 된다.
/** * state 객체에 속성명은 인자에 따라 동적으로 변경되므로, * [] 안에 넣어 게산된 속성명을 사용한다. */ const name = "answer"; const obj = { [name]: 100, }; console.log(obj); // {an: 100} // react handleFetch = (API, statename) => { fetch(API) .then(res => res.json()) // (1) .then(data => { this.setState({ [statename]: data.Result, // (2) }); }); }; // vue3 import { ref } from 'vue'; export default { methods: { async handleFetch(API, statename) { try { const response = await fetch(API); const data = await response.json(); // Assume this component has a ref named `state` to store reactive state this.state[statename] = ref(data.Result); // (2) } catch (error) { console.error('Error fetching data:', error); } }, }, data() { return { state: {}, // Initialize state object }; }, };

메서드 축약 표현

ES5에서 메서드를 정의하려면 프로퍼티 값으로 함수를 할당한다. 하지만 ES6에서는 메서드를 정의할 때 function 키워드를 생략한 축약 표현을 사용할 수 있다.
// ES5 let obj = { name: 'youn', sayHi: function() { log('Hi! ' + this.name); } }; obj.sayHi(); // Hi! youn // ES6 let obj = { name: 'youn', // 여기 부터 축약 표현 sayHi() { log('Hi! ' + this.name); } }; obj.sayHi(); // Hi! youn

for-in문

for-in 문을 사용하면 객체(배열 포함)에 포함된 모든 프로퍼티에 대해 루프를 수행할 수 있다.
자 여기서 for-in문을 살펴보는 이유는 객체를 순환하며 원하는 값을 뽑아내는 방법을 알려주기 위함이라는 것을 캐치하자.
let person = { 'first-name': 'yeochan', 'last-name': 'youn', gender: 'male', }; // prop에 객체의 프로퍼티 이름이 반환된다. 단, 순서는 보장되지 않는다. for (let prop in person) { console.log(prop + ': ' + person[prop]); } /* first-name: yeochan last-name: youn gender: male */ let array = ['one', 'two']; // index에 배열의 경우 인덱스가 반환된다 for (let index in array) { console.log(index + ': ' + array[index]); } /* 0: one 1: two */
for-in 문은 객체의 문자열 키(key)를 순회하기 위한 문법이다.
배열에는 사용하지 않는 것이 좋다.
이유는 아래와 같다.
  1. 객체의 경우, 프로퍼티의 순서가 보장되지 않는다. 그 이유는 원래 객체의 프로퍼티에는 순서가 없기 때문이다.
  1. 배열은 순서를 보장하는 데이터 구조이지만 객체와 마찬가지로 순서를 보장하지 않는다.
  1. 배열 요소들만을 순회하지 않는다.
// 배열 요소들만을 순회하지 않는다. let array = ['one', 'two']; array.name = 'my array'; for (let index in array) { console.log(index + ': ' + array[index]); } /* 0: one 1: two name: my array */ // 위와 같은 단점으로 for-of문이 추가되었다. const array = [1, 2, 3]; array.name = 'my array'; for (const value of array) { console.log(value); } /* 1 2 3 */ for (const [index, value] of array.entries()) { console.log(index, value); } /* 0 1 1 2 2 3 */
for–in 문객체의 프로퍼티를 순회하기 위해 사용하고 for–of 문배열의 요소를 순회하기 위해 사용한다.

Pass-by-reference

object type을 객체 타입 또는 참조 타입이라 한다.
참조 타입이란 객체의 모든 연산이 실제 값이 아닌 참조값으로 처리 됨을 의미한다.
원시 타입은 값이 한번 정해지면 변경할 수 없지만, 객체는 프로퍼티를 변경, 추가, 삭제가 가능하므로 변경 가능한 값이라할 수 있다.
객체 타입은 동적으로 변화할 수 있기에 메모리 공간을 신경써야한다고한다. 런타임에 메모리 공간을 확보하고 메모리의 힙 영역에 저장된다. 이에 반해서
원시 타입은 값(value)으로 전달된다. 즉, 복사되어 전달된다. 이를 pass-by-value라 한다.
// Pass-by-reference let foo = { val: 10 } // 여기서 bar에 foo를 할당함으로 참조값으로 설정 된것 let bar = foo; console.log(foo.val, bar.val); // 10 10 console.log(foo === bar); // true bar.val = 20; console.log(foo.val, bar.val); // 20 20 console.log(foo === bar); // true
foo 객체를 객체 리터럴 방식으로 생성하였다. 이 때 변수 foo는 객체 자체를 저장하고 있는 것이 아니라 생성된 객체의 참조값(address)를 저장하고 있다.
변수 bar에 변수 foo의 값을 할당하였다.
notion image
변수 foo의 값은 생성된 객체를 가리키는 참조값이므로 변수 bar에도 같은 참조값이 저장된다.
즉, 변수 foo, bar 모두 동일한 객체를 참조하고 있다. 따라서 참조하고 있는 객체의 val 값이 변경되면 변수 foo, bar 모두 동일한 객체를 참조하고 있으므로 두 변수 모두 변경된 객체의 프로퍼티 값을 참조하게 된다. 객체는 참조(Reference) 방식으로 전달된다.
결코 복사되지 않는다.
// 별개의 객체를 생성하여 참조값을 할당하였다. var foo = { val: 10 }; var bar = { val: 10 }; console.log(foo.val, bar.val); // 10 10 console.log(foo === bar); // false var baz = bar; console.log(baz.val, bar.val); // 10 10 console.log(baz === bar); // true
위와는 다른 예제이다.
변수 foo와 변수 bar는 비록 내용은 같지만 별개의 객체를 생성하여 참조값을 할당하였다.
즉 → foo와 bar의 참조값 즉 어드레스는 동일하지 않다.
 
notion image
변수 baz에는 변수 bar의 값을 할당하였다. 결국 변수 baz와 변수 bar는 동일한 객체를 가리키는 참조값을 저장하고 있다. 따라서 변수 baz와 변수 bar의 참조값은 동일하다.
 
여기까진 모던 자바스크립트 딥다이브의 내용이었고 아래론 공부한 내용이다.

객체

객체는 배열, 함수 그리고 흔히 보는 객체 리터럴을 통합해서 부르는 말이다.
notion image
이런 관점으로 해석해보면 위에서 공부했듯 원시 타입(string, number, bigint, boolen, undefined, symbol, null)을 제외한 모든 값이라고할 수 있다.
 
그럼? 위 ↑ 그림 처럼 배열, 함수를 객체라는 개념에 포함을 시킬까에 대한 의문이 생겼다.
이유는 함수와 배열도 객체의 성질을 사용할 수 있다.
let log = console.log; function hello() {}; hello.a = 'really?'; const array = []; array.b = 'wow'; log(hello.a); // really? log(array.b); // wow
위 예제를 확인해보면 대충 되는 이유는 알겠지만 정확하게 왜? 되는지에 대한 생각을 해야한다는 생각이들었다.
let log = console.log; function hello() {}; hello.a = 'really?'; const array = []; array.b = 'wow'; log(hello); // [Function: hello] { a: 'really?' } log(array); // [ b: 'wow' ]
이러한 특성은 JavaScript의 유연성을 나타내며, 특히 객체의 동적인 속성 추가 및 확장이 가능하다는 특징이다.
그러나 이렇게 사용하는 것은 일반적으로 권장되지 않는다.
함수에 속성을 추가하는 것은 가독성을 저해할 수 있으며, 배열을 객체처럼 사용하는 것은 의도치 않은 부작용을 초래할 수 있다.

객체를 객체로 비교

{} === {} // ?
과연 ? 물음표에 들어갈 값은 어떻게 될까~?
내가 1차적으로 생각했을 때는 음 아마… true? 아니었다. 생각이 짧았다. 객체는 비교하기 위해 두번 쓰게 되었고 결국 2번 선언 된 거랑 똑같기 때문이다.
[] === [] // ?
배열도? 맞다 fasle이다.
즉 , 객체를 제외한 원시 타입들은 따로 선언과 할당을 한다고 해도 같은 주소값을 포인팅하고 있다. 그러나, 객체는 다른게 하나의 객체를 생성하는 순간 새로운 주소 값이 할당 된다. 설령 똑같은 빈 객체라고 해도 주소 값이 다른 상황이 생기는 것이다.
한가지 예를 들면 같은 산본 레미안 아파트에 살아도 호수가 다른것과 같다.
그럼? 객체를 값이 같은지 확인하려면? 어떻게 할까?
const a = { name: 'youn' }; const arr = [1, 2, a]; log(a === arr[2]); // true
위처럼 비교하면 원하는 값을 얻을 수 있었다. 즉, 같은 주소값을 포인팅해주는 상황을 만들어 주면 된다.
 

마지막으로 당연하지만 객체를 짧게 정리해보자

  • 객체는 중괄호 {}를 이용해서 만든다.
  • 중괄호 안에는 키(key) : 값(value) 쌍으로 구성된 프로퍼티(property) 를 여러 개 넣을 수 있다.
  • 키엔 문자형, 값엔 모든 자료형이 허용된다.
  • 프로퍼티 키는 프로퍼티의 이름 이라고 불린다.
  • for...in 문은 상속된 열거 가능한 속성들을 포함하여 객체에서 문자열로 키가 지정된 모든 열거 가능한 속성에 대해 반복
  • 위에 공부했듯 for…of 문for…in 문 보다 대상이 더 적다.
    • 배열 요소들만을 순회하지 않는 문제로 of문이 추가된 것
자 이렇게 오늘은 객체를 정리해보았다. 사실 객체를 정리하면서 느낀 것은 안다고 생각한게 제대로 알진 못했다는 것 이었다. 객체, 배열, 함수는 진짜 제대로 잡고 나아가려한다. 나의 미래를 위해
Share article
RSSPowered by inblog