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']
// ]