- [JavaScript] 클린코드 자바스크립트 : 객체2023년 10월 25일 00시 34분 46초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
이 글은 유데미의 "클린코드 자바스크립트" 강의 내용(섹션 7: 객체 다루기)을 정리한 것이다.
1. Shorthand Properties
ES2015 이후로 Shorthand Properties 덕분에 키와 값으로 이루어진 속성들을 축약할 수 있다.
1.1. CSS에서도 비슷한 것이 있다!
아래의 코드를 통해 CSS attribute가 축약되는 것을 보자! before는 after와 같이 축약될 수 있다.
.before { margin-top: 5px; margin-right: 5px; margin-bottom: 5px; margin-left: 5px; } .after { margin : 5px, 5px, 5px, 5px; }
1.2. JavaScript 객체의 축약!
아래의 코드는 축약하기 전의 코드이다.
const firstName = "il-nam"; const lastName = "kim"; const person = { firstName: firstName, lastName: lastName, getFullName: function () { return `${this.firstName} ${this.lastName}`; }, }; console.log(person.getFullName()); // il-nam kim
위 코드는 아래와 같이 축약이 가능하다. getFullName() 함수의 경우 Concise Method(간결한 메서드)라고 말한다.
const person = { firstName, lastName, getFullName() { return `${this.firstName} ${this.lastName}`; }, }; console.log(person.getFullName()); // il-nam kim
2. Computed Property Name
객체의 속성(property)으로서 계산된(computed) 값을 사용할 수 있다. 객체의 키에 대괄호([])를 이용해 작성한다.
const name0 = "1남"; const name1 = "2남"; const name2 = "3남"; const obj = { [name0]() { return `김${name0}`; }, [name1]() { return `김${name1}`; }, [name2]() { return `김${name2}`; }, }; // 위 코드는 이 코드가 축약된 것이다! // const obj = { // [name0]: function () { // return `김${name0}`; // }, // [name1]: function () { // return `김${name1}`; // }, // [name2]: function () { // return `김${name2}`; // }, // }; console.log(obj); // { '1남': [Function: 1남], '2남': [Function: 2남], '3남': [Function: 3남] } for (let i = 0; i < 3; i++) { console.log(obj[`${i + 1}남`]()); } // 김1남 // 김2남 // 김3남
3. Object Lookup Table
Lookup Table은 key와 value 형식을 갖는 것을 말한다. 이 것을 이용하면 불필요한 분기문을 줄일 수 있다.
// 이 코드는 스위치문을 사용한 것이다. 이 다음에 나오는 Lookup Table을 이용한 경우와 비교해 보자! function getUserTypeSwitch(type) { switch (type) { case "user1": return "사용자1"; case "user2": return "사용자2"; case "user3": return "사용자3"; default: return "해당 없음"; } } console.log(getUserTypeSwitch("user1")); /** * Lookup Table Case1 */ function getUserTypeObjectOr(type) { // 자바스크립트 컨벤션룰로서 대문자 스네이크 케이스는 상수로 취급한다. const USER_TYPE = { USER1: "사용자1", USER2: "사용자2", USER3: "사용자3", }; return USER_TYPE[type] || "해당 없음"; // 단축평가로 or연산자 사용 } console.log(getUserTypeObjectOr("USER1")); // 사용자1 /** * Lookup Table Case2 * 강사는 이 경우를 가장 바람직하다고 보았다! * USER_TYPE 상수를 별도로 정의한 경우이다. */ function getUserTypeObjectNullishCoalescing1(type) { const USER_TYPE = { USER1: "사용자1", USER2: "사용자2", USER3: "사용자3", UNDEFINED: "해당 없음", }; return USER_TYPE[type] ?? USER_TYPE.UNDEFINED; // ??연산자 사용 } console.log(getUserTypeObjectNullishCoalescing1("USER2")); // 사용자2 /** * Lookup Table Case3 * 상수 선언 없이 바로 return하였다. */ function getUserTypeObjectNullishCoalescing2(type) { return ( { USER1: "사용자1", USER2: "사용자2", USER3: "사용자3", }[type] ?? "해당 없음" ); } console.log(getUserTypeObjectNullishCoalescing2("USER4")); // 해당 없음 /** * Case2 사용예 * 상수를 포함하고 있는 모듈을 import하여 아래 코드와 같이 사용한다. */ // user-type.js 모듈 내용 export const USER_TYPE = { USER1: "사용자1", USER2: "사용자2", USER3: "사용자3", }; // user-type.js 모듈 import import { USER_TYPE } from "./user-type.js"; function getUserTypeBest(type) { return USER_TYPE[type] || "해당 없음"; } console.log(getUserTypeBest("USER5")); // 해당 없음
4. Object Destructuring
강의 내용 중 리액트의 props 등 객체 구조분해 할당이 사용되는 일반적인 경우들은 생략하고-개인적으로-평소에 잘 사용하지 않았던 아래의 코드의 경우를 정리해 보았다. 특별히 "case3"를 눈여겨보자!
// users 배열의 요소 중 1번 인덱스에 해당하는 "김이남"을 건너뛰는 코드를 작성하는 경우들이다. const users = ["김일남", "김이남", "김삼남"]; // case1 const case1 = users[0]; const case2 = users[2]; console.log(`case1, case2 : ${case1}, ${case2}`); // case1, case2 : 김일남, 김삼남 // case2 : 구조분해할당 const [case3, , case4] = users; console.log(`case3, case4 : ${case3}, ${case4}`); // case3, case4 : 김일남, 김삼남 // case3 : 구조분해할당 // 배열도 객체임을 상기하라! const { 0: case5, 2: case6 } = users; console.log(`case5, case6 : ${case5}, ${case6}`); // case5, case6 : 김일남, 김삼남
case3은 속성의 키에 새로운 변수명을 부여한 것이다. MDN에서 해당 내용을 확인할 수 있다.
5. Object.freeze
const는 상수처럼 값을 변경할 수 없지만, 객체나 배열의 경우 요소를 변경할 수 있다. 그런데 객체의 경우 Object.freeze를 사용하면 객체의 요소를 변경할 수 없도록 동결할 수 있다. 하지만 shallow freeze 만 가능하기 때문에 중첩된 객체의 경우에는 동결되지 않는다. 아래의 코드를 참고하자!
const USER = Object.freeze({ FIRST_NAME: "일남", LAST_NAME: "김", AGE: 99, CHILD: { FIRST: { FIRST_NAME: "가루", CHILD: "밥" }, SECOND: { FIRST_NAME: "자반" }, }, }); console.log(Object.isFrozen(USER)); // true // Object.isFrozen은 객체가 동결이 잘되었는지 확인함 USER.CHILD.SECOND.FIRST_NAME = "말이"; console.log(USER); // { // FIRST_NAME: '일남', // LAST_NAME: '김', // AGE: 99, // CHILD: { // FIRST: { FIRST_NAME: '가루', CHILD: '밥' }, // SECOND: { FIRST_NAME: '말이' } // 중첩 객체의 경우 요소가 변경되었다! // } // }
중첩된 객체를 동결하는 방법은 다음과 같다.
5.1. 중첩된 객체 동결 방법
- 대중적인 유틸 라이브러리 사용(lodash)
- TypeScript의 readonly 사용
- 직접 유틸 함수 작성
5.2. 유틸 함수 직접 작성 방법
객체를 깊게 동결하려면 중첩된 객체의 모든 프로퍼티와 하위 객체도 동결해야 한다. 따라서 아래의 순서로 코드를 작성하면 된다.
- 객체를 순회
- 값이 객체인지 확인
- 객체이면 재귀
- 그렇지 않으면 Object.freeze
6. prototype 조작 지양하기
prototype을 조작하는 방식은 class 문법이 추가되지 않았을 때 주로 사용됐었다. 예제 코드는 아래와 같다.
// prototype function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } Person.prototype.sayName = function () { return this.lastName + this.firstName; }; const kim1 = new Person("일남", "김"); console.log(kim1.sayName()); // 김일남
하지만 이제 자바스크립트에 class가 추가되었으므로 prototype 사용을 지양하는 것이 좋다. 그 이유는 다음과 같다.
- 이미 JS는 많이 발전했다.
- JS 빌트인 객체를 건들지 말자! prototype을 사용하면 내장 객체나 메서드들을 변경할 위험성이 크기 때문이다.
- marpple/FxJS, lodash도 자바스크립트의 내장 객체나 메서드와 유사한 기능을 가지고 있지만 절대 prototype을 건드리지 않는다!
- class 문법의 경우도 바벨로 변환하면 결국 prototype을 이용하는 코드임을 확인할 수 있다. 따라서 prototype에 대한 이해 갖추는 것은 좋지만 직접 사용하는 것은 조심해야 한다.
- 몽키패치 조심! 어떤 기능을 위해 이미 있던 코드에 삽입하는 것이다. 원숭이 패치는 종종 위험한 기술로 간주된다. 왜냐하면 몽키패치는 기존 코드와의 충돌이 있을 수 있기 때문이다.
아래의 코드는 위에서 언급한 prototype 코드를 class 문법으로 변경한 것이다.
// class class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } sayName() { return this.lastName + this.firstName; } } const kim1 = new Person("일남", "김"); console.log(kim1.sayName()); // 김일남
7. hasOneProperty
hasOwnProperty() 메서드는 객체가 특정 프로퍼티를 가지고 있는지 확인 후 불리언 값(true 나 false)을 반환한다.
const person = { name: "김일남", }; console.log(person.hasOwnProperty("name")); // true console.log(person.hasOwnProperty("age")); // false
하지만-mdn의 예제와 같이-아래 코드에서 객체의 프로퍼티로 hasOwnProperty() 함수 정의하는 경우에 안전하지 못하다.
const foo = { hasOwnProperty() { return false; }, bar: "Here be dragons", }; // 이 케이스에서는 foo객체의 hasOwnProperty 속성과 겹친다. // 결국 "Here be dragons"가 아닌 false가 반환된다. console.log(foo.hasOwnProperty("bar")); // false // prototype타입에 접근하는 것은 좋지 않지만 call형태로 접근하는 것이 안전하다. console.log(Object.prototype.hasOwnProperty.call(foo, "bar")); // true
아래와 같이 재사용 가능한 함수로 만들어 사용하는 것이 유용하겠다.
function hasOwnProp(targetObj, targetProp) { return Object.prototype.hasOwnProperty.call(targetObj, targetProp); } console.log(hasOwnProp(foo, "bar")); // true
8. 직접 접근 지양
데이터의 값에 직접 접근해서 변경하지 않도록 해야한다. 이는 예측 가능한 코드를 작성하기 위함이다. 이는 대표적인 리액트 상태관리 라이브러리인 Redux가 추구하는 바이기도 하다. 예측 가능할때 디버깅도 쉬워진다. 아래의 코드는 set 함수를 통해 model 객체의 값을 변경하는 예제 코드이다.
// 직접 접근 지양 const model = { isLogin: false, isValidToken: false, }; // model 객체에 대신 접근 function setLogin(bool) { model.isLogin = bool; serverAPI.log(model.isLogin); // 로그를 추가하여 확인 가능 } // model 객체에 대신 접근 function setValidToken(bool) { model.isValidToken = bool; serverAPI.log(model.isValidToken); // 로그를 추가하여 확인 가능 }
9. Optional Chaining
객체의 값에 접근할 때 체이닝(.)을 통해 접근한다. 이때 ?를 이용해 선택적으로 접근할 수 있다. 이를 통해 해당 객체 내에 명시한 키가 없더라도 터지지 않고 undefined를 반환 받을 수 있다.
// response 객체에 userList 이하를 주석 처리했다. const response = { data: { // userList: [ // { // info: { // tel: "010", // email: "kim1@gmail.com", // }, // }, // ], }, }; // 1. 체이닝으로 email에 접근 const getUserEmailByIndex = (userIndex) => { return response.data.userList[userIndex].info.email; }; console.log(getUserEmailByIndex(0)); // 에러 // 2. if문 사용 에러 처리 const getUserEmailByIndex = (userIndex) => { if (response.data.userList) { return response.data.userList[userIndex].info.email; } return "알 수 없는 에러 발생"; }; // 3. if문 사용 에러 처리 // 가능한 모든 깊이에 대한 에러를 고려하게 된다면 if문 중첩이 깊어지게 된다. const getUserEmailByIndex = (userIndex) => { if (response.data) { if (response.data.userList) { if (response.data.userList[userIndex]) { return response.data.userList[userIndex].info.email; } } } return "알 수 없는 에러 발생"; }; // 4. if문 사용 에러 처리 // &&문으로 if 한번만 사용할 수 있으나 이 역시 &&가 늘어지게 된다. const getUserEmailByIndex = (userIndex) => { if ( response.data && response.data.userList && response.data.userList[userIndex] ) { return response.data.userList[userIndex].info.email; } return "알 수 없는 에러 발생"; }; // 5. Awesome "?" // 배열의 경우 대괄호 앞에 .이 붙는 것을 유념하라! const getUserEmailByIndex = (userIndex) => { if (response?.data?.userList?.[userIndex]?.info?.email) { return response?.data?.userList?.[userIndex]?.info?.email; } return "알 수 없는 에러 발생"; };
728x90반응형'언어·프레임워크 > JavaScript' 카테고리의 다른 글
[JavaScript] split() 문자열 메서드 이용해 특정 문자열의 개수 구하기 (0) 2023.12.10 [JavaScript] every 배열 메서드를 활용한 소수(prime) 판별 (0) 2023.11.18 [JavaScript] base64 데이터를 다룰 때 만난 TypeError 에러, base64를 Blob로 변환하여 해결! (0) 2023.10.20 [JavaScript] 클린코드 자바스크립트 : 배열 (1) 2023.10.14 [JavaScript] 클로저(Closure) 정리! (0) 2023.09.26 다음글이 없습니다.이전글이 없습니다.댓글