JavaScript
JS - GOOD/BAD CODE
foxlee
2021. 7. 18. 10:22
const/let
- const 는 재할당이 불가능하기에 다른 개발자나 이후 본인이 다시 코드를 볼때 변수 선언 후 변경이 생기지 않는 다는 것을 확신할 수 있기 때문에 값을 변경하는 경우가 아니라면 무조건 const 를 사용해라(배열, 객체의 경우 배열 항목, 객체 키,값은 변경할 수 있음)
배열 메서드 활용
// 배열의 경우 빈 배열로 선언하여 loop 하는 것보다 filter 등 배열 메서드를 활용해라
// 배열 내의 일정 조건으로 다시 배열을 만들때
const items = [
{
name: "car",
active: true,
},
{
name: "bus",
active: false,
},
];
// Bad
const activeItems_ = [];
for (let i = 0; i < items.length; i++) {
if (items[i].active) {
activeItems_.push(items[i]);
}
}
// Good
// 1. 조작(mutation)을 피할수 있다.
// 2. 코드가 줄어든다.
// 3. 변수 선언을 미리 하지 않아도 됨
const activeItems = items.filter((item) => item.active);
console.log(activeItems_, activeItems);
// 배열의 매서드 find
const dogPair = [
["name", "Don"],
["color", "black"],
];
function getName(dogPair) {
return dogPair.find((attribute) => attribute[0] === "name")[1];
}
console.log(getName(dogPair)); // Don
// 배열의 매서드 include
const sections = ["contact", "shipping"];
// Good
function displayShipping(sections) {
return sections.includes("shipping");
}
// Bad
// 존재하지 않을 경우 -1, 첫 항목의 경우 인덱스가 0이기에 fasly 값으로 좋지 못함
function displayShipping(sections) {
return sections.indexOf("shipping") > -1;
}
// 배열의 매서드 map
const band = [
{
name: "corbett",
instrument: "guitar",
},
{
name: "evan",
instrument: "guitar",
},
{
name: "sean",
instrument: "bass",
},
{
name: "brett",
instrument: "drums",
},
];
// map은 콜백함수를 받기때문에 함수를 인자로 넣거나 화살표 익명함수
function getInstruments() {
function getInstrument(member) {
return member.instrument;
}
const instruments = band.map(getInstrument);
return instruments;
}
function getInstruments2() {
const instruments = band.map((member) => member.instrument);
return instruments;
}
// find
const instructors = [
{
name: "Jim",
libraries: ["MERIT"],
},
{
name: "Sarah",
libraries: ["Memorial", "SLIS"],
},
{
name: "Eliot",
libraries: ["College Library"],
},
];
function findMemorialInstructor(instructors) {
const librarian = instructors.find((instructor) => {
return instructor.libraries.includes("Memorial");
});
return librarian;
}
function findAnyInstructor(instructors) {
const findByLibrary = (library) => (instructor) => {
return instructor.libraries.includes(library);
};
const librarian = instructors.find(findByLibrary("MERIT")); // find 메서드에 콜백함수로 findByLibrary 보내줌 - 커링 기법
return librarian;
}
console.log(findMemorialInstructor(instructors));
console.log(findAnyInstructor(instructors));
// chain
const students = [
{
focus: "nagarjuna",
},
{
focus: "logic",
},
{
focus: "consciousness",
},
{
focus: "",
},
{
focus: "logic",
},
];
const focuses = students
.map((student) => student.focus)
.filter((focus) => focus)
.reduce((focuses, focus) => {
const count = focuses.get(focus) || 0;
focuses.set(focus, count + 1);
return focuses;
}, new Map());
const formattedFocus = [...focuses]
.sort()
.map(([name, count]) => `${name}: ${count}`);
console.log(formattedFocus); // ["consciousness: 1", "logic: 2", "nagarjuna: 1"]
const sailors = [
{
name: "yi hong",
active: true,
email: "yh@yhproductions.io",
},
{
name: "alex",
active: true,
email: "",
},
{
name: "nathan",
active: false,
email: "",
},
];
function sendActiveMemberEmail(sailors, sendEmail) {
sailors
.filter((sailor) => sailor.active)
.map((sailor) => sailor.email || `${sailor.name}@wiscsail.io`)
.forEach((sailor) => sendEmail(sailor));
}
const sendEmail = (email) => {
console.log(`Eamil sent to ${email}`);
};
console.log(sendActiveMemberEmail(sailors, sendEmail));
// Eamil sent to yh@yhproductions.io
// Eamil sent to alex@wiscsail.io
펼침 연산자
// 기존의 team 변수를 참조하는 것이 아닌 새롭게 할당하기 위해 펼침 연산자 사용
const team = ["Joe", "Dyan", "Bea", "Theo"];
// Good
function alphabetizeTeam(team) {
return [...team].sort();
}
const sortedTeam = alphabetizeTeam(team);
console.log(team, sortedTeam); // ["Joe", "Dyan", "Bea", "Theo"] (4) ["Bea", "Dyan", "Joe", "Theo"]
// Bad
const team_ = ["Joe", "Dyan", "Bea", "Theo"];
function alphabetizeTeam_(team) {
return team.sort();
}
const sortedTeam_ = alphabetizeTeam_(team_);
console.log(team_, sortedTeam_); // ["Bea", "Dyan", "Joe", "Theo"] (4) ["Bea", "Dyan", "Joe", "Theo"]
// 펼침 연산자 2
const book = ["Reasons and Persons", "Derek Parfit", 19.99];
function formatBook(title, author, price) {
return `${title} by ${author} $${price}`;
}
// bad
formatBook(book[0], book[1], book[2]);
// good
formatBook(...book);
// 배열에서 항목 제거
function removeItem(items, removable) {
if (items.includes(removable)) {
const index = items.indexOf(removable);
return [...items.slice(0, index), ...items.slice(index + 1)];
}
return items;
}
객체 할당
const defaults_ = { author: "", title: "", year: 2017, rating: null };
const defaults = { author: "", title: "", year: 2017, rating: null };
const textbook = {
author: "Joe Morgan",
title: "Simplifying JavaScript",
};
// bad defaults 값을 변경함
const updated_ = Object.assign(defaults_, textbook);
console.log(defaults_, updated_);
// {author: "Joe Morgan", title: "Simplifying JavaScript", year: 2017, rating: null}
// {author: "Joe Morgan", title: "Simplifying JavaScript", year: 2017, rating: null}
// good
const updated = Object.assign({}, defaults, textbook);
console.log(defaults, updated);
// {author: "", title: "", year: 2017, rating: null}
// {author: "Joe Morgan", title: "Simplifying JavaScript", year: 2017, rating: null}
// 객체 깊은 복사
const defaultEmployee = {
name: {
first: "",
last: "",
},
years: 0,
};
// bad
const employee_ = Object.assign({}, defaultEmployee);
employee_.name.first = "Joe";
defaultEmployee; // 객체 안의 name 객체의 경우 깊은 복사가 안되고 참조되어 원래 데이터의 값이 변경됨
// good
const employee2 = Object.assign({}, defaultEmployee, {
name: Object.assign({}, defaultEmployee.name),
});
객체보다는 Map 활용
- 키-값 쌍이 자주 추가, 삭제되는 경우
- 키가 문자열이 아닌 경우
- 아래의 기능들로 Map 에 항상 메서드를 사용하며, 언어 수준의 연산자를 섞지 않음
- 객체처럼 delete, 재할당 하지 않으며, clear() 메서드를 사용할 수 있어 새로운 인스턴스 생성할 필요 없음
- 단점 : 정렬 메서드는 없음
- 펼침 연산자로 [...mapData] = [[key1, value1], [key2, value2]] 의 배열로 변경 가능
const petFilters = new Map();
function addFilters(filters, key, value) {
filters.set(key, value);
}
function deleteFilters(filters, key) {
filters.delete(key);
}
function clearFilters(filters) {
filters.clear();
}
// Map의 업데이트
function applyDefaults(map, defaults) {
return new Map([...defaults, ...map]);
}
// 위와 같은 기능의 객체
// 단점 : 객체의 키 값은 숫자도 문자열로 변경되어 저장됨
// 삭제 시 결국 변수를 재 할당하는 것
function addFilters_(filters, key, value) {
filters[key] = value;
}
function deleteFilters_(filters, key) {
delete filters[key];
}
function clearFilters_(filters) {
filters = {};
return filters;
}
Set - 중복된 값은 추가하지 않는 배열
function getUniqueColors(dogs) {
const unique = new Set();
for (const dog of dogs) {
unique.add(dog.color);
}
return [...unique];
}
조건문과 삼항 연산자
function configureTimePermissions(title) {
const permissions = title === "manager" ? ["time", "pay"] : ["time"];
return permissions;
}
// 자바스크립트에서 거짓값
const faslyList = [false, null, 0, NaN, "", undefined];
function getImage_(userConfig) {
// userConfig.images = 객체에 키 값이 존재하는지
// userConfig.images.length > 0 = 빈 배열이 아닌지 체크해야 TypeError 방지
if (userConfig.images && userConfig.images.length > 0) {
return userConfig.images[0];
}
return "default.png";
}
// better
function getImage(userConfig) {
const images = userConfig.images;
return images && images.length ? images[0] : "default.png";
}
화살표 함수와 콜백함수
function applyCustomGreeting(name, callback) {
return callback(capitalize(name));
}
function greetWithExcitement() {
const greeting = applyCustomGreeting("joe", (name) => `Hi, ${name}!`);
return greeting;
}
반복문
// 반복분
const firms1 = {
10: "Ivie Group",
23: "Soundscaping Source",
31: "Big 6",
};
function checkConflicts1(firms, isAvailable) {
for (const id in firms) {
if (!isAvailable(parseInt(id, 10))) {
return `${firms[id]} is not available`;
}
}
return "All firms are available";
}
const firms2 = new Map()
.set(10, "Ivie Group")
.set(23, "Soundscaping Source")
.set(31, "Big 6");
function checkConflicts2(firms, isAvailable) {
for (const firm of firms) {
const [id, name] = firm; // key, value 같이 받음
if (!isAvailable(id)) {
return `${name} is not available`;
}
}
return "All firms are available";
}
함수의 매개변수 해체 할당
function determineCityAndState([latitude, longitude]) {
const region = {
city: "Hobbs",
county: "Lea",
state: {
name: "New Mexico",
abbreviation: "NM",
},
};
return region;
}
// 매개 변수 객체 해체 할당
function setRegion({ location, ...details }) {
const { city, state } = determineCityAndState(location);
return {
city,
state: state.abbreviation,
...details,
};
}
// 매개 변수의 default 값
// Bad
function convertWeight(weight, ounces, roundTo) {
const oz = ounces / 16 || 0;
const total = weight + oz;
const conversion = total / 2.2;
const round = roundTo === undefined ? 2 : roundTo;
return roundToDecimalPlace(conversion, round);
}
// Good
function convertWeight(weight, ounces = 0, roundTo = 2) {
const total = weight + ounces / 16;
const conversion = total / 2.2;
return roundToDecimalPlace(conversion, roundTo);
}
// arguments - 배열
function getArguments(...args) {
return args;
}
console.log(getArguments("Bloomsday", "June 16")); // ["Bloomsday", "June 16"]
function validateCharacterCount(max, ...items) {
// ... 없이 items으로 하면 매개 변수 그대로 적용되어 배열이 아닌 경우 items.every 에서 에러 발생
return items.every((item) => item.length < max);
}
validateCharacterCount(10, "wvoquie"); // ...items 로 배열로 할당됨
// true
validateCharacterCount(10, ...["wvoquie"]);
// true
validateCharacterCount(10, "Hobbs", "Eagles");
// true
함수
// 해체 할당
const landscape = {
title: "Landscape",
photographer: "Nathan",
equipment: "Canon",
format: "digital",
src: "/landscape-nm.jpg",
location: [32.7122222, -103.1405556],
};
const anonymous = {
title: "Kids",
equipment: "Nikon",
src: "/garden.jpg",
location: [38.9675338, -95.2614205],
};
// Bad
function displayPhoto_(photo) {
const title = photo.title;
const photographer = photo.photographer || "Anonymous";
const location = photo.location;
const url = photo.src;
const copy = { ...photo };
delete copy.title;
delete copy.photographer;
delete copy.location;
delete copy.src;
const additional = Object.keys(copy).map((key) => `${key}: ${copy[key]}`);
return `
<img alt="Photo of ${title} by ${photographer}" src="${url}" />
<div>${title}</div>
<div>${photographer}</div>
<div>Latitude: ${location[0]} </div>
<div>Longitude: ${location[1]} </div>
<div>${additional.join(" <br/> ")}</div>
`;
}
// Good ( 해체 할당은 let으로 변수를 할당 - 변경 가능)
function displayPhoto({
title,
photographer = "Anonymous",
location: [latitude, longitude],
src: url, // 키 변경 src -> url
...other
}) {
const additional = Object.keys(other).map((key) => `${key}: ${other[key]}`);
return `
<img alt="Photo of ${title} by ${photographer}" src="${url}" />
<div>${title}</div>
<div>${photographer}</div>
<div>Latitude: ${latitude} </div>
<div>Longitude: ${longitude} </div>
<div>${additional.join(" <br/> ")}</div>
`;
}
커링
링은 함수 하나가 n개의 인자를 받는 과정을 n개의 함수로 각각의 인자를 받도록 하고, 부분적으로 적용된 함수를 체인으로 계속 생성해 결과적으로 값을 처리
const discounter = (discount) => (price) => price * (1 - discount);
const tenPercentOff = discounter(0.1);
tenPercentOff(100); // 90
const dogs = [
{
name: "max",
weight: 10,
breed: "boston terrier",
state: "wisconsin",
color: "black",
},
{
name: "don",
weight: 90,
breed: "labrador",
state: "kansas",
color: "black",
},
{
name: "shadow",
weight: 40,
breed: "labrador",
state: "wisconsin",
color: "chocolate",
},
];
function getDogNames(dogs, filterFunc) {
return dogs.filter(filterFunc).map((dog) => dog.name);
}
const identity = (field) => (value) => (dog) => dog[field] === value;
const colorCheck = identity("color");
const stateCheck = identity("state");
console.log(getDogNames(dogs, colorCheck("chocolate")));
// ['shadow']
console.log(getDogNames(dogs, stateCheck("kansas")));
// ['don']
const building = {
hours: "8 a.m. - 8 p.m.",
address: "Jayhawk Blvd",
};
const manager = {
name: "Augusto",
phone: "555-555-5555",
};
const program = {
name: "Presenting Research",
room: "415",
hours: "3 - 6",
};
const exhibit = {
name: "Emerging Scholarship",
contact: "Dyan",
};
// END:info
// START:func
function mergeProgramInformation(building, manager) {
const { hours, address } = building;
const { name, phone } = manager;
const defaults = {
hours,
address,
contact: name,
phone,
};
return (program) => {
return { ...defaults, ...program };
};
}
const programInfo = mergeProgramInformation(building, manager)(program);
const exhibitInfo = mergeProgramInformation(building, manager)(exhibit);
const birds = ["meadowlark", "robin", "roadrunner"];
const zip =
(...left) =>
(...right) => {
return left.map((item, i) => [item, right[i]]);
};
console.log(zip("kansas", "wisconsin", "new mexico")(...birds)); // [
// ['kansas', 'meadowlark'],
// ['wisconsin', 'robin'],
// ['new mexico', 'roadrunner']
// ]