원서 요약 : 자바스크립트 코딩의 기술
목표
- 간결함, 가독성, 예측 가능성 높이기
변수 할당 키워드
// let 보다는 const를 사용해라!
// var는 사용하지 말 것!
const array = [];
let value = 1;
/*
const 변수는 재할당 할 수 없으므로 처음 할당된 자료형이 유지됩니다.
그러므로 100줄 밑에서도 자료형은 동일합니다.
let 변수는 재할당 가능하므로 100줄 밑에서 자료형이 동일하지 않을 수 있습니다.
곧 매번 자료형을 확인해야 합니다.
*/
문자열 + 변수 +문자열 No! 템플릿 리터럴 Yes (^_^)
const searchKey = "js";
const url1 = "https://www.easysearch.com/search/" + searchKey;
const url2 = `https://www.easysearch.com/search/${searchKey}`;
펼침 연산자를 사용합시다.
//하샤드 수 구하기
//각 자리의 수를 더 한 값으로 원 값을 나누어 떨어지면 하샤드 수
//ex) 18 -> 1+8 = 9 -> 18 % 9 == 0
function isHarshad(x) {
return x % Number([...String(x)].reduce((a, c) => Number(a) + Number(c)))
? false
: true;
}
console.log(isHarshad(10)); // true : 10 % 1 -> 0
console.log(isHarshad(11)); // false : 11 % 2 -> 1
console.log(isHarshad(18)); // true : 18 % 9 -> 0
펼침 연산자는 얕은 복사라는 것을 주의하세요.
문자열, 배열, 객체 전부 펼침 연산자를 사용할 수 있습니다. 배열은 배열에서 펼치고 객체는 객체에서 펼쳐 사용합니다.
존재 여부를 파악하려면 indexOf() 대신 Includes() 사용합시다.
const guests = ["wayne", "joe", "duke", "jane"];
function isGuestV1(list, name) {
return list.indexOf(name) > -1; // 실수 연발
}
function isGuestV2(list, name) {
return list.includes(name); // 코드도 짧고 실수도 줄고
}
console.log(isGuestV1(guests, "wayne"));
console.log(isGuestV2(guests, "kim"));
배열 원본을 조작하는 메소드는 최소한으로 사용합시다!
const guestListV1 = ["wayne", "joe", "duke", "jane"];
const guestListV2 = ["wayne", "joe", "duke", "jane"];
function addNewGuestV1(guest, guestList) {
guestList.push(guest);
return guestList;
}
function addNewGuestV2(guest, guestList) {
return [...guestList, guest];
}
console.log(addNewGuestV1("kim1", guestListV1));
console.log(addNewGuestV1("kim2", guestListV1));
console.log(addNewGuestV2("kim1", guestListV2));
console.log(addNewGuestV2("kim2", guestListV2));
/*
순수 함수
함수는 동일한 입력 값의 결과로 동일한 결과 값을 반환해야 합니다.
배열 같은 참조값은 원본을 수정 할 경우, 사이드 이펙트(부작용)를 유발 할 수 있습니다.
*/
배열의 각 원소가 원시 값이라는 전제하에 펼침 연산자를 사용하면 배열 원본 조작을 최소화 할 수 있습니다.
위 전제와 동일한 경우, 배열 원본 조작을 최소화 할 수 있는 배열의 메소드
- filter : 리턴 값이 참인 요소만을 가진 배열을 반환
- map : 리턴 값으로 구성 된 배열을 반환
- reduce : 누산한 하나의 값을 반환
얕은 복사와 깊은 복사
대부분의 자바스크립트 네이티브 복사 메소드는 얕은 복사 합니다.
참조값을 복사하는 경우, 깊은 복사로 사이드 이팩트를 예방합시다!
JSON.parse(JSON.stringify(/* Object to be deeply copied */));
// 또는 오픈소스 lodash의 cloneDeep() 메소드를 이용합시다!
동일한 정렬 유지하기
배열의 원본을 정렬할 경우 다른 필터 값을 사용 할 때 마다 조금씩 다른 정렬을 반환하는 경우를 볼 수 있습니다. 이는 배열의 원본을 가지고 정렬하기 때문에 발생하는 문제 입니다. 그러므로 정렬 할 때에도 원본을 복사하여 정렬합니다.
const guests = [
{
name: "Joe",
class: 10,
},
{
name: "Theo",
class: 5,
},
{
name: "Dyan",
class: 10,
},
];
function sortByClass(a, b) {
if (a.class === b.class) {
return 0;
}
return a.class - b.class;
}
const sortByName = (a, b) => {
if (a.name === b.name) {
return 0;
}
return a.name > b.name ? 1 : -1;
};
console.log(guests.sort(sortByClass));
console.log(guests.sort(sortByName));
console.log(guests.sort(sortByClass));
console.log("------------------------");
console.log([...guests].sort(sortByClass));
console.log([...guests].sort(sortByName));
console.log([...guests].sort(sortByClass));
어떤 경우 객체를 선택해야 하는 가?
객체는 정적인 정보에 적합합니다. 정보를 공유할 때는 객체가 유용합니다.
계속해서 갱신, 반복, 대체, 정렬 해야 할 정보에는 적절하지 않습니다. 이때는 맵을 사용하는 것이 낫습니다.
원래 데이터를 유지하면서 새로운 객체 생성하기
객체에 무심코 필드를 추가하거나 설정하면 색다른 지옥의 맛을 경험 할 수 있습니다. 그러므로 원래 데이터를 유지하면서 새로운 객체를 생성하여야 합니다.
lodash를 사용합시다.
맵으로 키-값 데이터를 갱신합시다
맵 자료형은 키와 값 쌍을 자주 변경하는 경우에 적합하도록 설계되었습니다. 반복(entries)과 같은 동작이 내장되어 있습니다. 또한 키 탐색의 경우 logN 시간을 소요합니다. 기본 객체는 선형 탐색으로 N 시간을 소요합니다.
// 키와 값 추가 V1
let filters = new Map()
.set("견종", "래브라도레트리버")
.set("크기", "대형견")
.set("색상", "갈색");
filters.get("견종");
// 키와 값 추가 V2
let filters2 = new Map([
["견종", "래브라도레트리버"],
["크기", "대형견"],
["색상", "갈색"],
]);
filters.get("색상");
// 값 제거
filters.delete("색상");
// 모든 키와 값 쌍 제거
filters.clear();
Map 객체는 정보를 자주 변경하는 경우 객체보다 훨씬 편리합니다. 코드가 보다 명료하다는 것이 제일 좋은 점 이지만요. 키로 모든 값을 이용 할 수 있습니다. 심지어 NaN도 됩니다. 참고로 NaN은 숫자값 중 하나 입니다.
Map.keys() 메소드는 배열이 아닌 이터레이터 객체를 반환합니다.
맵과 펼침 연산자로 키-값 데이터를 순회하기
객체를 순회할 때 for...in 문을 사용할 수 있지만 객체 키 외에는 접근할 수 없습니다. 객체 순회는 번거로운 작업을 요구합니다. 이와 대조적으로 맵은 직접 순회할 수 있습니다.
let filters = new Map()
.set("견종", "래브라도레트리버")
.set("크기", "대형견")
.set("색상", "갈색");
for (const [key, value] of filters.entries()) {
console.log(key, value);
}
Map 객체의 entries 메소드는 맵이터레이터를 반환합니다. entries 메소드는 매우 편리하기 때문에 ES2017부터 Object의 내장 메서드로 추가되었습니다.
Map 객체는 sort() 메소드를 내장하고 있지 않습니다. 그렇기 때문에 펼침 연산자를 이용해야 합니다.
console.log(
[...filters].sort((e1, e2) => {
return e1[0] > e2[0] ? 1 : -1;
})
);
배열 객체에 Map 객체를 펼치기 때문에 배열을 이용한다는 점에 유의합니다.
Map 생성 시 부수 효과 피하자
펼침연산자를 이용하여 원본을 훼손하지 않고 새로운 Map 객체를 만듭시다! Map 객체는 이미 존재하는 키와 값에 대해서는 갱신하기 때문에 아래와 같이 사용할 수 있습니다.
const defaults = new Map()
.set("견종", "래브라도레트리버")
.set("크기", "대형견")
.set("색상", "갈색");
const filters = new Map().set("색상", "검정색");
let update = new Map([...defaults, ...filters]);
console.log(update);
Set를 이용해 고유값을 관리하자
종종 많은 객체가 담긴 거대한 배열에서 값을 수집해야 할 때, 아주 유용합니다.
const colors = ['blue', 'red', 'red', 'black'];
const uniqueColors = new Set(colors);
console.log(uniqueColors); // Set { 'blue', 'red', 'black' }
console.log([...uniqueColors]); // [ 'blue', 'red', 'black' ]
const dogs = [
{
name: "Max",
size: "Small",
type: "Boston Terrier",
color: "Black",
},
{
name: "Doni",
size: "Big",
type: "Labrador Retriever",
color: "Black",
},
{
name: "Blue",
size: "Middle",
type: "Labrador Retriever",
color: "Brown",
},
];
const colors = new Set(dogs.map((e) => e.color));
const type = new Set(dogs.map((e) => e.type));
console.log(colors);
console.log(type);
Set 객체도 Map 객체와 마찬가지로 delete, clear, has 메소드가 있습니다. 추가 할 때는 add 메소드를 사용합니다.
조건문을 깔끔하게 작성합시다
"", '', false, 0, null, NaN, undefined은 거짓인 값 입니다. 이 값들을 이용하면 조건문을 더욱 짧게 작성 할 수 있습니다.
이 외에 값은 참인 값 입니다.
삼항 연산자를 이용합시다.
const AMPM = new Date().getHours() < 12 ? "AM" : "PM";
console.log(AMPM);
단락 평가로 더욱 더 짧게 조건문 작성합시다.
확실한 것을 먼저 평가하여 나머지 평가는 건너뛰는 것이 단락 평가 입니다. 나머지 평가를 건너뛰고 해야 할 일이 할당이기에 아래와 같이 이용 할 수 있습니다.
const result = true && "abc";
console.log(result);
const result2 = true || "abc";
console.log(result2);
const icon = {};
const path = icon.path || "pubilc/icons/default.png";
console.log(path);
조건문과 && 연산자 그리고 삼항 연산자를 조합하면 TypeError를 적절히 피할 수 있고 더욱 간략하게 코드를 작성 할 수 있습니다.
const userCongif = {};
if(userCongif.images && userConfig.images.length > 0){
console.log(userConfig.images[0]);
}
const userCongif2 = {
images : []
}
if(userCongif2.images && userCongif2.images.length > 0){
console.log(userCongif2.images[0]);
}
const images = userConfig.images; // undefined
console.log(images && images.length ? images[0] : 'default.png');
화살표 함수를 많이 이용합시다!
구문을 매우 짧게 작성 할 수 있습니다. 한 번만 사용하는 콜백 함수에 아주 좋습니다.
화살표 함수는 자신의 스코프를 기준으로 가장 가까운 객체와 this를 바인딩 합니다.
배열 순회 할 때 for( ; ; ) 문 사용을 피합시다.
for 문의 색인을 이용해서 값을 한 번에 하나씩 순회하는 방법은 관리해야 할 변수를 늘리며, 배열을 조작해야 합니다. 게다가 코드의 의도를 바로 알아챌 수 없습니다. 그러므로 배열을 순회하는 의도를 나타내는 배열의 메소드를 사용합시다!
배열의 메소드
map : 요소 형태를 바꿀 수 있지만 길이는 유지
filter : 길이를 변경하지만 형태는 유지
find : 한 개의 요소가 반환되고 형태는 유지
sort : 형태는 유지 순서 변경
forEach : 형태를 이용하지만 반환하지 않음
reduce : 길이, 형태 바꾸기 및 무엇이든 처리 가능
map() 메소드로 원하는 배열을 생성합시다!
const userInfoList = [
{
name: "Wayne",
age: 27,
},
{
name: "Kim",
age: 17,
},
];
const userNameList = userInfoList.map((e) => e.name);
console.log(userNameList);
filter()와 find()의 데이터의 부분집합을 생성해보자.
const userInfoList = [
{
name: "Wayne",
age: 27,
},
{
name: "Kim",
age: 17,
},
];
const adultUserLists = userInfoList.filter((e) => e.age > 18);
console.log(adultUserLists);
if(userInfoList.some(e => e.age < 19)){
console.log("미성년자가 있습니다!");
}
if(userInfoList.every(e => e.age < 19)){
console.log("오늘도 신분증 검사는 확실하게 합시다!");
}
const userInfoList = [
{
name: "Wayne",
age: 27,
},
{
name: "Kim",
age: 17,
},
{
name: "Nick",
age: 20,
},
];
const matchedUserList = userInfoList.filter((e) => e.name.match(/i/));
console.log(matchedUserList);
const studentScoreList = [
{
name: "Wayne",
score: 70,
},
{
name: "Kim",
score: 100,
},
{
name: "Nick",
score: 50,
},
];
const passedStudentList = studentScoreList.filter((e) => e.score > 50);
console.log(passedStudentList);
forEach 메소드
데이터를 조작하지 않으면서 부수효과를 낼 경우 유용합니다. 요소 하나 씩 읽으면서 특정 작업을 수행하는 것 이죠.
이제 forEach 메소드가 보이면 특정 부수효과 발생을 예측 할 수 있습니다.
const employeeList = [
{
name: "Wayne",
departMent: "dev",
email: "wayne@acb.kr",
},
{
name: "Kim",
departMent: "human",
email: "kim@acb.kr",
},
{
name: "Nick",
departMent: "human",
email: "nick@acb.kr",
},
];
sendNoticeToEmail = (list, msg) => { /* ... */ };
employeeList.forEach((e) => sendNoticeToEmail(employeeList, "공지"));
체이닝 적극 활용합시다!
배열의 내장 메소드 중 다수는 원본을 훼손하지 않고 새로운 결과 배열을 반환합니다. 곧 체이닝을 활용하기 아주 좋은 케이스 입니다.
const customerList = [
{
name: "Wayne",
age: 20,
},
{
name: "Kim",
age: 22,
},
{
name: "Nick",
age: 15,
},
];
customerList
.filter((e) => e.age < 19)
.forEach((e) =>
console.log(`${e.name}님 미성년자는 밤 10시 이후로 이용이 불가합니다.`)
);
reduce()를 적극 활용합시다!
배열을 이용해서 근본적으로 다른 새로운 데이터를 만들어야하는 경우가 있습니다. reduce 메소드는 배열의 길이와 데이터 형태를 모두 또는 각각 변경할 수 있습니다!
const customerList = [
{
name: "Wayne",
age: 20,
gender: "m",
active: true,
},
{
name: "Kim",
age: 22,
gender: "m",
active: false,
},
{
name: "Nick",
age: 15,
gender: "m",
active: true,
},
{
name: "Joy",
age: 14,
gender: "w",
active: true,
},
];
const aggregateActiveUserGender = customerList.reduce(
(a, c) => {
if (c.active) {
a.sum = a.sum + 1;
c.gender == "m" ? (a.man = a.man + 1) : (a.woman = a.woman + 1);
}
return a;
},
{
sum: 0,
man: 0,
woman: 0,
}
);
console.log(aggregateActiveUserGender);
종합해야 정보가 명확하지 않아도 괜찮다!
const customerList = [
{
name: "Wayne",
age: 20,
gender: "m",
active: true,
},
{
name: "Kim",
age: 22,
gender: "m",
active: false,
},
{
name: "Nick",
age: 15,
gender: "m",
active: true,
},
{
name: "Joy",
age: 14,
gender: "w",
active: true,
},
];
const aggregateActiveUserGender = customerList.reduce((a, c) => {
const count = (c.active && a[c.gender]) || 0;
return {
...a,
[c.gender]: count + 1,
};
}, {});
console.log(aggregateActiveUserGender);
reduce를 잘 쓰면 배열 데이터 처리는 아주 깔끔해지겠다! 동적 언어의 양날의 검은 아주 매력있습니다!
for...in 문과 for...of 문
배열의 메소드는 유용하지만 특정한 상황일 때 반복을 빠져나오고 싶을 때는비효율적 입니다.
for...of 문은 컬렉션의 멤버를 직접 순회 합니다. 펼침 연산자로 컬렉션을 배열에 담지 않아도 됩니다. 하지만 배열 메소드가 명확하고 적합할 때는 배열 메소드를 우선으로 사용합시다. 예측 가능성이 더욱 높이기 위해서요!
for...in 문은 이터러블을 위한 반복문 입니다. (Object.kets())
객체를 순회하면서 객체를 조작하지는 행위는 매우 위험합니다. 버그를 생성하는 아주 좋은 선택 입니다.
매개변수 기본값
함수에 매개변수가 누락되었을 경우 문제가 발생하는 경우 매개변수 기본값으로 이용합시다.
옵션 매개변수
옵션 매개변수를 여러개 사용하는 것은 실수를 저지르기 쉽습니다. 순서가 바뀌는 경우가 아주 많습니다. 그러므로 옵션은 객체 하나에 담아 전달하는 것이 좋습니다. 이 방법은 해결책이 아닌 차선책 입니다.
해체 할당
함수의 반환값으로써 객체, 매개변수로써 객체, import로써 객체 등을 재할당 할 때 아주 유용합니다.
const me = {
name: "Wayne",
age: 27,
gender: "m",
};
const { name, age, gender, country = "korea" } = me;
console.log(name, age, gender, country);
여기에 펼침 연산자까지 이용하면 전달 받은 객체의 키를 몰라도 새로운 변수에 정보를 담을 수 있습니다. 다만 여기서는 키워드는 동일하지만 나머지 매개변수라고 부릅니다. 해체 할당 후 남은 나머지 매개변수를 말합니다.
const me = {
name: "Wayne",
age: 27,
gender: "m",
country: "korea",
hairColor: "black",
};
const { name, age, gender, ...additional } = me;
console.log(name, age, gender, additional);
추가 키-값을 나머지 매개변수에 할당 합시다!
객체와 배열 모두 해체 할당 할 수 있습니다.
const landscape = {
src: "/landscape",
location: [32.0, -103.0],
};
const {
src: url,
location: [latitude, longitude],
} = landscape;
console.log(url, latitude, longitude);
매개변수로써 할당 단순화
옵션 매개변수가 객체라는 것을 알고 있을 경우에 아주 유용합니다.
const options = {
color: "red",
grade: 1,
};
function assignment({ color, ...others }) {
console.log(color, others);
}
assignment(options);
나머지 매개변수
arguments 객체는 함수에 전달된 모든 인수를 담은 배열과 유사한 컬렉션 입니다. 다만, 함수를 쓰는 입장에서는 내부 코드를 직집 보지 않는 이상 인수 목록을 받아 처리한다는 것을 알 수 없습니다. 예측 가능성이 떨이집니다. 그러므로 펼침 연산자를 이용한 나머지 매개변수를 이용하는 것이 더욱 좋습니다.
function getThenValue(value, ...args) {
return args.filter((e) => e > value);
}
console.log(getThenValue(10, 1, 5, 10, 15, 20));
console.log(getThenValue(10, ...[9, 18, 27]));
'Javascript' 카테고리의 다른 글
자바스크립트 깔끔하게 사용하기 - 기본2 (0) | 2020.04.10 |
---|---|
자바스크립트 깔끔하게 사용하기 - 테스트 (0) | 2020.04.08 |
자바스크립트 관련 책 추천 (0) | 2020.02.16 |
자바스크립트 알짜배기 - 1 (0) | 2020.01.28 |