Destructuring이 마치 냉장고에서 샌드위치 재료를 꺼내듯 원하는 프로퍼티(속성)을 추출해서 손쉽게 사용한다(변수에 바인딩 한다)는 개념을 이전 글에서 배웠습니다. 쉽게 말해, Object Destructuring은 냉장고(객체) 속 음식재료(프로퍼티)를 변수(냄비)에 넣어 사용한다고 볼 수 있습니다.
Array Destructuring에 대해 짧게 알아봤지만, Array도 중요하지만 Object Destructuring 또한 너무나 중요한 개념이기 때문에 이번에 배워보겠습니다. 먼저, JavaScript에서의 'Object'는 무엇일까요?
Object
먼저 객체에 대해 짧게 알아보겠습니다.
JavaScript는 객체기반 프로그래밍이라고도 하죠. 자바스크립트를 구성하는 거의 모든 것들은 객체라고 할 수 있습니다. 객체는 최종적으로 0개 이상의 프로퍼티 집합으로 볼 수 있으며, 프로퍼티란 {Key:Value}라고 볼 수 있습니다.
Object(객체) : 키(key)와 값(value)으로 구성된 프로퍼티(property)의 정렬되지 않은 집합
JavaScript의 기본 타입(data type)은 객체(Object)입니다.
JavaScript에서 숫자, 문자열, 불리언, undefined 타입 (즉, 원시 타입)을 제외한 모든 것이 객체입니다.
숫자, 문자열, 불리언, undefined과 같은 원시 타입은 객체가 아니지만, 값이 정해진 객체로 간주되어 객체로서의 특성도 가집니다.
프로퍼티의 값으로 함수(function)이 올 수 있는데, 이 때 이 것을 메서드(method)라고 부릅니다.
// 객체 person
var person = {
// 프로퍼티 키(Key) : 프로퍼티 값(Vaule)
name: 'hwonda', // 프로퍼티 1
age: 30 // 프로퍼티 2
}
△ 위와 같이 객체는 프로퍼티 집합으로 볼 수 있습니다.
var person = {
distance: 0,
// 객체 속 프로퍼티 키가 '함수'인 경우, 이를 '메서드'라고 부른다.
// 객체 'person' 내의 메서드 'walking'
walking: function() {
distance++;
}
}
△ 이처럼 메서드는 프로퍼티(key:value형태의 data)를 참조하고 조작할 수 있는 동작(like function)입니다.
// ES6
let a = 0;
let b = 1;
// ES6 문법으로 프로퍼티 값으로 변수를 사용할 때
// 변수이름 == 프로퍼티 키라면
// 프로퍼티 키를 생략할 수 있습니다.
const obj = {a, b};
console.log(obj); // {a: 0,b: 1}
△ 이 내용을 잘 기억해주세요! 아래 Object Destructuring ES6문법에서 참고할 예정입니다.
Object Destructuring
객체 추출은 다음과 같이 진행됩니다.
// 변수선언 = 객체 혹은 프로퍼티
//
// 그러면 내가 만든(객체의 필요한 data를 가지는) 새 변수를 마음껏 쓸 수 있지.
ES5 문법으로 객체 추출을 하기 위해서는 프로퍼티 키(Key)를 사용합니다.
// ES5
var obj = {a: 1, b: 2};
var one = obj.a;
var two = obj.b;
console.log(one, two); // 1 2
△ 위와 같은 경우, 마침표 프로퍼티 접근 연산자(.)를 사용했습니다.
마침표 외에 대괄호 프로퍼티 접근 연산자([ ])도 사용 가능합니다.
// ES5
var obj = {a: 1, b: 2, 10: '이것도 돼?'}
// 대괄호로 프로퍼티를 가져오는 경우, 프로퍼티 키는 '따옴표' 안에 넣어야 합니다.
var one = obj['a'];
console.log(one); // 1
// 그렇지 않는 경우 다음과 같은 에러가 뜹니다.
var two = obj[b];
console.log(two); // Uncaught ReferenceError: b is not defined
// 신기하게도, 프로퍼티 키가 숫자인 경우 '따옴표'는 생략 가능합니다.
var ten = obj[10];
console.log(ten); // '이것도 돼?'
다음과 같이 git clone은 원격 저장소의 내용을 로컬 저장소에 그대로 이전시켜 버립니다. 이 때, 자동으로 원격 저장소(remote)의 이름이 'origin'으로 저장됩니다. 따라서 나중에 git fetch origin을 한다면 clone 이후 수정된 사항에 대해 업데이트할 수 있습니다.
git pull은 git clone과 비슷하게 내용을 이전시키는 것은 맞지만, 나의 branch의 상태를 유지하면서 내용만 내려받습니다. 이 때, 내 로컬 히스토리가 유지되는 상태이기 때문에 히스토리 싱크가 맞지 않으면 오류가 발생합니다. (병합 충돌)
→ 이로 인해 초보자의 경우, branch가 꼬이는 경우가 많아 git pull보다 git clone을 해버리는 경우가 왕왕 있습니다.
▽ git pull의 형식은 다음과 같습니다.
$ git pull [remote] [branch]
▽ 그리고 git clone과 동일한 작업을 수행하려면 다음과 같습니다.
$ git pull origin master
: remote의 이름을 origin, branch 이름을 master로 하면, 로컬의 master가 원격의 master를 추적하는 git clone과 동일한 작업을 수행할 수 있습니다.
git fetch
사실 git clone과 git pull을 비교하기보다 git fetch와 git pull를 비교하는 게 일반적입니다.
$ git fetch [remote]
: 로컬에는 없지만 원격 저장소에 있는 모든 데이터를 가지고 옵니다. 하지만, 자동으로 로컬 branch에 merge시키는 git pull과는 달리, 내용만 가져오는 것이기 때문에 merge를 따로 수행해야 합니다.
$ git checkout [병합할 줄기 branch]
$ git merge [병합당하는 가지 branch]
깃허브에 있는 코드를 내 컴퓨터로 가져오고 싶으신가요? 그렇다면 아래 내용을 잘 읽어보시기 바랍니다.
깃허브(github.com) 사이트 내에 있는 레파지토리는 이전 글에 설명한 바와 같이 원격저장소에 저장됩니다. 내 PC에서 작업하는 내용은 로컬저장소에 저장됩니다. 원격저장소 -> 로컬저장소로 파일을 이동하고 싶으면, git clone 혹은 git pull을 하면 됩니다. 이 둘은 비슷하지만 약간의 차이점이 있습니다. git pull은 merge를 자동진행하는 특징을 가지는데, 이 것은 다음 글에 설명하도록 하겠습니다.
먼저, 다음과 같이 로컬저장소(내 PC)에 폴더를 3개 만들어 진행해보겠습니다.
helloworld_01 : Terminal을 이용하여 git clone
helloworld_02 : VScode를 이용하여 git clone
helloworld_03 : Terminal을 이용하여 git pull
Terminal로 git clone
△ 1. github에서 복제하고싶은 레파지토리의 주소를 복사합니다. 긴 네모상자의 주소를 드래그해서 복사하거나, 화살표 부분의 버튼을 누르면 자동 복사됩니다.
△ 2. helloworld_01 폴더를 열고 들어가 Terminal을 엽니다. 그리고 다음과 같은 명령을 입력합니다.
$ git clone [복사한 레파지토리 주소]
그러면 원격저장소에 있는 README.md 파일이 내 PC로 들어온 것을 알 수 있습니다.
VScode로 git clone
△ 1. VScode 홈화면의 Clone Git Repository를 누릅니다.
△ 2. 활성화된 상단 바에 복제할 리포지토리 URL을 복붙합니다.
△ 3. 복제를 진행할(리포지토리 대상으로 선택할) 폴더를 선택합니다.
△ 4. 요렇게 신뢰하십니까? 나오면 Yes 누르시면 됩니다.
△ 5. 그러면 내가 고른 파일에 해당 리포지토리가 복제된 것을 확인할 수 있습니다.
Terminal로 git pull
git clone은 원격저장소의 리파지토리를 일방적으로 로컬저장소 폴더로 옮기는 명령입니다. git clone만을 위해서는 두 저장소를 연결(remote)하지 않아도 됩니다.
반면, git pull을 하기 위해서는 로컬저장소를 원격저장소와 연결(remote)해줘야 합니다. (remote : 로컬저장소 <-> 원격저장소)
$ git remote -v
// 현재 연결된 원격저장소를 알려줌. (아무 것도 안 뜨면, 연결되지 않은 것.)
$ git remote add origin [리파지토리 URL]
// 원격저장소 리파지토리와 연결.
$ git remote rm origin
// 연결된 원격저장소 연결 해제.
△ 1. git pull할 폴더를 엽니다.
△ 2. git remote 후 git pull 해줍니다.
맨 처음 git remote 가 안된 이유는 초기화가 안됐기 때문입니다(.git 폴더 없음) git init을 해줍시다!
그 뒤 해당 리포지토리 URL로 remote하고 다음 명령어를 쳐주면
$ git pull origin master
해당 리포지토리의 파일이 로컬저장소로 들어옴을 확인할 수 있습니다.
※ git clone 또한 나중에 로컬저장소 -> 원격저장소로 파일을 이동하기 위해서는 remote가 필요합니다.
// type 1
function foo() {
// ...
}
// type 2
const foo = function() {
// ...
};
// type 3
const foo = function bar() {
// ...
};
: Naver에서는 함수 스타일에 대해 차별을 두지 않지만, Airbnb의 경우 type3의 명명된 함수식을 권장합니다.
▽ 7.2 즉시 실행함수는 함수를 괄호로 감싼다.
// bad
!function()) {
console.log("Welcome to the Internet. Please follow me.");
}();
// bad - Airbnb권장
(function() {
console.log("Welcome to the Internet. Please follow me.");
}());
// good - Naver권장
(function() {
console.log("Welcome to the Internet. Please follow me.");
})();
: 즉시 실행함수(IIFE, Immediately Involked Function Expression)은 단 한번 호출되며, 재호출이 불가한 함수입니다.
IIFE를 사용하는 이유는 '전역변수 사용을 억제하기 위해서'입니다.
IIFE를 (괄호)로 감싸는 이유는 함수 객체를 생성하기 위함인데, 구체적인 이유는 다음과 같습니다.
보통 IIFE는 익명함수를 쓰는 것이 일반적인데, JavaScript엔진이 함수이름을 생략할 수 없어 에러가 발생됩니다.
함수이름을 넣더라도 JavaScript엔진이 암묵적으로 세미콜론(;)을 자동 삽입해 함수선언문이 끝나는 위치에 세미콜론이 추가돼 에러가 발생됩니다.
세미콜론이 추가되더라도 세미콜론 다음의 괄호는 함수 호출 연산자가 아닌 그룹 연산자로 해석돼 에러가 발생됩니다.
해당 내용에 대해서는 추후 즉시 실행함수에 대해 따로 정리해보겠습니다.
▽ 7.3 함수 이외의 블록(if, while 등) 안에서 함수를 선언하지 않는다.
// bad
let i;
for (i = 10; i; i--) {
(function() {
return i;
})();
}
// bad
while(i) {
let a = function() {
return i;
};
a();
}
// good
const a = function() {};
let i;
for (i = 10; i; i--) {
a();
}
: browser마다 함수 외의 블록 내에 있는 함수를 모두 다르게 해석하기 때문입니다.
▽ 7.4 Airbnb와 다르게 block 내의 함수선언에 대해서는 별도로 가이드 하지 않는다.
이하 Airbnb 내용
// Naver가 아닌 Airbnb 내용입니다.
// bad
if (currentUser){
function test(){
console.log('Nope');
}
}
// good
let test;
if (currentUser){
test = () => {
console.log('Yup');
}
}
▽ 7.5 절대로 파라미터에 arguments를 지정하지 않는다. 이것은 함수 스코프에 전해지는 arguments 오브젝트의 참조를 덮어버린다.
// bad
function nope(name, options, arguments) {
// ...stuff...
}
// good
function yup(name, options, args) {
// ...stuff...
}
: JavaScript 함수에는 '인수객체(arguments)'라는 '내장객체'가 있습니다. 인수객체에는 함수가 호출될 때 사용된 인수 배열이 포함됩니다. 즉 이미 내장객체에 arguments가 있으니 파라미터로 쓸 수 없다는 뜻입니다.
▽ 7.6 절대 arguments를 이용하지 않는다. 대신에 rest 파라미터(...)를 이용한다.
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join("");
}
// good
function concatenateAll(...args) {
return args.join("");
}
: rest 파라미터는 원하는 값에 대해 명확히 표현할 수 있습니다. 하지만 인수객체(arguments)를 쓴다면 오류 유발 가능성이 커집니다.
▽ 7.7 함수의 파라미터를 재정의하지 않는다. 대신 default 파라미터를 이용한다.
// really bad
function handleThings(opts) {
opts = opts || {};
// ...
}
// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
: default parameter(기본 매개변수)는 함수에 전달된 파라미터 값이 'undefined'거나 '값이 없을 때, 초기화 설정된 값'을 의미합니다.
'void'은 관용적으로 사용하는 JavaScript 연산자입니다.
'undefined' 원시값을 얻기 위해 void 0 또는 void(0)을 사용합니다.
void는 오버라이드 되지 않고 void 0은 항상 undefined를 리턴합니다.
undefined는 예약어가 아니라서 어떤 값이든 될 수 있습니다. (ex. var undefined = 0; // 0)
▽ 7.8 side effect를 유발하는 default 파라미터의 이용을 피한다.
var b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
▽ 7.9 항상 default 파라미터는 뒤쪽에 둔다.
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
▽ 7.10 절대 새 함수를 작성하기 위해 Function constructor를 이용하지 않는다.
// bad
var add = new Function("a", "b", "return a + b");
// still bad
var subtract = Function("a", "b", "return a - b");
: 이런 방식으로 함수를 만들면 취약성을 여는 eval()과 유사한 문자열처럼 처리됩니다. eval()에 관한 글 보기
▽ 7.11 익명함수는 function과 괄호 사이에 공백이 없다. 기명 함수(named function)는 함수 이름과 괄호 사이에 공백이 없다. async arrow function인 경우 async와 arrow function 사이에 공백이 있다.
// bad
const f = function () {};
const g = function a (){};
const h = async(v,i) => {};
// good
const x = function() {};
const y = function a() {};
const z = async (v,i) => {};
: 이는 코드를 일관성 있게 보이게 하며, 이름을 추가하거나 제거할 때 공백을 추가하거나 제거할 필요가 없어 관리가 편합니다.
Naver에서는 async와 arrow function 사이에 공백도 추가했네요.
▽ 7.12 가급적 mutate parameter는 사용하지 않는다. 하지만, 필요에 의해서는 주의하여 사용한다.
// 권장하지 않음.
function f1(obj){
obj.key = 1;
}
: 함수의 파라미터로 전달된 개체를 변형(mutate)하면, caller의 변수에 side effects가 발생할 수 있기 때문입니다.
▽ 7.13 파라미터를 재할당하지 않는다. 단, 파라미터의 속성에 대해서는 재할당이 가능하다.
// bad
function f1(a) {
a = 1;
// ...
}
// bad
function f2(a) {
if (!a) { a = 1; }
// ...
}
// good
function f3(a) {
const b = a || 1;
// ...
}
// good
function f4(a) {
if (!a) { a.b = 1; }
// ...
}
: Airbnb에서는 f4 함수에서 보이는 파라미터 속성값의 재할당 또한 금하고 있습니다.
▽ 7.14 가변함수를 호출할 때는 spread 연산자(...)를 사용한다.
// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);
// good
const x = [1, 2, 3, 4, 5];
console.log(...x);
// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));
// good
new Date(...[2016, 8, 5]);
: spread 연산자를 사용하면, 더 깔끔하고 문맥을 제공하지 않아도 됩니다.
▽ 7.15 함수의 정의가 멀티라인인 경우, 오브젝트와 같은 스타일 가이드를 따른다.
// bad
function foo(bar,
baz,
quux) {
// ...
}
// good
function foo(
bar,
baz,
quux,
) {
// ...
}
// bad
console.log(foo,
bar,
baz);
// good
console.log(
foo,
bar,
baz,
);
예상하다시피 JavaScript Function에 대한 컨벤션은 길고, 사전지식이 많이 필요한 것 같습니다. 위 내용에서 궁금한 점도 많이 생겼으니 추후 자세하게 다뤄볼 주제들도 많네요. 다음 주제는 Arrow Function(화살표 함수)입니다.
문자열은작은 따옴표(' '), 큰 따옴표(" "), 백틱(` `)으로 묶을 수 있습니다.- mac 에서 백틱은 [Opt + 원화] 입니다.
특수문자를 사용할 때는escape문자( \ )를 사용합니다.
Strings
▽ 6.1 따옴표는 쌍따옴표를 사용한다. 이스케이프한 경우는 예외로 홑따옴표를 사용할 수 있다.
// bad
var key = 'naver';
var obj = {
'key': '1'
}
// good
var key = "naver";
var obj = {
"key": "1"
}
▽ 6.2 문자열은 100자를 넘지 않는다. 100자가 넘는 긴 물자열인 경우 줄바꿈시 escape 문자()를 사용하지 않는다. escape문자 대신 +연산자를 이용한다. ES6의 template strings가 사용 가능한 환경에서는 6.3룰을 적용한다.
// bad
const 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.';
// good
const 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.";
▽ 6.3 프로그램에서 문자열을 생성하는 경우는 문자열 연결이 아닌 template strings를 이용한다.
// bad
function sayHi(name) {
return "How are you, " + name + "?";
}
// bad
function sayHi(name) {
return ["How are you, ", name, "?"].join();
}
// good
function sayHi(name) {
return `How are you, ${name}?`;
}
: ES6에서는템플릿 리터럴이라는 새로운 문자열 표기법을 도입했습니다.
따옴표와 다르게 백틱( ` )을 이용합니다. -> 연산자 없이 간단히 문자열을 삽입할 수 있습니다.
escape를 사용하지 않아도 됩니다. ex. \n
문자열 내부에 포함식을 사용할 수 있습니다. 위 예제와 같이.
멀티라인도 가능합니다.
▽ 6.4 절대로 eval()을 사용하지 않는다.
: eval()이라는 JavaScript의 내장함수는 사용을 절대 금하고 있습니다. 이유가 뭘까요?
eval()은 JS파일로부터 인자로 받은 코드 중 문자열이 있으면 이를 수치화 합니다.(문자열->숫자)
이 때, 인자로 받은 코드를caller의 권한으로 수치화합니다. 관리자 권한으로 실행과 같은 느낌이죠.
위의 이유로 바이러스에취약하고, 제 3자가 eval()이 호출된 위치의 스코프 추적이 가능합니다.
JS 파싱단계에서 JS파일의 문자열을 발견하면 수행합니다.
위 단계에서 인터프리터를 사용하여 다른 대안보다느리고 비효율적으로 동작합니다.
결국 eval()이 취약하다는 점에서 사용을 금하고 있는 것 같습니다. (아마도!)
▽ 6.5 문자열에 불필요한 escape 문자를 사용하지 않는다.
// bad
const foo = "\'this\' \i\s \'quoted\'";
// good
const foo = "\"this\" is 'quoted'";
const foo = `my name is '${name}'`;
문자열 또한 그렇게 많이 언급할 부분은 없는 것 같습니다. 그래도 eval()을 사용하지 않아야겠다! 정도 얻은 것 같습니다.
추후 escape나 백틱, string 내장함수들에 대해서도 정리하는 시간을 가져야겠습니다.
네이버 컨벤션은 Destructuring에 대해 Airbnb와 다르게 별도의 가이드를 제공하지 않습니다. 하지만 Airbnb의 Destructuring conventions에도 좋은 점이 많이 보여 정리하였습니다.
JavaScript의 Destructuring 개념에 대해 알고싶으시다면이전 글을 참고해주시기 바랍니다.
Airbnb Conventions
▽ 5.1 object의 여러 프로퍼티에 접근하거나 사용하기 위해 destructuring을 사용합니다.
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
: Destructuring을 진행하지 않고 각각의 변수를 선언합니다.
// good
function getFullName(user) {
const { firstName, lastName } = user;
return `${firstName} ${lastName}`;
}
// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
: Destructuring의 다음과 같은 장점을 사용하기 위해서입니다.
해당 프로퍼티에 대한 임시 참조를 생성합니다 : 해당 프로퍼티 사용 시, 블록 전체를 읽어낼 필요가 없어 속도가 향상됩니다.
개체에 대한 반복적인 접근(Access)를 방지합니다 : 반복적인 엑세스는 더 많은 반복적인 코드를 생성해 실수를 유발합니다.
▽ 5.2 배열 Destructuring을 사용합니다.
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
▽ 5.3 반환 값이 여러개라면, Array Destructuring보다 Object Destructuring이 권장됩니다.
// bad
function processInput(input) {
// then a miracle occurs
return [left, right, top, bottom];
}
// 이 경우 반환 데이터의 순서를 고려해야됩니다.
const [left, __, top] = processInput(input);
// good
function processInput(input) {
// then a miracle occurs
return { left, right, top, bottom };
}
// 원하는 데이터만 가져올 수 있습니다.
const { left, top } = processInput(input);