프론트엔드로서 공부를 하면서 라이브러리, 프레임워크도 중요하지만 자바스크립트도 아직 제대로 모르면서 그것들을 공부하기엔 뭔가 순서가 잘못되지 않았나란 생각이 들었다. 그래서 새롭게 자바스크립트 공부를 하면서 내가 앞으로도 계속 참고를 할 수 있는 JS 정리글을 하나 만들고 싶어 이 글을 작성하고자 한다.
이 글의 주요 요지는 JS를 사용할 때 효율성, 가독성을 위해 지켜야할 style들을 정리하는 것이다. 좋은 코드라고 제목을 거창하게 지었는데 사실 좋은 코드란 무엇이다라고 특정할 수는 없다. 개개인 마다 당연히 생각의 차이는 있을 것이고, 내가 작성한 방식들에 누군가는 동의를 하지 않을 수도 있다. 하지만 코드를 작성했을 때 유지보수, 가독성, 효율성에 좋지않은 스타일은 분명히 있기에 그런 것들에 초점을 맞춰서 정리를 하고자 한다.
자바스크립트는 실행환경에 따라 전역공간도 바뀐다. 브라우저에서는 window가 되고 NodeJS환경에서는 global이다. 그렇기에 전역공간을 잘못 활용하면 NodeJS환경에서 잘 돌아가던 코드가 브라우저에서는 에러를 발생시킬 수 있다.
함수를 사용하면서 보통 함수 하나에 한가지 기능만 부여 하는데 임시변수를 많이 사용하면 가독성이 떨어지고 추상화 작업이 힘들어질 수 있다.
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'))
그래서 위 처럼 바로 반환을 해주거나 고차함수를 활용해서 임시변수 사용을 최대한 줄이면 코드가 더 깔끔해질 것이다.
호이스팅은 런타임 시기에 선언과 할당이 분리된 것을 말한다.
호이스팅은 변수 뿐만 아니라 함수에도 적용이 되기 때문에 이점을 주의해야한다.
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
}
자바스크립트에서 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
undefined는 아직 무언가가 정의되지 않은 것.
null은 정의는 되었지만 0의 수치를 의미한다.
숫자로 된 string타입이 int타입과 연산을 하면 자동으로 int타입으로 바뀌고 하는데 이런 변환이 자동으로 발생하기 전에 직접 명시적으로 변형해주자. 눈에 보이는 부분이 아니기 때문에 실수를 유발할 수 있다.
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 ("")
function fetchData(){
return state.data || 'Fetching...'
}
state.data 값이 있으면 그대로 반환. 없으면 'Fetching...'반환
function getAdminName(user){
if(user.type){
return user.name
}
return 'not admin'
}
if-else를 완성시키는 것보다 훨씬 깔끔하다
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;
}
매개변수로 아무 값이 들어오지 않아도 기본값을 정해주면 예외를 방지할 수 있다.
두 번을 넘는 연산이 이어질 때 사칙연산을 알면서 쓰더라도 괄호로 구분을 해줘서 예측이 쉬운 코드를 작성하자.
let a;
console.log(a ?? 5) // 5
let b = 0;
console.log(b ?? 10) // 0
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
매개변수에서 바로 구조분해 할당을 해주면 깔끔
JS에는 함수에 매개변수를 선언하지 않고 인자를 주입해도 받을 수 있는 객체가 있다.
function num(){
return arguments;
}
console.log(num(1,2,3,4)) // { [Iterator] 0: 1, 1: 2, 2: 3, 3: 4 }
arguments를 쓰면 넣는 그대로 받아진다.
const arr1 = [1, 2, 3]
const arr2 = [...arr1]
arr2.push(4)
console.log(arr1) // [1, 2, 3]
console.log(arr2) // [1, 2, 3, 4]
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]
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원' ]
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;
}
}
변수, 함수 모두 축약 가능
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' }
말 그대로 동결이다. 안에 정해진 값을 바꾸는 것도 안되고 새로운 프로퍼티를 추가하는 것도 안된다.
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를 시켜줘야한다.
함수도 매개변수 기본 값 설정이 가능하다.
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가 필요합니다.
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
const user = {
name: 'foo',
getName: ()=>{
return this.name
}
}
console.log(user.getName()) // undefined
화살표 함수는 렉시컬 스코프를 가지는데 여기는 this라는 것이 존재하지 않는다. this가 없으니 그 상위의 환경을 참조하게되는데 상위는 객체이고 여기서 this는 전역을 나타내므로 undefined가 나오는 것이다.
# 미해결
전역에서 name설정을 해줬는데 똑같이 undefined가 나옴.... 전역 으로 설정하면 나와야하는거 아닌가... 찾아봐야한다.
JS는 this, 실행 컨텍스트, 스코프처럼 동적으로 변하는게 많다. 그래서 최대한 코드를 단순하고 알아보기 쉽게 짜는게 좋다.
순수함수는 side effect를 유발하지 않는 함수를 말한다.
함수를 반환하는 함수
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