-
Udemy 클린코드 JS 강의 정리JavaScript & TypeScript 2022. 1. 6. 22:48
※ 출처: https://www.udemy.com/course/clean-code-js/
- 총 섹션 13으로 이루어져 있고 22년 01월 기준 섹션 8까지 올라와 있음.
- 나머지 강의 아직 안 올라와서 나중에 듣고 업데이트 하기.
- 앞에 부분은 이전에 들어서 제목만 적음.
- 01-01 :: ~5장, 26강 early return
- 01-02 :: ~ 8장, 57강 화살표 함수
- 01-06 :: ~ 8장, 60강 Closure
- 이곳저곳에서 다 듣거나 본 내용이라 어렵진 않아 2배속으로 봤는데 막상 적용 시키지 않는 것도 좀 있는듯..
- 계속 의식하고 적용시키려고 해야할 듯.
2장: 변수 다루기
- var 지양하기
- function scope & block scope
- 전역 공간 사용 최소화
- 임시변수 제거하기
- 호이스팅 주의하기
3장: 타입 다루기
- 타입 검사
- undefined & null
- eqeq 줄이기
- 형변환 주의하기
- isNaN
4장: 경계 다루기
- min - max
- begin - end
- first - last
- prefix - suffix
- 매개변수의 순서가 경계다.
5장: 분기 다루기
- 값식문
- 삼항 연산자 다루기
- Truthy & Falsy
- 단축평가
- 부정조건문 지양하기
- 코드를 좀 더 명시적이게 작성하기 위함.
- 생각을 한번 더 해야한다 → 실수 확률 높음.
- if 문이 처음부터 오고 true 부터 실행시킨다.
- 그럼 언제?
- early return
- form validation
- 보안 혹은 검사 로직
// 부정조건문 지양하기 if (!isCondition) { console.log('거짓인 경우만 실행'); } if (isNotCondition) { console.log('거짓인 경우만 실행'); }
- Default Case 고려하기
- edge case 를 고려하자.
- ex) 함수의 인자가 안들어오거나 잘못들어오거나 이런경우 고려하자.
- 대부분의 라이브러리나 언어에서 default 값 설정을 중요시하게 여김.
- 유저의 실수를 대비
function safeParseInt(number, radix) { return parseInt(number, radix || 10); }
- 명시적인 연산자 사용 지양하기
- 연산자 우선순위를 생각해서 작성 하지말고 () 와 같은 걸 써서 좀 더 명시적이게 작성하자.
- 예측 가능하고 누가봐도 읽기 쉽고 디버깅 하기 쉬운...
- Nullish coalescing operator
- null 과 undefined 를 평가할때만 사용해야 한다!
function createElement1(type, height, width) { const element = document.createElement(type || 'div'); element.style.height = String(height || 10) + 'px'; element.style.width = String(width || 10) + 'px'; return element; } function createElement2(type, height, width) { const element = document.createElement(type ?? 'div'); element.style.height = String(height ?? 10) + 'px'; element.style.width = String(width ?? 10) + 'px'; return element; } // width, height 가 0인 element 를 생성하고 싶음. const el = createElement1(span, 0, 0); console.log(el.style.height) = '10px'; // ??? const el = createElement2(span, 0, 0); console.log(el.style.height) = '0px'; // good
- createElement1 의 경우 0은 falsy 값으로 인식되어 기본값 10이 들어가버림.
- createElement2 의 ?? 연산자는 좌항이 null 이거나 undefined 일 경우만 우항의 값을 대입함.
// No chaining with AND or OR operators // syntax error null || undefined ?? 'foo'; // good (null || undefined) ?? 'foo';
- 드모르간의 법칙
if (isValidToken && isValidUser) { console.log('로그인 성공'); } // bad - 감싸서 부정연산자를 넣는건 더 복잡해지고 헷갈린다. // 뒤에 연산이 더 추가된다면 복잡해짐. if (!(isValidToken && isValidUser)) { console.log('로그인 실패'); } // good - 좀 더 간단해짐 if (!isValidToken || !isValidUser) { console.log('로그인 실패'); }
6장: 배열 다루기
- JavaScript의 배열은 객체다.
// 배열인지 체크하고 싶을때 이렇게 사용 추천 Array.isArray(arr);
- Array.length
- Array.length 가 배열의 길이를 보장하진 않음. 그냥 마지막 인덱스라고 봐야함.
- JS 에서 배열의 length 는 의식적으로 주의해서 사용하자.
// Array.length 이용하기 Array.property.clear = function() { this.length = 0; } function clearArray(array) { array.length = 0; return array; } const arr = [1, 2, 3]; // arr.clear(); console.log(arr); // [] console.log(clearArray(arr)); // []
- 배열 요소에 접근하기
// ex1) bad - 배열[num] 이런식으로 직접 접근하는건 어떤 값인지 명확하지 않음. function operateTime(input, operators, is) { inputs[0].split('').forEach((num) => { .... logic }); inputs[1].split('').forEach((num) => { .... logic }); } // ex1) good1 - 구조분해할당을 사용해서 접근 (명시적) function operateTime(input, operators, is) { const [firstInput, secondInput] = inputs; firstInput.split('').forEach((num) => { .... logic }); secondInput.split('').forEach((num) => { .... logic }); } // ex1) good2 - 받을때부터 분해해서 받기 function operateTime([firstInput, secondInput], operators, is) { firstInput.split('').forEach((num) => { .... logic }); secondInput.split('').forEach((num) => { .... logic }); } // ex2) bad - 직접 접근은 이게 뭐하는애인지 모름. function formatDate(targetDate) { const date = targetDate.toISOString().split('T')[0]; ... logic } // ex2) good1 - 배열에 하나만 들어있어도 구조분해할당 가능 function formatDate(targetDate) { const [date] = targetDate.toISOString().split('T'); ... logic } // ex2) good2 - 유틸함수 만들어 사용 function head(arr) { return arr[0] ?? ''; } function formatDate(targetDate) { const date = head(targetDate.toISOString().split('T')); ... logic }
- 유사 배열 객체
function generatePriceList() { // arguments는 배열이 아님 // arguments.map is not a function console.log(Array.isArray(arguments)); // false 1. return arguments.map((arg) => arg + '원'); // map 을 사용하고싶으면 Array.from 으로 배열로 바꿔서 사용해야함 2. return Array.from(arguments.map((arg) => arg + '원')); } generatePriceList(100, 200, 300, 400, 500);
- 불변성
- 배열을 복사한다.
- 새로운 배열을 반환하는 메서드들을 활용한다.
- Array.filter(), map(), slice() ...
- for 문 배열 고차 함수로 리팩터링
- for 문 대신 배열의 고차 함수를 사용하자.
- 배열 메서드 체이닝 활용하기
// 이런식으로 메서드 체이닝 활용 가능 function getWonPrice(priceList) { return priceList .filter(isOverOneThousand) .sort(ascendingList) .map(suffixWon); }
- map vs forEach
- return 이 있는가 없는가
- continue vs break
- 고차함수 안에서 break, continue 는 syntax error 임
- 사용하고 싶을땐 try catch 이나 차라리 for 문 사용
- every, some, find, findIndex() 를 사용하여 반복을 종료할 수 있음.
7장: 객체 다루기
- Shorthand Properties
const firstName = 'subin'; const lastName = 'lee'; const name = someFunction({ firstName, lastName, });
- Computed Property Name
// ex const handleChange = (e) => { setState({ [e.target.name]: e.target.value, }); }
- Lookup Table (순람표)
// bad - 새로운 조건이 추가될때마다 쭉 늘어남 function getUserType(type) { switch (key) { case 'ADMIN': return '관리자'; break; case 'INSTRUCTOR': return '강사' break; default: return '해당없음'; } } // good - 객체, computed property 활용 function getUserType(type) { // ex1 - 객체 사용 const USER_TYPE = { ADMIN: '관리자', INSTRUCTOR: '강사', STUDENT: '학생', UNDEFINED: '해당 없음', } // 1 return USER_TYPE[type] ?? '해당 없음'; // 2 return USER_TYPE[type] ?? USER_TYPE.UNDEFINED; // ex2 - 바로 리턴, ex1 방법을 더 권장함. return ( { ADMIN: '관리자', INSTRUCTOR: '강사', STUDENT: '학생', }[type] ?? '해당 없음'; ) }
- Object Destructuring
// ex1 - 인자의 순서를 지켜야함. function Person(name, age, location) { this.name = name; this.age = age; this.location = location; } const subin = new Person('subin', 29, 'korea'); // ex2 - 구조분해할당 - 인자 순서 안지켜도됨. // 인자가 3개 이상일때 이런식으로 하는걸 추천함. function Person({ name, age, location }) { this.name = name; this.age = age ?? 30; this.location = location ?? 'korea'; } const subin = new Person({ name: 'subin', age: 29, location: 'korea', }); // ex3 - 필수적인 값을 받고싶을때 function Person(name, { age, location }) { this.name = name; this.age = age; this.location = location; } const options = { age: 29, location: 'korea', } const subin = new Person('subin', options);
- Object.freeze
- 대중적인 유틸 라이브러리 (lodash) 사용
- 직접 유틸 함수 생성 (deepFreeze 처럼)
- TS 사용 ⇒ readonly
// Object.freeze -> 변화 불가하게 만듬 const STATUS = Object.freeze({ PENDING: 'PENDING', SUCCESS: 'SUCCESS', FAIL: 'FAIL', OPTIONS: { GREEN: 'GREEN', RED: 'RED', }, }); STATUS.PENDING = 'P2'; // 변화 X STATUS.KING = 'KING'; // 추가도 안됨 console.log(STATUS.PENDING); // PENDING Object.isFrozen(STATUS.PENDING); // true // 깊은 freezing 안됨. Object.isFrozen(STATUS.OPTIONS); // false STATUS.OPTIONS.GREEN = 'G'; STATUS.OPTIONS.YELLOW = 'YELLOW'; console.log(STATUS.OPTIONS.GREEN); // G console.log(STATUS.OPTIONS.YELLOW); // YELLOW
- Prototype 조작 지양하기
- 이미 JS는 많이 발전했다.
- 직접 만들어서 모듈화
- JS 빌트인 객체를 함부로 건들지 말자.
- 다른 라이브러리들도 프로토타입을 건들진 않음.
- hasOwnProperty
// ex1. const person = { name: 'subin', } person.hasOwnProperty('name'); // true person.hasOwnProperty('age'); // false // ex2. 예약어 보호를 받지 못함 // -> Object.prototype.hasOwnPrototype.call() 이런식으로 사용 const foo = { hasOwnProperty: function() { return 'hasOwnProperty'; }, bar: 'string', }; foo.hasOwnProperty('bar') // hasOwnProperty Object.prototype.hasOwnProperty.call(foo, 'bar') // true // ex3. 유틸로 만들기 function hasOwnProp(targetObj, targetProp) { return Object.prototype.hasOwnProperty.call( targetObj, targetProp, ); }
- 직접 접근 지양하기
- 예측 가능한 코드를 작성해 동작이 예측 가능하게 하자.
- 데이터에 접근할때는 항상 안전하게 접근하자.
- 괜히 setter, getter 를 사용하는게 아니다.
- Optional Chaining
- Extends & Mixin
8장: 함수 다루기
- 함수, 메서드, 생성자
- 함수, 메서드, 생성자를 구분할 수 있고 어쩔때 사용하는지 알아야한다.
- argument & parameter
- Parameter (Formal Parameter)
- Argument (Actual Parameter)
function example(parameter) { console.log(parameter); } const argument = 'foo'; example(argument);
- 복잡한 인자 관리하기
- 무조건 인자가 3개 이상이라고 나쁜것은 아니다. 맥락이 중요하다.
- 객체 구조분해할당 사용하면 좋다.
- 다른사람이 이 함수를 사용해도 문제 없게 명시적으로 작성.
- Default value, Default parameter
// ex1 function createCarousel({ margin = 0, center = false, navElement = 'div', } = {}) { ... logic return { margin, center, navElement, }; } createCarousel(); // {margin = 0, center = false, navElement = 'div'} // ex2. 필수로 받아야하는 인자 설정 const required = (argName) => { throw new Error(`required ${argName}`) }; function createCarousel({ items = required('items'), margin = 0, center = false, navElement = 'div', } = {}) { ... logic return { margin, center, navElement, }; }
- Rest Parameters
- 인자의 가장 마지막에 들어와야함.
- 배열로 들어옴.
function sumTotal(initValue, bonusValue, ...args) { console.log(initValue); // 100 console.log(bonusValue); // 99 Array.isArray(args); // true return args.reduce((acc, cur) => acc + cur); } sumTotal(100, 99, 1, 2, 3, 4, 5, 6, 7, 8);
- void & return
- void function 에 불필요하게 return 을 해줄 필요가 없음.
- API 명세를 잘 읽어서 return 값이 무엇인지 알 필요가 있음.
- 함수명으로 반환값을 유추할 수도 있으니 네이밍을 잘하자.
- 화살표 함수
- 내부에서 arguments, call, apply, bind 함수 사용 안됨.
- this 주의해야함.
- 생성자로 사용할 수 없음.
- 클래스의 메소드로 사용할때 예외가 많음.
- Callback Function
- 제어권을 위임하는 것.
- 무조건 나쁘다? 잘못 이해하는 것임.
- 순수 함수
- side effect를 일으키지 않는 함수
- 예측이 가능한..
- input이 동일하면 output도 동일해야지.
let num1 = 10; let num2 = 20; function impureSum() { return num1 + num2; } function puerSum(num1, num2) { return num1 + num2; } // 객체, 배열 => 새롭게 만들어서 리턴 function changeObj(targetObj) { // targetObj.num = 100; return { ...targetObj, num: 100 }; }
- Closure
- 어색해서 그런지 읽히는게 직관적이지 않음.
- 어디에 어떻게 써먹을까 했는데 잘쓰면 엄청 유용해보임.
- 쓰려고 연습해야할듯.
// ex.1 function add(num1) { return function sum(num2) { return num1 + num2; } } const addOne = add(1); // function const addTwo = add(2); // function const addThree = add(1)(2) // 3 // ex.2 function add(num1) { return function (num2) { return function (calculateFn) { return calculateFn(num1, num2); } } } function sum(num1, num2) { return num1 + num2; } function multiple(num1, num2) { return num1 * num2; } const addOne = add(5)(2); // function const sumAdd = addOne(sum); // 7 const sumMultiple = addOne(multiple); // 10 // ex.3 function log(value) { return function(fn) { fn(value); } } const logFoo = log('foo'); logFoo((v) => console.log(v)); logFoo((v) => console.info(v)); logFoo((v) => console.error(v)); logFoo((v) => console.warn(v)); // ex.4 const arr = [1, 2, 3, 'A', 'B', 'C']; // 4-1 const isNumber = (value) => typeof value === 'number'; const isString = (value) => typeof value === 'string'; // 4-2 function isTypeOf(type) { return typeof value === type; } const isNumber = (value) => isTypeof('number'); const isString = (value) => isTypeof('string'); // 4-3 function isTypeOf(type) { return function (value) { return typeof value === type; } } const isNumber = isTypeOf('number'); const isString = isTypeOf('string'); arr.filter(isNumber); arr.filter(isString); // ex.5 function fetcher(endpoint) { return function (url, options) { return fetch(endpoint + url, options) .then((res) => { if (res.ok) { return res.json(); } else { return new Error(res.error); } }) .catch((err) => console.err(err)); } } const naverApi = fetcher('http://naver.com'); const daumApi = fetcher('http://daum.net'); naverApi('/webtoon').then((res) => res); daumApi('/webtoon').then((res) => res); // ex.6
9장: 추상화하기
10장: 에러 다루기
11장: Browser & Web API
12장: 도구에 위임하기
13장: 함께하기
'JavaScript & TypeScript' 카테고리의 다른 글
fastify-multipart handle multiple file streams and fields in TypeScript (2) 2022.09.22 브라우저, JS 흐름 정리 메모 (2) 2022.05.22 Next export 시 Image Optimization 설정 문제 (3) 2022.01.03 TypeORM synchronize true 시 이전 table 까지 생성 문제 (2) 2021.12.26 axios (0) 2020.12.13