1.변수다루기
# 섹션1. 변수 다루기 ## 1. var를 지양하자 **1. var가 아닌 const를 사용해야 하는 이유** - let과 const 는 ES2015에 등장 - var는 함수스코프 ,let & const 는 블록 스코프를 가진다. - 블록스코프는 TDZ를 유의해야한다. - var(함수스코프) 위험한 요소 - 변수명이 같지만 다른 값이 할당 된 경우에도 error를 발생시키지 않는다. - 가장 마지막에 할당한 값이 리턴 - 선언 이전에 변수를 호출해도 error를 발생시키지 않는다. - undefined 리턴 - 재할당, 재선언의 제약이 없이 계속 가능하다. - var(함수스코프) 위험한 이유 - 1000줄, 10000줄등 코드가 방대해진다면, 모든 변수를 통제할 수 없기 때문에 이전에 선언된 변수를 예기치 않게 재할당, 재선언하여 예기치않은 오류를 발생시킬 수 있다. - let이 해결해 줄 수 있는 부분 - 재선언이 불가능하기 때문에 중복된 변수를 사용할 수 없다. - const가 해결해 줄 수 있는 부분 - 재선언 뿐만 아니라 재할당이 불가능하기 때문에, 선언한 값이 변하지 않음을 보장할 수 있다. - 단, 참조타입의 자료형의 경우, 내부의 값은 변경가능하다. ### 한 줄 요약 **var는 재선언과 재할당이 가능하기 때문에 예상치 못한 오류를 발생시킬 수 있다. 따라서 let과 const를 통해 예측가능한 코드를 작성한다.** ## 2. function scope & block scope **Function Scope와 BlockScope** - 함수스코프는 오로지 함수를 기준으로 스코프를 구분하기 때문에, if(), for() 문 등의 block스코프 내부에서 값을 변경한다면, 전역의 값에 영향을 미치게 된다. ```js //Function Scope by Var var global = "전역"; if (global === "전역") { var global = "지역"; console.log(global); // '지역' } console.log(global); // '지역' ``` ```js // Block Scope by let let global = "전역"; if (global === "전역") { let global = "지역"; console.log(global); // '지역' } console.log(global); // '전역' ``` **let보다 const 사용을 지향하는 이유** let은 재할당이 가능하기 때문에, 실수로 인하여 값이 바뀔 가능성이 있다. 따라서 반드시 재할당이 필요한 변수에만 let을 사용한다. - const를 기능적으로 바라보았을 땐 단순히 변수의 재할당이 불가능하다 이지만 const를 사용한다는 뜻은 하나의 변수는 하나의 기능만 한다는 뜻을 내포하고 있습니다. - 다른 개발자가 const로 선언한 변수와 변수명을 보면 해당 변수는 하나의 목적을 위해 만들어졌다는 것이 파악되기 때문에 협업과 유지 보수에 있어서 많은 이점을 가져올 수 있습니다. - 재할당 금지가 값의 변경 금지를 의미하지는 않는다. - 객체와 배열의 경우 메소드와 프로퍼티를 통해 내부의 값들을 변경할 수 있다. ### 한 줄 요약 **BlockScope를 통해 scope의 전역오염을 막을 수 있고, 이를 위해서 let,const를 통해 변수를 선언한다.** **const는 해당 변수가 하나의 목적을 위해 만들어졌다는 것이 분명하기 때문에 협업과 유지보수에 있어서 장점이 있다.** - ## 3. 전역 공간 사용 최소화 **전역공간이란?** 전역공간은 최상위 공간을 의미한다. - global 혹은 window - 브라우저 환경의 경우 `window` - NodeJS 환경의 경우 `global` ### 사용하면 안되는 이유 '어디에서나 접근할 수 있으면 좋은 것 아니야?' => No!! **1. 서로 다른 js파일에서 window.변수, 혹은 변수를 직접적으로 접근할 수 있다.** - 예상치 못한 동작 - 선언이 안되어 있는 데 사용이 가능한 case 발생 - 보안상 다른 파일의 정보를 접근가능하다는 점 **2. WebAPI와 중복된 변수명을 선언할 경우, 코드환경이 아닌 실행환경(브라우저)에서 error를 발생한다.** - setTimeout 이라는 변수명을 사용할 경우, 전역공간(window)가 가지고 있는 setTimeout이 변경되어, 다른 어떤 곳에서도 setTimeout함수를 사용할 수 없게 된다. - 코드 환경에서 error를 띄우지 않는다는 것도 치명적이다! ### 전역공간을 사용하지 않는 방법 **1. const, let을 통한 blockScope 사용** **2. 즉시실행함수 (IIFE)** - 즉시 실행 함수 내부에서 사용하는 변수들은 외부에서는 접근할 수 없다. **3. Module** - es6의 export,import 형식의 모듈 type 사용 - 조금 더 공부 필요 (https://github.com/Shinye/TIL/blob/master/JavaScript/module.md) **4. Closure패턴 사용** ### 한 줄 요약 **전역공간을 사용하게 된다면, 어디에서나 접근이 가능하여 실행환경에서 예상치 못한 오류가 발생할 수 있다. 따라서 전역변수가 아닌 지역변수만을 사용하고, window,global 조작하지 않는 것, const,let, IIFE, Closure등 으로 해결할 수 있다.** ## 4. 임시변수 제거하기 **1.임시변수란** 임시변수는 어떤 특정 Scope안에서 전역변수 처럼 활용되는 변수 <br /> 아래와 같은 const로 선언한 임시객체와 같은 경우도 **함수가 커지다보면**, 전역변수와 같은 역할을 하게 된다. ```js function getElement() { const result = {}; result.title = document.querySelector("title"); result.text = document.querySelector("text"); // ... return result; } ``` **2.임시변수가 위험한 이유** 함수를 작게 만들거나 스코프가 작은 상황에서는 위험하지 않지만, 함수가 커졌을 때 이러한 습관이 남아있다면, 팀원이나 몇개월 후의 내가 임시변수를 사용할 유혹을 받을 수 있다. 즉 임시변수는 CRUD등의 조작(SideEffect)하는 유혹을 발생시킨다. **3.임시변수를 대체하는 방법** 원칙: 함수를 잘게 쪼개어서 사용 임시객체의 경우 해당 함수의 역할을 정확하게 살펴보고, 객체안에 바로 선언하는 방식 ```js function getElement() { const result = { title: document.querySelector("title"), text: document.querySelector("text"), // ... }; return result; } ``` 혹은 기능의 범위가 작고 명확하다면, 바로 return하여 **함수명을 통해 변수가 가지는 의미를 대체**할 수 있고, sideEffect를 줄일 수 있다. ```js function getElement() { return { title: document.querySelector("title"), text: document.querySelector("text"), // ... }; } ``` **4. Date사례** 시간을 가져오는 util성 함수가 있다. 만약 현재 함수가 할 수 없는 추가적인 스펙이 요구되었을 때, 문제가 발생할 수 있다. 추가 스펙이 요구되었을 때 2가지 방법이 가능하다. 1. 함수를 추가로 만드는 것 2. 함수를 유지보수하며 수정하는 것 2번 방법은 다른 곳에서 사용하는 곳이 있다면 문제를 발생시킬 가능성이 높다. 따라서 기존 getDateTime함수는 CRUD없이 날짜 자체만을 리턴시키는 역할을 고정시키고, 해당 함수를 호출하여 변경된 날짜를 리턴하는 새로운 함수를 추가시켜준다. 함수를 잘게 쪼갬과 동시에 sideEffect를 줄일 수 있다. ```js // BAD Case function getDateTime(targetDate) { let month = targetDate.getMonth(); let day = targetDate.getDate(); let hour = targetDate.getHours(); month = month >= 10 ? month : "0" + month; day = day >= 10 ? day : "0" + day; hour = hour >= 10 ? hour : "0" + hour; return { month, day, hour, }; } ``` ```js /* Good Case 1) 객체 자체를 바로 리턴 (CRUD X) 2) 해당 함수를 호출하여 새로운 함수 정의 */ function getDateTime(targetDate) { const month = targetDate.getMonth(); const day = targetDate.getDate(); const hour = targetDate.getHours(); return { month: month >= 10 ? month : "0" + month, day: day >= 10 ? day : "0" + day, hour: hour >= 10 ? hour : "0" + hour, }; } function getDateTime() { const currentDateTime = getDateTime(new Date()); return { month: computedKrDate(currentDateTime.month) + "분 전", day: computedKrDate(currentDateTime.day) + "분 전", hour: computedKrDate(currentDateTime.hour) + "분 전", }; } ``` **5. Point 반복** 처음부터 임시변수를 사용할 유혹을 받지 않도록, 하나의 역할만 할 수 있는 함수를 작성하기 위해 바로 return 하는 방식 많이 사용한다. ```js //BAD CASE function getRandomNumber(min, max) { const randomNumber = Math.floor(Math.random() * (max + 1) + min); // .을 통해 해당 값을 변경하고자 하는 유혹을 받게 된다. // randomNumber. return randomNumber; } // Good Case function getRandomNumber(min, max) { return Math.floor(Math.random() * (max + 1) + min); } ``` **6. 명령형 함수 보다 선언형 함수로 작성하기** 임시 배열을 통해 로직을 작성하다 보면 명령형 함수로 작성될 가능성이 높다. 명령형 함수: 로직에 따라 값이 바뀌기 때문에 예상하기 힘들다. 선언형 함수: 어떤 값이 나올 것이 라는 것을 명확히 하여 예상가능하다. 선언형 함수를 작성하기 위한 방법으로 고차배열함수등을 활용할 수 있다. (추후 배우게 됨) ```js // Bad Case // 입문자들이 주로 쓰는 패턴 // 임시변수 + 명령형 함수 function getSomeValue(params){ let tempVal = ''; for(let index = 0; index < array.length; index++){ temp = array[index]; temp += array[index]; temp += array[index]; } if( temp ?? ...){ tempVal = ?? } else if( temp ??){ temp += ?? } return temp; } ``` **7.요약** - 임시변수 좋지 않다 - 이유? 명령형으로 가득찬 로직 - 명령형? - 1. 어디서 어떻게 잘못되었는지 디버깅 어렵다 - 2. 추가적인 코드를 작성하게 하는 유혹을 발생 - 결과적으로 유지보수가 어렵게 만듦 - 해결책 - 1. 함수 나누기 - 2. 바로 반환 - 3. 고차함수 (map, filter, reduce) - 4. 선언형 코드 작성하기 ### 한 줄 요약 **임시변수는 좋지 않다.<br />** **임시변수는 명령형 로직으로 어디서 어떻게 잘 못 되었는지 디버깅이 어렵고, 추가적인 코드 작성을 붙이게 되는 유혹을 만들어, 유지보수가 어렵게 만든다.** **따라서 함수의 역할을 명확히 하여 data를 조작 없이 리턴하는 함수, data를 가져와서 조작하는 함수등으로 쪼개어 분리하거나, 조작이 필요한 경우 고차함수를 사용하고 선언형 코드로 작성하여 이러한 sideEffect를 제거할 수 있다.** ### 느낀 점 임시변수, 특히 임시배열을 선언하고 채워나간 후 리턴하는 방식은 코딩테스트를 할 떄 내가 많이 사용하던 방법이었다. 인턴생활을 할 때에도, 이런 패턴보다는 고차배열함수등을 통한 방식을 지향한다는 것을 배웠었는데 다시 한 번 중요성을 인지하게 되었다. ## 5. 호이스팅 주의하기 **1.호이스팅이란** 호이스팅은 **런타임에서** 선언과 할당이 분리되어 선언이 최상단으로 끌어올려지는 것 - `런타임` - 코드를 작성할 때 예측하지 못한 실행결과가 노출되는 어려움이 발생된다. - var로 선언한 변수가 `초기화가 제대로 되어 있지 않았을 때(?)` undefined로 최상단에 끌어올려지는 현상 - 초기화는 선언+할당 단계 - '초기화가 제대로 되어 있지 않다'는 선언만 하고 할당하지 않은 경우 - var로 선언한 변수 뿐만 아니라 `함수선언문도 호이스팅 되는 것을 주의`해야함 (함수 선언문이 변수를 덮어씌어버림) ```js var sum; console.log(typeof sum); // function function sum() { return 1 + 2; } ``` **2. 호이스팅을 방지하는 방법** 1. var가 아닌 `let, const`를 통해 변수를 선언한다. - let, const는 함수의 선언과 할당이 동시에 이루어진다. 2. 함수선언 시 함수선언문이 아닌 `함수표현식`을 사용한다. **3. var, let,const의 선언,초기화,할당 과정** [ref](https://medium.com/korbit-engineering/let%EA%B3%BC-const%EB%8A%94-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85-%EB%90%A0%EA%B9%8C-72fcf2fac365) > 초기화단계 > 초기화 단계는 선언된 변수를 위한 메모리를 확보하고, 해당변수를 undefined로 초기화 하는 단게 > var > var는 `1.선언과 초기화 단계가 같이 발생` 한 후 `2.할당 단계 발생` - var는 초기화단계 이전에 해당 변수에 접근하면 undefined를 반환한다. - 호이스팅에 의해 `선언이 끌어올려지면 초기화단계가 같이 끌어올려지게 되어` 변수에 undefined가 할당된다. > let > let은 `1.선언` `2.초기화` `3.할당` 로 구성 - 초기화 단계 이전에 해당 변수에 접근할 경우 `Reference에러(by TDZ)`가 발생한다. - 호이스팅 시 선언 부만이 끌어올려지고, `초기화단계가 끝나지 않았기 때문에` 실제 `변수 선언 code를 만나 초기화`되기 전에는 reference 에러가 발생하는 것 > TDZ > TDZ(Temporal Dead Zone)은 `let/const`로 변수 선언 시, `선언단계와 초기화 단계 사이의 일시적 사각지대`를 말한다. > 런타임에서 변수선언키워드를 만나 초기화단계가 이루어지기 전에 해당 변수에 접근하게 되면, TDZ에 의한 ReferenceError가 발생하게 된다. ### 요약 호이스팅은 런타임에서 선언부분이 최상단으로 끌어올려지는 현상으로, 예상치 못한 실행결과가 발생하는 문제가 있다. <br /> 따라서 var, 함수선언문이 아닌 let, const, 함수표현식으로 변수를 선언하여 호이스팅을 방지할 수 있다.
2.타입다루기
# 섹션2. 타입다루기 ## 10. 타입 검사 **1.typeof를 사용한 타입검사는 완벽하지 않다.** `typeof` : 우항의 피연산잔를 평가한 후 해당 타입을 문자열로 반환하는 연산자 `typeof가 완변하지 않은 이유` js의 타입은 primitive와 reference로 구분할 수 있다. reference 타입의 경우 타입 검출이 완벽하지 않은 경우가 많다. - ES6의 Class는 'function' 타입 검출 - new 생성자를 통해 생성된다면 'object'로 검출 - `null`의 경우 'object'로 검출되어 큰 혼란을 야기한다. (js에서 오류로 인정) reference 타입 검사가 어려운 이유 2) js는 동적으로 변하는 언어 -> `타입도 동적으로 변화한다.` **2. instanceof를 통한 프로토타입의 타입검사** outwater와 obj는 같은 객체형식과 값을 가지고 있지만. 타입검사의 결과가 다르다. 즉 instanceof를 통해 prototype을 타고 올라가 `동적으로 타입 검사`를 할 수 있다. ```js function Person(name, age) { this.name = name; this.age = age; } const outwater = new Person("outwater", 29); const obj = { name: "outwater", age: 29 }; outwater instanceof Person; // true obj instanceof Person; // false ``` **3. Object.prototype.toString.call() 을 통한 reference타입의 세부 타입검사** toString()의 경우 인자가 없을 때, 해당하는 타입을 `[object type]`의 형태로 반환한다. 따라서 reference type의 걍우 typeof, instanceof를 통해 분별할 수 없을 때 `call을 통해 인자를 넘겨 세부 타입을 검사`할 수 있다. ```js const arr = []; const func = function () {}; const date = new Date(); const cons = new String("문자열"); const nullV = null; const undefinedV = undefined; arr instanceof Object; // true func instanceof Object; // true date instanceof Object; // true cons instanceof Object; // true Object.prototype.toString.call(arr); // [object Array] Object.prototype.toString.call(func); // [object Function] Object.prototype.toString.call(date); // [object Date] Object.prototype.toString.call(cons); // [object String] Object.prototype.toString.call(nullV); // [object Null] Object.prototype.toString.call(undefinedV); // [object Undefined] ``` **타입검사시 Tip** - js에서 타입검사가 필요할 때 해당타입을 검사하는 방법을 확실하게 체크하고 가는 것이 좋다. - 구글링에서 다음과 같이 검색하여, 스택오버플로우 등에서 좋아요가 높은 답변, 답변의 작성일자 등을 참고하여 판단할 것 - javascript isFunction .. - javascript is String .. ### 요약 js언어는 `동적인 타입`을 가지는 언어이다. 따라서 `타입검사가 어렵다.` 여러가지 타입검사가 존재하기 때문에, `타입검사가 필요할 때 잘 찾아서 확인`하고 검사하자. (외울필요는 x) **Primitive와 Reference 타입의 차이를 생각하기** - typeof는 무적이 아니다. (reference에서) - instanceof (동적타입검사) - Object.prototype.toString.call() (세부타입검사) ## 11. undefined & null ### 1. undefined와 null의 차이 **undefined** undefined은 아무것도 지정하지 않았을 때의 기본 값 - 선언했지만, 값이 할당되지 않았을 때 - 산수계산시 NaN으로 취급되어 계산되지 않음 - typeof undefined === 'undefined' **null** null은 사용자의 의도가 들어가 없음을 나타냄 (nothing, empty, unknown) - 산수계산 시 0로 취급 - typeof null === 'object' ### 한 줄 요약 undefined는 선언했지만, 값이 할당되지 않았을 때의 기본 값이며, 산수계신시 NaN으로 취급되는 반면<br /> null은 사용자가 의도적으로 빈값을 표현한 것으로, nothing, empty, unknown에 해당할 경우 사용한다. 산수계산시 0으로 취급되어 주의가 필요한다. ## 12. eqeq 줄이기 eqeq는 동등연산자(==)를 의미한다. \*일치연산자(===) **2. eqeq를 줄여야하는 이유** js엔진에서 암묵전 형변환(type casting)을 진행하여 자동으로 데이터 타입을 변환하는 경우가 있다. - `'1' == 1, true ` - javscript type 테이블 **3. eqeq를 대체하는 방법** 명시적형변환 + 일치연산자를 통해 엄격한 비교연산을 진행한다. `Number('1') === 1, true` ## 13. 형변황 주의하기 **1. 암묵적 형변환의 사례** 11 + '문자열 결합' => '11 문자와 결합' !!'문자열' => true !!'' => false **2. 명시적형변환** paserInt('9,9999', 10) // 9 Boolean('문자열') // true Boolean('') // false Number('123') // 123 String(123) // '123' ## 14. isNaN (다시듣기) **1.** 사람은 10진수를 사용, 컴퓨터는 2진수를 사용하기 때문에 혼란이 발생 - 정수판별하는 방법 - Number.MAX_SAFE_INTEGER // 900719925473991 가장많이표현할수 있는 정수값 리턴 - Number.isInteger // Boolean - 숫자가 아닌 값을 판별 - isNaN() - Number.isNaN()으로 판별
3.경계다루기
# 섹션3. 경계다루기 ## 1. Min-Max 1. 가장좋은 방법은 상수로 MIN, MAX를 선언해두기 > `getRandomNumber(MIN_NUMBER, MAX_NUMBER);` - 좋은 이유: 함수가 명시적으로 표현 내부 구현 부분을 보지 않고도, 어떻게 함수가 동작할 지 알 수 있다. 2.경계의 모호함 주의 (이상,이하 VS 초과,미만) : MIN, MAX 값이 포함되는지 안되는지 여부 > 경계값 처리 방법 - 1. 개발 팀 내부 컨벤션에 경계값 처리 방식을 명시하는 방법 (ex. 우리 팀은 이상,이하를 사용한다.) - 2. 네이밍에서 표현하기 - `MAX_NUMBER_LIMIT`: 초과,미만 - `NUMBER_IN_MAX`: 이상, 이하 ### 한 줄 요약 최대와 최소값의 표현은 `상수`를 통해 명확히 하는 것이 좋다. 이 때, 경계값이 포함되는지 안되는지는 `팀 컨벤션`에서 정하거나, `상수 네이밍에서 LIMIT, IN `등을 통해 명시적으로 표현하는 것이 좋다. ## 2. Begin-End 시작은 고정적인데, 끝은 동적인 경우 `begin-end`라는 용어를 암묵적으로 사용한다. : ex) 체크인 날짜는 고정(begin), 체크아웃 날짜는 동적(end) ```js function reservationDate(beginDate, endDate) { // ... } reservationDate("YYYY-MM-DD", "YYYY-MM-DD"); ``` ## 3. first-last 1. first-last는 `포함된 양 끝을 의마할 때` && `~부터 ~~까지` 를 표현할 때 사용한다. 2. min-max 와의 차이 - min-max는 min부터 max까지 순차적으로 동일한 연속된 계산을 핧 때 주로 사용한다. - `[1,2,3,4,5]` min:1 max:5 - first-last는 시작과 끝은 고정되어 있지만, 사이의 값들이 연속적으로 사용되지 않은 경우 사용 - `[firstChild, ...elements, lastChild]` ### 요약 first-last는 시작과 끝값이 유의미한 의미를 가질 때 주로 사용하며, min-max는 사이의 연속된 값들을 모두 사용할 때 주로 사용한다. ## 4. prefix-suffix 1. prefix는 접두사, suffix는 접미사를 의미한다. 2. 접두사 사용사례 getter `get-`, setter `set-` Hook의 `use-` jQuery의 `$` underScore,lodash 등의 라이브러리 `_.` 컴포넌트의 `Base-, App-, V-` 3. 접미사 사용사례 리덕스의 요청처리 `action_REQUEST, action_SUCCESS, action_FAILRUE` 파일트리에서 복수처리 `-s` ### 요약 prefix와 suffix를 규칙을 두어 사용하는 것은 `코드를 읽는 일관성`을 주는 가장 좋은 방법 중 하나이다. ## 5. 매개변수의 순서가 경계다 매개 변수를 2개가 넘지 않도록 만드는 것을 지향한다. 1. 매개변수가 2개일 때 - 매개변수의 관계들을 쉽게 유추할 수 있기 때문에 함수 자체가 명시성을 가지게 된다. - 최소,최대값인가? 시작과 끝인가? , ... 2. 매개변수가 2개 이상일 때 - 객체로 받기 - 매개변수의 순서에 영향을 받지 않는다는 장점 - `function someFunc({someArg1, someArg2, someArg3, someArg4})` - rest parameter, arguments 로 받기 - 자주 사용하지 않는 매개변수를 rest parameter로 처리할 수 있다 - `function someFunc({someArg1, someArg2, ...Args})` - 랩핑하는 함수 - 이미 만들어 사용하고 있는 함수들이 있다고 할 때 (최악의 경우) - 함수명, arg1, arg3 으로 명확히 함수를 표현할 수 있을 때 `getFunc()` 만들어 사용 ```js function someFunc(someArg1, someArg2, someArg3, someArg4) { // ... } function getFunc(someArg1, someArg3) { someFunc(someArg1, undefined, someArg3); } ``` ### 요약 호출함는 함수의 네이밍과 매개변수의 순서를 통해 명시성을 확보할 수 있다. 이 때 명시성을 높이기 위해서 `매개변수는 2개를 넘지않는 것을 지향`하는데, 2개가 넘는 경우 `1)객체로 받기 2) arguments, rest paramter 3)랩핑하는 함수 만들기` 를 통해 매개변수의 수를 줄일 수 있다. ## 6. 요약 경계를 다룰 때에 특정 의미를 가지고 있는 변수명세트와 함수명을 사용한다면, 내부 구현을 보지 않고도 함수의 동작을 빠르게 알 수 있다. 대표적인 경계값으로는 'Min-Max', 'Begin-End' , 'First-Last', 'Prefix-Suffix' 가 존재한다. - 'Min-Max' min에서 max까지 연속적인 값이 사용될 때 주로 사용되며, min-max의 값이 포함되는지 여부에 따라 주의가 필요하다. 이 경우 팀 컨벤션에서 정의하거나, 초과&미만(LIMIT) 이상&이하(IN) 등의 추가 변수명을 주어 구분할 수 있다. - 'Begin-End' 시작은 고정적이나, 끝은 동적인 경우 'begin-end' 키워드를 암묵적으로 사용한다. ex. 체크인/체크아웃 날짜 - 'First-Last' 포함된 양 끝을 의미할 때 주로 사용한다. min-max와 달리, 사이의 값들이 연속적으로 사용되거나 유사 관계를 가지지 않을 때 주로 사용한다. ex. FirstChildNode, LastChildNode - 'Prefix-Suffix' 접두사와 접미사에 규칙을 두어 사용한다면 '코드를 읽을 때 일관성'을 줄 수 있다. 한편 매개변수는 개수 그 자체로 의미를 가질 수 있으며, 개수가 적을 때는 함수명과 매개변수명을 통하여 함수의 의미를 명시적으로 들어낼 수 있다. 따라서 매개변수는 2개가 넘지 않는 것을 지향한다. 매개변수가 2개 이상일 때는, 매개변수를 객체로 받거나, 자주사용하지 않는 매개변수를 rest parameter로 처리 할 수 있고, 이미 만들어 사용하는 함수들이 있는 최악의 경우에는 랩핑하는 함수를 새로 만들어서 사용하는 방법이 존재한다.
4. 분기다루기
# 분기다루기 ## 1. 값식문 > 문법이 중요한 이유 : 프로그래밍도 하나의 언어이기 때문에 : 컴퓨터를 이해 시켜주어야 하기 때문에, 이해시키지 못한다면 문법(syntax)에러 발생 -> 큰 서비스 오류로 이어짐 > Case 1 ```js // JSX 문법 ReactDOM.render(<div id="msg">Hello World!</div>, mountNode); // is transformed to this JS by Babel: ReactDOM.render( React.createElement("div", { id: "msg" }, "Hello World!"), mountNode ); ``` > case 2 : 객체의 값으로 문(if,while)은 들어갈 수 없다. : 삼항연산자와 같은 식은 값을 할당하기 때문에 객체의 값으로 사용가능하다. ```js //JSX <div id={if (condition) {'msg'}}>Hello World</div> //It is transeformed to this JS by Babel React.createElement("div", {id: if(condition){'msg'}} , "Hello World"); // 동작하지 않음 : 객체의 값으로 if조건문이 들어갔기 때문에 <div id ={condition ? 'msg' : null}> Hello World </div> // 정상 동작: 객체의 값으로 삼항연산자 식에 의해 값이 할당되기 때문 ``` > case 3 : (꼼수)조건문이 값을 바로 리턴하도록 한다면 사용가능하다. (by IIFE,즉시실행함수) > case 4 : 조건문을 통해 값을 만드는 것보다, 고차함수를 이용하여 값과 식으로만 코드를 개선할 수 있다. > case 5 ### 요약 함수의 인자들로 조건문,for문 등을 받을 수 있을까에 대한 고민에서 시작 React의 JSX는 Babel에 의해 transeformed 되는데, 이 때 JSX의 요소들은 함수의 인자들로 취급된다.<br /> 함수의 인자들은 `값과 식`으로만 이루어져야 하므로 if문,for문 등은 문법오류를 발생시킨다. 즉시실행함수로 감싼다면 조건문들도 사용가능할 수 있지만, 고차함수등을 이용하여 값과 식을 사용하는 것을 지향한다. ## 2. 삼항 연산자 다루기 > 삼항연산자의 일관성 : 삼항 연산자를 이용한 나만의 방법이 있는가? 숏코딩에 주로 사용하는가? : 삼항연산자를 사용함에 있어서 일관성을 가지는 것이 중요하다. > 삼항연산자 조건 : 삼항연산자는 3개의 `피연산자`를 가진다. : `조건` ? `참`[식] : `거짓` [식] : 참과 거짓 연산자는 `식`임에 주의 (if문, for문 불가) > 01.삼항연산자 BadCase : if-else 문을 사용하는 것이 훨씬 더 직관적으로 이해하기 좋다 : if-else 문 보다는 switch문을 이용하는 게 더 적합하다. : edge케이스를 else로 처리하는 것보다 default로 처리하는 것이 현업에서 더 자주 사용 ```js function example() { return condition1 ? value1 : condition2 ? value2 : condition3 ? value3 : value4; } function example() { if (condition1) { return value1; } else if (condition2) { return value2; } else if (condition3) { return value3; } else { return value4; } } function example() { const condition = condition1 || condition2 || condition3 || condition4; let value = ""; switch (condition) { case conditon1: value = value1; break; case conditon2: value = value2; break; case conditon3: value = value3; break; default: value = value4; } return value; } ``` > 02.삼항연산자 BAD Case : 삼항연산자가 중첩되어 있기 때문에, 직관적으로 파악하기 어렵다. : 따라서 사람중심적으로 생각하여 이해가 쉽도록 한다. : `괄호`를 통래 우선순위를 직관적으로 파악할 수 있도록 한다. ```js //BAD CASE const example = condition1 ? (a === 0 ? "zero" : "positive") : "negative"; //BETTER CASE const exampler = condition1 ? (a === 0 ? "zero" : "positive") : "negative"; ``` > 03.삼함연산자 GODD CASE > : Nullable 한 상황에서 예외처리를 위해 삼항연산자를 사용할 수 있다. ```js const welcomeMessage = (isLogin) => { const name = isLogin ? getName() : "이름없음"; return `안녕하세요 ${name}`; }; //if문을 이용한 BAD CASE const welcomeMessage = (isLogin) => { if (isLogin) { return `안녕하세요 ${getName()}`; } return `안녕하세요 이름없음`; }; ``` > 04.삼항연산자 BAD CASE : alert는 인자에 상관없이 'undefined'를 반환하기 때문에, 사실상 isAdult에 따라서 같은 값을 반환하고 있다. : 따라서 삼항연산자의 본질보다는 `숏코딩`에 가까운 CASE이다. ```js function alertMessage(isAdult) { isAdult ? alert("입장이 가능합니다.") : alert("입장이 불가능합니다."); } //BETTER CASE function alertMessage(isAdult) { if (isAdult) { alert("입장이 가능합니다."); } else { alert("입장이 불가능합니다."); } } ``` > 05.Poco의 삼항연산자 사용 CASE : 1. 삼항연산자를 사용해서 무언가의 값을 만들고 변수로 담아낼 때 `const name = isLogin ? getName() : "이름없음"; ` : 2. 함수가 내뱉는 값이 간단할 때 `return isAdult ? '입장이 가능합니다' : '입장이 불가합니다'; ` ### 요약 삼항연산자는 3개의 피연산자를 가지고, 피연산자는 `식`임에 주의해야한다. 삼항연산자는 주로 조건에 따라 다른 값을 뱉어낼 때 사용된다. 삼항연산자를 중첩해서 사용한다면 사용자가 직관적이해가 어려울 수 있으므로 if-else문, switch문을 고려하고, 단순 숏코딩을 위해서 사용하는 것이 아닌지 체크한다. 삼항연산자 사용은 자신만의 기준을 가지고 일관성 있게 사용하는 것이 바람직하다. ## 3. Truthy & Falsy js동적인 타입이기 때문에, 의도하지 않은 형변환이 자주 발생한다. > Truthy한 값 - true, {} , [] , 42, "0", "false", new Date(), -42, 3.14, 12n, Infinity, -Infinity > Falsy한 값 - false, null, undefined, 0 ,NaN, '', -0, 0n : 엄격한 조건보다는 null || undefined를 동시에 체크해야할 때 !Falsy를 통해 주로 사용한다. ## 4. 단축평가 (short-circuit evaluatuion) : 단축평가란 논리연산자, 삼항연산자를 통해 어디까지 값이 계산되는지를 파악하고, 최대한 계산량을 줄이는 것. : && 연산자는 하나라도 Falsy가 나오면 계산을 멈춤 : || 연산자는 하나라도 Truthy가 나오면 계산을 멈춤 ```js true && true && "도달 O"; true && false && "도달 X"; false || false || "도달 O"; true || false || "도달 X"; // false 리턴 true || true || "도달 X"; ``` > 단축평가를 통해 if문을 줄여서 사용할 수가 있다. : or연산자는 default값이 정해져있을 때 사용하기 편리하다. : `return 할당되는값 || default값` > 01.or연산자 CASE ```js // BAD Case - if문 function fetchDate() { if (state.data) { return state.data; } else { return "Fetching..."; } } // BAD Case - 삼항연산자 function fetchDate() { return state.data ? state.data : "Fething..."; } // GOOD Case - or단축평가 function fetchDate() { return state.data || "Fetching..."; } ``` > 02.or연산자 CASE ```js // BAD CASE function favoriteDog(someDog) { let favoriteDog; if (someDog) { favoriteDog = someDog; } else { favoriteDog = "냐옹"; } return favoriteDog + "입니다"; } // GOOD CASE function favoriteDog(someDog) { return (someDog || "냐옹") + "입니다"; } ``` > 3. ```js // CASE const getActiveUserName = (user, isLogin) => { if(isLogin){ if(user){ if(user.name){ return user.name } else { return '이름없음' } } } } // GOOD CASE // &&연산자로 한 depth 처리 const getActiveUserName = (user, isLogin) => { if(isLogin && user){ if(user.name){ return user.name } else { return '이름없음' } } } // || 연산자로 default값 처리 const getActiveUserName = (user, isLogin) => { if(isLogin && user){ return user.name || '이름없음' } } } ``` ## 5. else if 피하기 > 01.BAD CASE - else if 가독성 저하 사례 : (내생각) else if문은 실행되지 않을 것이다. : else if 문은 promise의 체이닝(`.then().then() ... `) 구조와 다르다. 하지만 코드흐름상 파이프 라인 처럼 실행될 것 처럼 보이기 때문에, else if문의 사용은 지양되며, 여러개의 else if문이 필요할 경우 switch문을 사용한다. : 혹은 조건은 더 명확히 하여 여러개의 단일 if문으로 작성할 수 있다. ```js const x = 1; if (x >= 0) { ("x는 0과 같거나 크다"); } else if (x > 0) { ("x는 0보다 크다"); } else { ("Else"); } ``` ## 6. else 피하기 > 01.if-else BADCASE - 단축평가 대체 가능 ```js // BAD function getActiveUserName(user) { if (user.name) { return user.name; } else { return "이름 없음"; } } // BETTER function getActiveUserName(user) { if (user.name) { return user.name; } return "이름 없음"; } } // GOOD function getActiveUserName(user) { return user.name || "이름 없음"; } ``` > 02.if-else BAD CASE - 하나의 함수가 2개의 역할을 할 때 : 함수의 이름만 본다면, 인사를 리턴해야하는 함수이다. 그런데 특정 조건이 추가되면서 2가지 역할을 하는 함수가 되고, else 문으로 인해 함수에 이름에 맞는 동작이 아예 실행되지 않는 케이스가 발생하게 된다. : 포인트는 습관적으로 `if문과 함께 else를 사용하는 것에 주의`해야한다는 것 ```js /* age가 20 미만 시 특수 함수 실행*/ //BAD - age < 20 일 때, 인사하는 동작이 아예 실행이 안됨. function getHelloCustomer(user) { if (user.age < 20) { report(user); } else { return "안녕하세요"; } } // BETTER - 특수조건에 상관없이 인사하는 동작이 실행되도록 함. function getHelloCustomer(user) { if (user.age < 20) { report(user); } return "안녕하세요"; } ``` ## 7. early Return > 01.if-else문 BADCASE - 중첩되었을 경우 : 로그인 과정<br /> : 1) 로그인 여부 확인 -> 2) 토큰 존재 여부 확인 -> 3)기가입유저 확인 -> 4) 가입 or 로그인 : early Return을 사용할 경우, `사람이 생각하는 대로 코드가 동작`하게 된다. : 로그인의 예외사항들이 차례대로 리턴되고, 정상동작 코드만이 최종적으로 남게된다. ```js // BADCASE function loginService(isLogin, user) { if (!isLogin) { if (checkToken()) { if (!user.nickName) { return registerUser(user); } else { refreshToken(); return "로그인 성공"; } } else { throw new Error("No Token"); } } } //GOOD CASE - by Early Return function loginService(isLogin, user) { // 예외1. 로그인 된 경우 early Return if (isLogin) { return; } // 예외2. 토큰 없는 경우 에러 if (!checkToken()) { throw new Error("No Token"); } ㅊ; if (!user.nickName) { return registerUser(user); } // 정상동작. 로그인 성공 refreshToken(); return "로그인 성공"; } ``` > 02.하나의 로직이 많은 의존성이 붙어 있는 경우, ```js // BAD CASE function 오늘하루(condition, weather, isJob) { if (condition === "Good") { 공부(); 게임(); 유튜브(); } if (weather === "Good") { 운동(); 빨래(); } if (isJob === "Good") { 야간업무(); 조기취침(); } } // GOOD CASE function 오늘하루(condition, weather, isJob) { if (condition !== "Good") { return; } 공부(); 게임(); 유튜브(); if (weather !== "Good") { return; } 운동(); 빨래(); if (isJob !== "Good") { return; } 야간업무(); 조기취침(); } ``` ### 요약 Early Eeturn을 사용하는 경우, 로직의 흐름을 더 명확하고 간결해내게 표현할 수 있다. ## 8. 부정 조건문 지양하기 지양하는 이유1 : 부정조건의 경우 직관적으로 한 번에 이해하기가 어렵다. : 따라서 사람이 생각하게 할 것을 최소화한다. ex. if(isNaN(3)) 지양하는 이유2 : if-else문은 부정문에 해당하는 것은 후순위이며, else-if문 순으로 사용하는 경우가 없다. : 프로그래밍 언어 자체가 if문이 처음오고, true부터 실행시킨다. > BAD CASE - NAN의 경우 ```js const isCondition = true; const isNotCondition = false; // NaN : Not a Number // BAD : 조건 직관적 이해 어려움 if (!isNaN(3)) { // 숫자일 때만 동작하도록 console.log("숫자입니다."); } // GooD : 숫자임을 확인하는 util함수 만들어 해결 function isNumber(num) { return !Number.isNaN(num) && typeof num === "number"; } if (isNumber(3)) { console.log("숫자입니다."); } // Bad: 부정조건을 붙이는 것 보다. 긍정조건에 부정키워드(!)를 곁들여 사용하는 것이 더 바람직 if (isNotCondition) { } // Good: 조건이 거짓일 때만 실행 if (!isCondition) { } ``` ### 부정조건문을 사용하는 경우 1. Early Return 2. Form Validation 3. 보안, 검사로직 의 경우 예외적으로 부정조건문을 사용하기도 한다. ## 9. Default Case 고려하기 : Edge Case라고도 하며, 반복되고 당연한 연산의 경우 Default 값을 지정하여 연산을 간단하게 만들 수 있다. : 사용자의 입력을 받는 경우, 항상 특이 케이스를 유의해야한다. : 라이브러리의 경우, 수 많은 인자에 대하여 default값을 제공하므로 주의해야한다. (당연하게 사용한다면 모를 수 있다. parseInt의 두번째 인자의 default값이 10이다.) > 01 Case ```js // 값이 입력되지 않는다면 1을 default 값으로 한다. function sum(x, y) { x = x || 1; y = y || 1; return x + y; } ``` ## 10. 명시적인 연산자 사용 지향하기 : 연산자의 순서를 외우는 것도 좋지만, 명시적으로 `()`를 통해 순서를 지정하는 것이 더욱 바람직하다. : 증감연산자의 사용을 지양한다. - 예측가능한 코드를 작성하도록 한다. - 디버깅이 가능하도록 > 01 CASE - 괄호를 통한 연산자 숫자 명시 ```js // BAD if(isLogin && token || user){...} // GOOD if( (isLogin && token) || user) ``` > 02 CASE - 예측가능한 코드 ```js // BAD - 루프문 사이에 있거나, 디버깅의 어려움이 존재 function increment(){ number ++; } // GOOD function increment(){ number = number + 1 } ``` ## 11. Nullish coalscing operator (다시 듣기) > 널병합 연산자 `??` : 좌항이 `null` 혹은 `undefined` 인 경우에만 우항이 실행된다. > 사례로 살펴보기 > Early Return시 유의 > 부작용 널병합 연산자와 || 연산자를 통한 단축평가를 진핼할 때 > 논리연산자와 병합연산자가 동시에 사용될 수 없는 제약이 존재 ## 12 드모르간의 법칙 : 드모르간의 법칙으로 논리연산자 1step 벗겨내기 > 새로운 기획으로 로그인 실패로직이 필요해진다면? > !(A && B) => (!A || !B) ```js const isValidUser = true; // 서버에서 받아왔다고 가정 const isValidToken = true; // "" if(isValideToken && isValidUser){ console.log('로그인 성공!'); } // BAD CASE - 따라가기 어려움 if(!(isValidToken && isValidUser)){ console.log('로그인 실패!!') } // GOOD CASE // Token이 valid 하지 않으면 실패, User가 Valid하지 않으면 실패로 사람의 생각흐름대로 코드가 짜여지게 됨 if(!isValidToken || !isValidUser){ console.log('로그인 실패!!') } ```
5.배열다루기
5.1.JS의 배열은 객체다
const arr = [1,2,3]; arr[3] = 'test' arr['property'] = 'string value'; arr['obg'] = {}; arr[{}] = [1,2,3]; arr['func'] = function (){ return 'hello'} console.log(arr) [ 1, 2, 3, 'test', property: 'string value', obj: {}, '[object Object]': [ 1, 2, 3 ], func: [Function (anonymous)] ]
- 배열은
key-value로 이루어진 하나의 객체
이기 때문에,객체의 key
로 index뿐만 아니라‘string’, object, function 등의 다른 타입도 사용
될 수 있다.
- arr
[1, 2, 3]
는 obj{0:1,1:2,2:3} 의 형태와 유사하다.
배열의타입 확인 시 유의사항
typeof [] = ‘object’
- arr.length 가 아닌
Array.isArray(arr)
를 통해 배열 여부 판단하기
5.2 Array.length
arr.length > 10
과 같은 조건문은 많이 사용된다. 하지만 arr가 비어있을 경우test-case01
- arr.length는 사이의
빈값이 존재가능
하다. 실질적으로arr.length
는마지막 요소의 idx +1
이라고 할 수 있다.
const arr = [1,2,3]; console.log(arr.length); // 3 arr.length = 10; console.log(arr.length); // 10 console.log(arr) // [1,2,3, , , , , , , ,]
- arr.length로 배열 초기화 하기
const arr = [1,2,3] arr.length === 0; console.log(arr) // []
5.3 배열 요소에 접근하기
배열요소는 index를 통해 접근하기 때문에, 명확하게 의미를 파악하기 어렵다.
- Case01. 문자열의
String.split()
의 반환 결과는배열
에 담긴다.
구조분해할당을 통한
배열요소 변수명으로 관리
하기//badCase function operateTime(inputs, operators,is){ inputs[0].split('').forEach(()=>{ cy.get('.digit').contains(num).click(); }) inputs[1].split('').forEach(()=>{ cy.get('.digit').contains(num).click(); }) } //GoodCase function operateTime(inputs, operators,is){ const [firstInput, secondInput] = inputs; // const [state,setState] = React.useState('') 와 유사 firstInput.split('').forEach(()=>{ cy.get('.digit').contains(num).click(); }) secondInput.split('').forEach(()=>{ cy.get('.digit').contains(num).click(); }) }
- Case02. 구조분해할당을 통한 배열요소 접근 (buttons사례)
//BadCase function clickGroupBtn(){ const confirmBtn = document.getElementsByTagName('button')[0] const cancelBtn = document.getElementsByTagName('button')[0] const resetBtn = document.getElementsByTagName('button')[0] // ... some code } //GoodCase function clickGroupBtn(){ const [confirmBtn, cancelBtn, resetBtn] = document.getElementsByTagName('button') // ... some code }
- Case03. 날짜 포맷팅에서의 사례
하나의 배열 요소가 있을 때
도 구조분해할당으로 사용가능하다는 것// BadCase function formatDate(targetDate){ const date = targetDate.toISOString().split('T')[0]; const [year, month, day] = date.split('-') return `${year}년 ${month}월 ${day}일` } //GoodCase function formatDate(targetDate){ const [date] = targetDate.toISOString().split('T'); const [year, month, day] = date.split('-') return `${year}년 ${month}월 ${day}일` }
_.head(array)
를 통해 첫번째 요소 접근가능이를 유틸함수로 만들어 사용하는 방법
function head(arr){ return arr[0] ?? '' } // ??는 널병합 연산자로, 좌항이 null 혹은 undefined일 경우에만 우항이 실행된다.
5.4 유사 배열 객체 (다시듣기)
- js에서 배열은 객체이다.
Array.from()
은 객체들은 array타입으로 변경할 수 있다.
array.from( ...isArary() ) 혹은
arguments
는 대표적인 유사배열객체- 함수 내부에서 인자를 arguments로 접근가능한 것
- arguments는 배열이 아니기 때문에
배열메소드(map, filter, ...)등은 사용이 불가능
하다. - Array.from() 으로 바꿔주어 배열메소드 사용 가능
5.5 불변성
불변성이란 원본 배열을 참조하여 값을 변경할 때,원본 배열의 변화가 발생하지 않는 것
: 참조 주소값이 같아지기 때문이다.
불변성을 지키기 위한 방법
- 배열을 복사한다 (구글링 추천)
- 새로운 배열을 반환하는 메서들을 활용한다.
- mdn 문서를 통해 불변성을 가지는지 확인해보기
- map, filter, reduce, slice
5.6 for문 고차 함수로 리팩터링
임시변수를 줄이기 위한 방법
- 명령형 프로그래밍(for문)
const price = ['2000', '1000', '3000', '5000', '4000']; function getWonPrice(preiceList){ let temp = []; for(let i = 0; i < priceList.length; i++){ temp.push(preiceList[i] + '원'); } return temp; }
- case01. postfix ‘원’ 을 붙이는 동작
const price = ['2000', '1000', '3000', '5000', '4000']; const suffixWon = (price) => price + '원'; function getWonPrice(preiceList){ return priceList.map(suffixWon) };
- case02. 1000원 이상인 경우만 출력하는 조건 추가
const price = ['2000', '1000', '3000', '5000', '4000']; const suffixWon = (price) => price + '원'; const isOverOneThousand = (price) => Number(price) > 1000 function getWonPrice(priceList){ const isOverList = priceList.filter(isOverOneThousand) return isOverList.map(suffixWon) };
만약 이런 식으로 계속해서 추가 조건들이 생성된다면, 배열 선언 및 깔끔하지 못한 코드가 될 수 있다!! 이럴 땐 어떻게 해야할까? → 배열 메서드 체이닝
- case03. 가격순 정렬 추가
const price = ['2000', '1000', '3000', '5000', '4000']; const suffixWon = (price) => price + '원'; const isOverOneThousand = (price) => Number(price) > 1000 const sortByDecrease = ((a, b) => b - a) function getWonPrice(priceList){ const isOverList = priceList.filter(isOverOneThousand) const sortList = isOverList.sort(sortByDecrease) const sortList = isOverList.sort(sortByDecrease) const sortList = isOverList.sort(sortByDecrease) ... ... return sortList.map(suffixWon) };
5.7 배열메시드 체이닝 활용하기
- 순서에 따라 동작들이 계속 이어질 때, 메서드체이닝으로 코드 깔끔하게 작성하는 방법
파이프 라인
처럼 코드의 흐름을 빠르게 파악하기 쉽다.
const price = ['2000', '1000', '3000', '5000', '4000']; const suffixWon = (price) => price + '원'; const isOverOneThousand = (price) => Number(price) > 1000 const sortByDecrease = ((a, b) => b - a) function getWonPrice(priceList){ return sortList .filter(isOverOneThousand) .sort(sortByDecrease) .map(suffixWon) };
5.8 map VS forEach
map과 forEach의 차이점
- return 값의 유무
map
⇒ 원본 배열과 길이가 같은 배열을 리턴forEach
⇒ return 값이 없음(undefined)5.9 continue 와 break
for문에서의 continue와 break는 고차함수에서는 사용불가하다.
- 따라서 객체에서는
for ... in
, 배열에서는for ... of
를 통해 continue, break 사용한다.
조기 반복 종료 가능한 배열메서드 따로 존재
Array.every()
→ 모두가 truthy일 때(&&연산자)
Array.some()
→ 하나라도 truthy일 때 (|| 연산자)
Array.find()
→ 만족하는 첫번째 값 리턴
Array.findIndex()
→ 만족하는 첫번째 index 리턴