좋은 코드를 작성하기 위한 JS code style 정리

HANITZ·2023년 4월 15일
0

프론트엔드로서 공부를 하면서 라이브러리, 프레임워크도 중요하지만 자바스크립트도 아직 제대로 모르면서 그것들을 공부하기엔 뭔가 순서가 잘못되지 않았나란 생각이 들었다. 그래서 새롭게 자바스크립트 공부를 하면서 내가 앞으로도 계속 참고를 할 수 있는 JS 정리글을 하나 만들고 싶어 이 글을 작성하고자 한다.

이 글의 주요 요지는 JS를 사용할 때 효율성, 가독성을 위해 지켜야할 style들을 정리하는 것이다. 좋은 코드라고 제목을 거창하게 지었는데 사실 좋은 코드란 무엇이다라고 특정할 수는 없다. 개개인 마다 당연히 생각의 차이는 있을 것이고, 내가 작성한 방식들에 누군가는 동의를 하지 않을 수도 있다. 하지만 코드를 작성했을 때 유지보수, 가독성, 효율성에 좋지않은 스타일은 분명히 있기에 그런 것들에 초점을 맞춰서 정리를 하고자 한다.

변수

1. var는 되도록이면 사용하지말자

  • var는 중복선언이 가능하기 때문에 같은 이름의 변수가 다시 만들어질 위험이 있다.
  • let, const 는 블록단위 스코프이기 때문에 중괄호로 구분이 된 영역이면 지역 변수를 따로 선언해도 문제가 없다. 하지만 var는 함수 단위 스코프이기 때문에 중괄호 내부에서 변경한 값이 밖의 스코프까지 오염시켜버린다.
  • TDZ(Temporal Dead Zone)가 따로 존재하지 않아 코드에서 에러가 발생했을 때 발생 지점을 찾기 어려워질 수 있다.(할당 이전에 변수를 사용한 곳이 있으면 전부 undefined로 들어가기 때문)

2. 전역 공간 사용 최소화

자바스크립트는 실행환경에 따라 전역공간도 바뀐다. 브라우저에서는 window가 되고 NodeJS환경에서는 global이다. 그렇기에 전역공간을 잘못 활용하면 NodeJS환경에서 잘 돌아가던 코드가 브라우저에서는 에러를 발생시킬 수 있다.

3. 특정 스코프 안에서의 임시변수 최소화

함수를 사용하면서 보통 함수 하나에 한가지 기능만 부여 하는데 임시변수를 많이 사용하면 가독성이 떨어지고 추상화 작업이 힘들어질 수 있다.

const score = {
    'John': 10,
    'Mark': 20,
    'Tom': 50,
}

function getScore(man){
  	// 임시변수 
    const guy = score[man]
    return guy + '입니다.'
}

console.log(getScore('John'))

간단한 예시라 복잡하지 않아 보일 수 있지만 이렇게 guy같은 임시변수를 쓰면서 +'입니다.' 같은 다른 보조적인 기능이 추가될 수 있다.

const score = {
    'John': 10,
    'Mark': 20,
    'Tom': 50,
}

function getScore(man){
    return printMan(man)
}

function printMan(man){
    return man+'입니다.'
}

console.log(getScore('John'))

그래서 위 처럼 바로 반환을 해주거나 고차함수를 활용해서 임시변수 사용을 최대한 줄이면 코드가 더 깔끔해질 것이다.

4. 함수에도 const를 쓰자

호이스팅은 런타임 시기에 선언과 할당이 분리된 것을 말한다.
호이스팅은 변수 뿐만 아니라 함수에도 적용이 되기 때문에 이점을 주의해야한다.

console.log(num()) // 2

function num(){
    return 1
}
function num(){
    return 2
}

위 처럼 함수명이 일치하는 함수를 만들게 되면 문제가 발생하기 때문에 함수에도 변수처럼 const를 사용해주는 것이 좋다.

const num = function(){
    return 1
}
const num = function(){ // cons is not defined
    return 2
}

타입

1. 원시값이 아닌 값은 typeof()를 쓰면 안된다

자바스크립트에서 Reference 값들은 전부 Object의 한 종류로 포함된다. Object안에 Array, Function, Date같은 형식들이 포함되어 있는 것이다. 심지어 null도 typeof()를 해주면 Object를 반환한다. 그래서 배열의 경우엔 isArray()같은 메서드를 사용해줘야한다.

const a = {
    b: 1,
    c: 2,
}
const b = [1, 2, 3]
console.log(Array.isArray(a)) // false
console.log(Array.isArray(b)) // true

2. null, undefined 구분

undefined는 아직 무언가가 정의되지 않은 것.
null은 정의는 되었지만 0의 수치를 의미한다.

3. 자바스크립트에서 암묵적인 형변환을 해주기 전에 명시적으로 변환작업을 해주자

숫자로 된 string타입이 int타입과 연산을 하면 자동으로 int타입으로 바뀌고 하는데 이런 변환이 자동으로 발생하기 전에 직접 명시적으로 변형해주자. 눈에 보이는 부분이 아니기 때문에 실수를 유발할 수 있다.

4. Truthy, Falsy

Truthy가 되는 값들
if (true)
if ({})
if ([])
if (32)
if ("0")
if ("false")
if (new Date())
if (-85)
if (12n)
if (3.14)
if (-3.14)
if (Infinity)
if (-Infinity)
Falsy가 되는 값들
if (false)
if (null)
if (undefined)
if (0)
if (-0)
if (0n)
if (Nan)
if ("")

분기

1. 단축평가

function fetchData(){
    return state.data || 'Fetching...'
}

state.data 값이 있으면 그대로 반환. 없으면 'Fetching...'반환

2. else를 쓰지말자

function getAdminName(user){
    if(user.type){
        return user.name
    }
    return 'not admin'
}

if-else를 완성시키는 것보다 훨씬 깔끔하다

3. Default case를 고려해주자

function createElement(type, height, width){
    const el = document.createElement(type || 'div') // default: div
    el.style.height = height || 100 // default: 100
    el.style.width = width || 100 // default: 100
    return el;
}

매개변수로 아무 값이 들어오지 않아도 기본값을 정해주면 예외를 방지할 수 있다.

4. 연산을 할 때는 괄호를 사용해서 헷갈림을 방지하자

두 번을 넘는 연산이 이어질 때 사칙연산을 알면서 쓰더라도 괄호로 구분을 해줘서 예측이 쉬운 코드를 작성하자.

5. 0같은 falsy값이 필요할 때는 null 병합 연산자를 사용하자

let a;
console.log(a ?? 5) // 5
let b = 0;
console.log(b ?? 10) // 0

배열

1. 배열 요소 매개변수를 구조분해할당

const arr = ['Kim', 'Fooo']

function pickFirstName(inputs){
    return inputs[1]
}
console.log(pickFirstName(arr)) // Fooo

배열을 매개변수로 받으면 인덱스로 접근해서 가독성이 떨어질 수 있다.

const arr = ['Kim', 'Fooo']

function pickFirstName([last, first]){
    return first
}
console.log(pickFirstName(arr)) // Fooo

매개변수에서 바로 구조분해 할당을 해주면 깔끔

2. 유사 배열 객체(arguments)

JS에는 함수에 매개변수를 선언하지 않고 인자를 주입해도 받을 수 있는 객체가 있다.

function num(){
    return arguments;
}

console.log(num(1,2,3,4)) // { [Iterator] 0: 1, 1: 2, 2: 3, 3: 4 }

arguments를 쓰면 넣는 그대로 받아진다.

3. 배열 깊은 복사

  1. spread 이용한 복사
const arr1 = [1, 2, 3]
const arr2 = [...arr1]
arr2.push(4)
console.log(arr1) // [1, 2, 3]
console.log(arr2) // [1, 2, 3, 4]
  1. filter, map, slice 함수
const arr1 = [1, 2, 3]
const arr2 = arr1.map((v)=>v)
arr2.push(10)
console.log(arr1) // [1, 2, 3]
console.log(arr2) // [1, 2, 3, 10]

4. forEach vs map

forEach는 반환값이 없고, map은 반환값이 있다.
forEach는 단순히 순회하는 것이고, map은 새로운 배열을 반환한다.

const prices = ['1000', '2000', '3000']

console.log(prices.forEach((price) => price+'원')) // undefined
console.log(prices.map((price) => price+'원')) // [ '1000원', '2000원', '3000원' ]

객체

1. 축약

const person = {
    firstName: 'foo',
    lastName: 'Kim',
    getFullName: function(){
        return this.lastName + ' ' + this.firstName;
    }
}
const firstName = 'foo'
const lastName = 'Kim'

const person = {
    firstName,
    lastName,
    getFullName(){
        return this.lastName + ' ' + this.firstName;
    }
}

변수, 함수 모두 축약 가능

2. 객체의 키 동적할당

const [state, setState] = useState({
    id: '',
    password: '',
})

const handleChange = (e) => {
    setState({
        [e.target.name] : e.target.value // key 동적 할당
    })
}

return (
    <React.Fragment>
        <input value={state.id} onChange={handleChange} name="name" />
        <input value={state.password} onChange={handleChange} name="password" />
    </React.Fragment>
)

es6부터 Computed Property Name으로 key값에 []를 사용해서 식, 값을 넣을 수 있다.

let abc
b = {
    [abc ?? '몰루'] : 'dd'
}

console.log(b) // { '몰루': 'dd' }

3. Object.freeze

말 그대로 동결이다. 안에 정해진 값을 바꾸는 것도 안되고 새로운 프로퍼티를 추가하는 것도 안된다.

const mango = Object.freeze({
    weight: 5,
    price: 3,
})
mango.logo = 'mmaa'
mango.price = 5
console.log(mango) // { weight: 5, price: 3 }
console.log(Object.isFrozen(mango)) 
//  true  Object.isFrozen()으로 freeze확인 가능

하지만 freeze로 깊은 복사는 못한다.

const mango = Object.freeze({
    weight: 5,
    price: 3,
    friend: {
        green: 'greenmango',
        red: 'redmango'
    }
})
mango.friend.green = 'yellowmango'
console.log(mango.friend) 
// { green: 'yellowmango', red: 'redmango' }

freeze가 하고 싶으면 내부 객체에도 똑같이 freeze를 시켜줘야한다.

함수

1. Default value

함수도 매개변수 기본 값 설정이 가능하다.

function createEl({margin = 0, center = false, navElement = 'div'}){
    return {
        margin,
        center,
        navElement,
    }
}
console.log(createEl({margin: 3, navElement: 'img'})) 
// { margin: 3, center: false, navElement: 'img' }

기본 값에 함수를 넣을 수도 있다.

function createEl({margin = required('margin'), center = false, navElement = 'div'}){
    return {
        margin,
        center,
        navElement,
    }
}
const required = (el) =>{
    throw new Error(el + '이 필요합니다.')
}
console.log(createEl({ navElement: 'img'}))
// Error: margin가 필요합니다.

2. Rest Parameters

arguments처럼 넘겨주는 모든 인자를 다 받아준다.
spread operator 랑은 다른 개념이다.

function sumTotal (...args){
    return args.reduce((acc, curr)=> acc+curr,)
}
console.log(sumTotal(1,2,3,4)) // 10

원하는 만큼 변수 할당을 해주고 나머지를 받을 수도 있다.

function sumTotal (one, two, ...args){
    return args.reduce((acc, curr)=> acc+curr,one+two)
}
console.log(sumTotal(1,2,3,4,5)) // 15

3. 화살표 함수를 사용하면 안되는 곳

  • 객체 안의 함수
const user = {
    name: 'foo',
    getName: ()=>{
        return this.name
    }
}
console.log(user.getName()) // undefined

화살표 함수는 렉시컬 스코프를 가지는데 여기는 this라는 것이 존재하지 않는다. this가 없으니 그 상위의 환경을 참조하게되는데 상위는 객체이고 여기서 this는 전역을 나타내므로 undefined가 나오는 것이다.

# 미해결
전역에서 name설정을 해줬는데 똑같이 undefined가 나옴.... 전역		으로 설정하면 나와야하는거 아닌가... 찾아봐야한다.
  • 화살표 함수에서는 arguments를 사용하지 못한다
    일반 함수와 화살표 함수는 별개의 존재로 봐야하기 때문에 일반함수의 모든 프로퍼티가 화살표에도 존재한다고 생각하면 안된다. call, apply, bind도 사용 못함
  • Rest Parameter는 사용 가능
  • 생성자 함수로 사용하면 안된다

4. 순수 함수

JS는 this, 실행 컨텍스트, 스코프처럼 동적으로 변하는게 많다. 그래서 최대한 코드를 단순하고 알아보기 쉽게 짜는게 좋다.
순수함수는 side effect를 유발하지 않는 함수를 말한다.

5. 클로저

함수를 반환하는 함수

function add(num1) {
    return function (num2){
        return function (calculateFn) {
            return calculateFn(num1, num2)
        }
    }
}

function sum(num1, num2){
    return num1+num2;
}
const addOne = add(1)
const addTwo = addOne(2)
const sumAdd = addTwo(sum)
console.log(sumAdd) // 3

0개의 댓글

관련 채용 정보