// badconst bad = {foo: 3,bar: 4,'data-blah': 5,};// goodconst good = {foo: 3,bar: 4,'data-blah': 5,};
hasOwnProperty, propertyIsEnumerable, isPrototypeOf와 같은 Object.prototype 메소드를 직접 호출하지 말기객체의 속성에 의해 메소드가 가려질수 있음
가려진 예시
const object = {a: 1,hasOwnProperty: false,};// 이 경우에는 오류가 발생합니다.console.log(object.hasOwnProperty('a')); // TypeError: object.hasOwnProperty is not a function
예시
// badconsole.log(object.hasOwnProperty(key));// goodconsole.log(Object.prototype.hasOwnProperty.call(object, key));// bestconst has = Object.prototype.hasOwnProperty; // 모듈스코프에서 한 번 캐시하세요.console.log(has.call(object, key));/* or \*/import has from 'has'; // https://www.npmjs.com/package/hasconsole.log(has(object, key));
객체에 대한 얕은복사를 할 때는 Object.assign 대신 객체 전개 구문을 사용
특정 속성이 생략된 새로운 개체를 가져올 때는 객체 나머지 연산자 사용js에선 원본 객체가 손상되는 것을 최대한으로 지양한다
100자가 넘는 문자열을 문자열 연결을 이용해 여러줄에 걸쳐 쓰지 말기문자열이 끊어지면 작업하기 까다롭고, 찾기도 힘듬
에시
// badconst errorMessage ='This is a super long error that was thrown because \of Batman. When you stop to think about how Batman had anything to do \with this, you would get nowhere \fast.';// badconst errorMessage ='This is a super long error that was thrown because ' +'of Batman. When you stop to think about how Batman had anything to do ' +'with this, you would get nowhere fast.';// goodconst errorMessage ='This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';
문자열을 생성하는 경우, 문자열 연결 대신, 템플릿 문자열 사용
예시
// badfunction sayHi(name) {return 'How are you, ' + name + '?';}// badfunction sayHi(name) {return ['How are you, ', name, '?'].join();}// goodfunction sayHi(name) {return `How are you, ${name}?`;}
절대로 문자열에 eval() 사용하면 안됨, 너무나 많은 취약점을 만듦eval is evil
문자열에 불필요한 이스케이프 문자 사용 자제가독성을 해침
함수
함수선언식 말고 기명 함수표현식을 사용함수 선언은 호이스팅 된다. 즉 함수가 선언되기 전에 함수를 참조하는것이 쉽다는 뜻 이는 유지보수에 어려움을 가져다 준다, 함수에 이름을 명시적으로 지정하면 디버깅 시 콜 스택에서 함수이름을 알 수 있다
예시
// badfunction foo() {// ...}// badconst foo = function () {// ...};// good// 변수 참조 호출과 구분되는 이름const short = function longUniqueMoreDescriptiveLexicalFoo() {// ...};
즉시 함수 표현식을 괄호로 감싸기괄호로 감싸면 괄호 안의 표현을 명확하게 해줌
예시
// 즉시 호출 함수 표현식 (IIFE)(function () {console.log('Welcome to the Internet. Please follow me.');})();
*즉시 함수 표현식에 방법은 여러 방법이 있지만 전체를 괄호로 묶는 방식을 사용하자**(function() {})() [X] (function() {}()) [O]*
함수 이외의 블록(If, while, {}, 등)에서 함수를 선언하지 말기,브라우저는 이를 허용하지만, 모두 다 다르게 해석한다
// really badfunction handleThings(opts) {// 안돼요! 우리는 함수 인자를 변경하면 안됩니다.// 더 안 좋은 경우: 만약 opts가 falsy한 값일 경우 당신이 원하는 객체로// 설정되겠지만, 이는 미묘한 버그를 일으킬 수 있습니다.opts = opts || {};// ...}// still badfunction handleThings(opts) {if (opts === void 0) {opts = {};}// ...}// goodfunction handleThings(opts = {}) {// ...}
*void 0 은 undefined 를 뜻한다,**void 0 == null: true, void 0 === null: false, void 0 == false: false*
사이드 이펙트가 있을만한 기본 매개변수는 사용하지 않기혼란을 야기
에시
var b = 1;// badfunction count(a = b++) {console.log(a);}count(); // 1count(); // 2count(3); // 3count(); // 3
*사이드이펙트(부수효과): 하나의 동작에, 두개 이상의 효과를 가져다 주는 것이라고 이해했다함수를 실행 → 전체 상태가 변화 (리액트)함수를 실행 → 외부 변수의 값이 변화값을 할당 → 다른 값도 변화**사이드 이펙트가 나쁜건 아니지만, 유지보수의 측면에서 순수함수 를 작성하는게 좋다고 한다순수함수: 오직 함수의 입력만이 함수의 결과에 영향을 주는 함수*
모듈 import 구문에 Webpack loader 구문 사용하지 말기import 에서 Webpack 구문을 사용하면 이 코드가 모듈 번들러에 연결됨, loader 구문은 webpack.config.js 에서 사용
예시
// badimport fooSass from 'css!sass!foo.scss';import barCss from 'style!css!bar.css';// goodimport fooSass from 'foo.scss';import barCss from 'bar.css';
자바스크립트 파일 확장자명을 명시하지 말기리팩토링을 제한시킨다
예시
// badimport foo from './foo.js';import bar from './bar.jsx';import baz from './baz/index.jsx';// goodimport foo from './foo';import bar from './bar';import baz from './baz';
이터레이터 & 제네레이터
이터레이터를 사용하지 말기, for-in 이나 for-of 대신 자바스크립트의 고급함수 사용고급함수는 불변 규칙을 적용한다. 사이드이펙트에 대해 추측하는것보다 순수함수를 다루는 것이 더 쉽다
예시
const numbers = [1, 2, 3, 4, 5];// badlet sum = 0;for (let num of numbers) {sum += num;}sum === 15;// goodlet sum = 0;numbers.forEach((num) => {sum += num;});sum === 15;// best (use the functional force)const sum = numbers.reduce((total, num) => total + num, 0);sum === 15;// badconst increasedByOne = [];for (let i = 0; i < numbers.length; i++) {increasedByOne.push(numbers[i] + 1);}// goodconst increasedByOne = [];numbers.forEach((num) => {increasedByOne.push(num + 1);});// best (keeping it functional)const increasedByOne = numbers.map((num) => num + 1);
*배열을 이터레이트할 때* `map()` `every()` `filter()` `find()` `findIndex()` `reduce()` `some()` 을 사용배열을 생성할 때는 `Object.keys()` / `Object.values()` / `Object.entries()`를 사용*개인적으로 가장 충격적이었던 사실..*
당분간은 제네레이터 사용하지 말기ES5로 트랜스파일이 잘 안됨트랜스파일: 한 언어로 작성된 소스코드가 비슷한 수준의 추상화를 가진 다른언어로 변환하는 것
만약 제네레이터를 사용할거면, 함수 시그니처의 공백이 적절한지 확인*function과 *는 같은 개념의 키워드, function* 로 사용해야 적절함, function * [X]*
근데 쓰지 말라니까 걍 쓰지마
필요한 곳에서 변수를 할당한되, 합당한 곳에 두기let 과 const는 블록 스코프이지, 함수 스코프가 아님
예시
// bad - unnecessary function callfunction checkName(hasName) {const name = getName();if (hasName === 'test') {return false;}if (name === 'test') {this.setName('');return false;}return name;}// goodfunction checkName(hasName) {if (hasName === 'test') {return false;}const name = getName();if (name === 'test') {this.setName('');return false;}return name;}
코드 윗쪽에서 사용할 변수를 전부 선언해야하는 강박에서 자유로워질수 있다
변수 할당 체이닝을 사용하지 않기변수할당 체이닝은 암시 전역변수를 만든다
예시
// bad(function example() {// 자바스크립트는 이것을// let a = ( b = ( c = 1 ) );// 로 해석합니다.// let 키워드는 변수 a에만 적용됩니다.// 변수 b와 c는 전역 변수가 됩니다.let a = (b = c = 1);})();console.log(a); // throws ReferenceErrorconsole.log(b); // 1console.log(c); // 1// good(function example() {let a = 1;let b = a;let c = a;})();console.log(a); // throws ReferenceErrorconsole.log(b); // throws ReferenceErrorconsole.log(c); // throws ReferenceError// `const`에도 동일하게 적용됩니다
단항 증감 연산자를 사용하지 말기오류를 일으킬 수 있고, num += 1과 같은 구문으로 값을 변경하는것이 ++, -- 구문을 사용하는것보다 의미있는 일이라고 생각된다
이것도 좀 충격적이네
값을 할당할 때 = 앞 뒤에서 줄 바꿈하지 말기할당하는 값의 길이가 길어질 경우 값을 괄호로 둘러싸기
let과 const 는 Temporal Dead Zones (TDZ)이라고 불리는 새로운 개념의 혜택을 받음var는 가장 가까운 함수스코프 꼭대기에 호이스팅 되는다는데 (안쓸거니까 알 필요없다)
예시
// (전역 변수 notDefined가 존재하지 않는다고 판정한 경우)// 동작하지 않습니다function example() {console.log(notDefined); // => throws a ReferenceError}// 그 변수를 참조하는 코드의 뒤에서 그 변수를 선언한 경우// 변수가 호이스트된 상태에서 동작합니다.// 주의:`true` 라는 값 자체는 호이스트되지 않습니다.function example() {console.log(declaredButNotAssigned); // => undefinedvar declaredButNotAssigned = true;}// 인터프리터는 변수선언을 스코프의 선두에 호이스트합니다// 위의 예는 다음과 같이 다시 쓸수 있습니다:function example() {let declaredButNotAssigned;console.log(declaredButNotAssigned); // => undefineddeclaredButNotAssigned = true;}// const와 let을 이용한 경우function example() {console.log(declaredButNotAssigned); // => throws a ReferenceErrorconsole.log(typeof declaredButNotAssigned); // => throws a ReferenceErrorconst declaredButNotAssigned = true;}
익명 함수의 경우 함수가 할당되기 전의 변수가 호이스트 됨
예시
function example() {console.log(anonymous); // => undefinedanonymous(); // => TypeError anonymous is not a functionvar anonymous = function () {console.log('anonymous function expression');};}
명명 함수의 경우도 똑같이 변수가 호이스트되고, 함수의 이름이나 본체는 호이스트 되지 않음
예시
function example() {console.log(named); // => undefinednamed(); // => TypeError named is not a functionsuperPower(); // => ReferenceError superPower is not definedvar named = function superPower() {console.log('Flying');};}// 함수 이름이 변수 이름과 동일할 때도 마찬가지다.function example() {console.log(named); // => undefinednamed(); // => TypeError named is not a functionvar named = function named() {console.log('named');};}
함수 선언은 함수의 이름과 본체가 호이스트 됨
비교 연산자
”==”, “!=” 대신 ”=", "!” 사용
if문 과 같은 조건식은 ToBoolean 메소드에 의해 강제형변환이 되어 항상 다음과 같은 규칙을 따름
*Object는 true로 평가*
*Undefined는 false로 평가*
*Null은 false로 평가*
*Booleans는 boolean형의 값으로 평가*
*Numbers는 true로 평가, 하지만 +0, -0, Nan의 경우 false로 평가*
// badif (isValid === true) {// ...}// goodif (isValid) {// ...}// badif (name) {// ...}// goodif (name !== '') {// ...}// badif (collection.length) {// ...}*이것도 좀 간과하기 쉬운 컨벤션인듯더 자세한 내용은* [Truth Equality and JavaScript](https://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/#more-2108)
렉시컬 선언(let, const, function, class) 을 포함하는 switch 문 안에 블록을 만들 떄 괄호 사용하기
예시
// badswitch (foo) {case 1:let x = 1;break;case 2:const y = 2;break;case 3:function f() {// ...}break;default:class C {}}// goodswitch (foo) {case 1: {let x = 1;break;}case 2: {const y = 2;break;}case 3: {function f() {// ...}break;}case 4:bar();break;default: {class C {}}}
삼항 연산자를 중첩해서는, 안되며 일반적으로 한줄로 표현
불필요한 삼항 연산자는 사용하지 말기어지간하면 삼항 연산자는 사용하는걸 피하자
예시
// badconst foo = a ? a : b;const bar = c ? true : false;const baz = c ? false : true;// goodconst foo = a || b;const bar = !!c;const baz = !c;
연산자를 섞어 사용할 때 해당 연산자들을 괄호로 둘러싸기코드가 더 읽기쉬우지고, 개발자의 의도가 명확해짐
예시
// badconst foo = (a && b < 0) || c > 0 || d + 1 === 0;// badconst bar = a ** b - (5 % d);// bad// (a || b) && c 으로 혼동할 수 있습니다.if (a || (b && c)) {return d;}// goodconst foo = (a && b < 0) || c > 0 || d + 1 === 0;// goodconst bar = a ** b - (5 % d);// goodif (a || (b && c)) {return d;}// goodconst bar = a + (b / c) * d;
// bad// make()는 전달된 태그명을 기반으로// 새로운 요소를 반환한다.//// @param {String} tag// @return {Element} elementfunction make(tag) {// ...return element;}// good/*** make()는 전달된 태그명을 기반으로* 새로운 요소를 반환한다.*/function make(tag) {// ...return element;}
한 줄 주석을 사용 할때에는 // 를 사용하기. 주석 전에는 빈행을 넣기
예시
// badconst active = true; // is current tab// good// is current tabconst active = true;// badfunction getType() {console.log('fetching type...');// set the default type to 'no type'const type = this.type || 'no type';return type;}// goodfunction getType() {console.log('fetching type...');// set the default type to 'no type'const type = this.type || 'no type';return type;}// also goodfunction getType() {// set the default type to 'no type'const type = this.type || 'no type';return type;}
모든 주석은 공백으로 시작해야함
예시
// bad//is current tabconst active = true;// good// is current tabconst active = true;// bad/***make()는 전달된 태그명을 기반으로*새로운 요소를 반환한다.*/function make(tag) {// ...return element;}// good/*** make()는 전달된 태그명을 기반으로* 새로운 요소를 반환한다.*/function make(tag) {// ...return element;}
문제를 지적하거나 재고를 요구하는 경우, 주석 앞에 FIXME, TODO 를 붙임다른 개발자에게 빠른 이해를 도울 수 있음
문제에 대한 주석은 // FIXME: 사용
예시
class Calculator extends Abacus {constructor() {super();// FIXME: 전역 변수를 사용해서는 안 됨total = 0;}}
여러 빈행을 쓰지 말기, 파일의 맨앞에 빈행 쓰지 말기파일 끝에는 마지막행에는 빈행 하나두기
예시
// bad - 여러 개의 빈 줄var x = 1;var y = 2;// bad - 파일 끝에 2개 이상의 빈 줄var x = 1;var y = 2;// bad - 파일 시작에 1개 이상의 빈 줄var x = 1;var y = 2;// goodvar x = 1;var y = 2;
// bad - 마지막에 쉼표가 없는 경우 git diffconst hero = {firstName: 'Florence',- lastName: 'Nightingale'+ lastName: 'Nightingale',+ inventorOf: ['coxcomb chart', 'modern nursing']};// good - 마지막에 쉼표가 있는 경우 git diffconst hero = {firstName: 'Florence',lastName: 'Nightingale',+ inventorOf: ['coxcomb chart', 'modern nursing'],};
쉼표를 뒤에 붙이는 스타일을 유지해야만 git diffs에 불필요한 정보들이 들어가지 않는다
세미콜론
세미콜론 쓰자하던대로 하면된다
형변환 강제
구문의 선두에서 형 강제를 하자
문자열String 으로 형변환 시키자원시타입에 new를 붙여서 초기화하면 Object가 된다, new 붙이지 말기
예시
// => this.reviewScore = 9;// badconst totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string"// badconst totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf()// badconst totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string// goodconst totalScore = String(this.reviewScore);
숫자를 형 변환하는 경우 Number를 사용하고, parseInt를 사용하는 경우 기수도 명시를 해줘야 함10진수로 변환의 경우, Number(numStr) 이나 parseInt(numStr, 10) 를 사용하라는 뜻
예시
const inputValue = '4';// badconst val = new Number(inputValue);// badconst val = +inputValue;// badconst val = inputValue >> 0;// badconst val = parseInt(inputValue);// goodconst val = Number(inputValue);// goodconst val = parseInt(inputValue, 10);
어떤 이유에서인지 parseInt가 병목현상을 일으켜 성능상의 이슈로 비트 시프트를 사용해야하는 경우 하려고했던 것을 왜(why)와 무엇을(what) 주석으로 남겨라
예시
// good/*** parseInt was the reason my code was slow.* Bitshifting the String to coerce it to a* Number made it a lot faster.* 코드가 느린 원인은 parseInt였음.* 비트 시프트를 통해 문자열을 강제 형변환하여* 성능을 개선시킴.*/const val = inputValue >> 0;
주의: 비트연산자를 사용하는경우, 숫자는 64비트로 표현되어 있으나, 비트시프트를 사용하는경우 32비트 정수로 넘겨짐따라서 숫자가 32비트 이상 넘어가면 예기치못한 현상을 야기할수 있음
// badfunction user(options) {this.name = options.name;}const bad = new user({name: 'nope',});// goodclass User {constructor(options) {this.name = options.name;}}const good = new User({name: 'yup',});
언더스코어를 사용하지 마라자바스크립트는 속성이나 메소드 측면에서 은닉된 정보라는 개념을 갖고 있지않다. 언더스코어는 일반적으로 “private” 을 의미하지만 자바스크립트에서 해당 속성은 완전히 public하다.요약 자바스크립트에서 뭔가 private하게 사용하고싶다면, 그것을 불가능하다
예시
// badthis.__firstName__ = 'Panda';this.firstName_ = 'Panda';this._firstName = 'Panda';// goodthis.firstName = 'Panda';// good, in environments where WeakMaps are available// see https://kangax.github.io/compat-table/es6/#test-WeakMapconst firstNames = new WeakMap();firstNames.set(this, 'Panda');
참조에 this를 저장하지 말기화살표 함수나, Function#bind 사용
예시
// badfunction foo() {const self = this;return function () {console.log(self);};}// badfunction foo() {const that = this;return function () {console.log(that);};}// goodfunction foo() {return () => {console.log(this);};}
bind, apply, call 예시
공통점: 전부 첫번째 인자가 this의 값으로 들어감
차이점: bind는 말그대로 값들과 함수나 클래스를 묶어주는 느낌, apply, call은 즉시 호출
apply, call의 차이점: apply는 배열을 인자로 쓰고, call은 각각 변수를 인자롤 쓴다
function sum(a, b, c) {return a + b + c;}const sum123 = sum.bind(null, 1, 2, 3);const arr = [1, 2, 3];console.log(sum123()); // 6console.log(sum.apply(null,arr)); // 6console.log(sum.call(null, 1, 2, 3)); // 6
파일 이름은 default export의 이름과 일치해야 함
대문자든 소문자든간에 파일이름은 default export의 이름과 일치해야한다
예시
// 파일 1 내용class CheckBox {// ...}export default CheckBox;// 파일 2 내용export default function fortyTwo() { return 42; }// 파일 3 내용export default function insideDirectory() {}// 다른 파일// badimport CheckBox from './checkBox'; // PascalCase import/export, camelCase filenameimport FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase exportimport InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export// badimport CheckBox from './check_box'; // PascalCase import/export, snake_case filenameimport forty_two from './forty_two'; // snake_case import/filename, camelCase exportimport inside_directory from './inside_directory'; // snake_case import, camelCase exportimport index from './inside_directory/index'; // requiring the index file explicitlyimport insideDirectory from './insideDirectory/index'; // requiring the index file explicitly// goodimport CheckBox from './CheckBox'; // PascalCase export/import/filenameimport fortyTwo from './fortyTwo'; // camelCase export/import/filenameimport insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"// ^ supports both insideDirectory.js and insideDirectory/index.js
함수를 export default할 때 캐멀케이스 사용하기, 파일이름과 함수이름은 같아야 함
생성자 / 클래스 / 싱글톤 / 함수 라이브러리 / 단순 객체를 export 할 때에는 파스칼케이스 사용하기헷갈릴만한데, export하는 객체의 경우는 파스칼케이스로 쓰라한다 / 일반적으로는 캐멀케이스
const일 때
모든 const 변수 이름을 대문자로 짓나요? - 이것은 필수사항이 아니며, 파일 내 상수 이름을 꼭 대문자로 지을 필요는 없습니다. 하지만 내보내기되는 상수 이름은 대문자로 지어야 합니다.
프로그래머가 해당 변수와 그 속성이 변하지 않을것이라 확신할 때내보내기 되는 객체 이름을 대문자로 짓나요? - 최상위 수준의 내보내기를 할 때 대문자로 이름짓고 (예시: EXPORTED_OBJECT.key) 모든 중첩된 속성이 변경되지 않도록 유지합니다.
예시
// badconst PRIVATE_VARIABLE ='should not be unnecessarily uppercased within a file';// badexport const THING_TO_BE_CHANGED = 'should obviously not be uppercased';// badexport let REASSIGNABLE_VARIABLE ='do not use let with uppercase variables';// ---// allowed but does not supply semantic valueexport const apiKey = 'SOMEKEY';// better in most casesexport const API_KEY = 'SOMEKEY';// ---// bad - unnecessarily uppercases key while adding no semantic valueexport const MAPPING = {KEY: 'value',};// goodexport const MAPPING = {key: 'value',};
접근자
속성을 위한 접근자 함수는 필수가 아님
자바스크립트의 getters/setters를 사용하지 마라예기치 못한 사이드이펙트를 일으키고, 테스트와 유지보수를 힘들게 한다