top bar

글 목록

레이블이 javascript인 게시물을 표시합니다. 모든 게시물 표시
레이블이 javascript인 게시물을 표시합니다. 모든 게시물 표시

2018년 7월 3일 화요일

[Javascript] Code Snippet - 객체로 Query String 만들기


Javascript 객체를 Query String으로 변환해보자.
Arrow Function을 인자로 받는 배열 고계함수를 사용한다.
buildQueryString(parameters) {
  return encodeURIComponent(
    Object.keys(parameters)
    .map(key => key + '=' + parameters[key])
    .join('&')
  );
}
결과 Query String을 'encodeURIComponent' 로 감싸는 것을 잊지 말자.
개발하다가 남겨두면 좋을 것 같아 메모.

2017년 7월 16일 일요일

[Javascript] 함수 호출 방법과, this 스코프의 설정

개요



오늘은 자바스크립트에서 함수를 호출하는 방식을 정리하려고 한다.

각각 함수호출 방법과 그 방법을 사용할때 'this' 객체는 서로 상이하기 때문에, 잘 알아두고 사용해야 유효범위(이하 '스코프')의 혼란을 예방할 수 있다.


함수는 '객체'다



놀랍게도 자바스크립트 에서의 함수는 '객체' 다. 정확히 말하면 값으로 취급 될 수 있는 '1급 객체' 다. 이 말 뜻은, 함수 자체에도 속성을 가질 수 있다는 것이며, 그 속성은 또 다른 함수가 될 수 있다는 말이다.

따라서 함수는 아래와 같이 '그냥' 호출 할 수도 있지만,

function func() {
    // do something
}

func();

아래처럼 함수 객체의 속성 메서드인 'call', 'apply', 'bind' 로도 호출 될 수 있다.

func.call(...);
func.apply(...);
func.bind(...);


함수 호출과 'this'



1) 기본

아래처럼 일반 함수 호출에서의 'this' 는 'window'(머리객체) 이다. ('nodejs'의 경우는 'global')

function func() {
    console.log(this); // window
}

하지만 'use strict' 를 사용하면 undefined 이다.

function func() {
    'use strict'
    console.log(this); // undefined
}


2) 객체 속성 으로서의 함수

하지만 객체에 속한 '메서드' 라면, 얘기가 조금 달라진다.
아래에서 'this' 는 객체 자신이다.

const someone = {
  name: 'Someone',
  sayHello: function(otherName) {
    console.log(this.name + ' says hello to ' + otherName);
  }
};

someone.sayHello('thomson'); 
// 결과 > 'Someone says hello to thomson'


하지만 위에서 언급한 call, apply 메서드를 사용하면, 첫번째 파라메터로 전달되는 객체를 해당 함수 스코프에서의 'this' 로 할당 할 수 있다. 아래와 같다

const alex = {
    name: 'Alex'
};

someone.sayHello.call(alex, 'thomson'); 
// 'Alex says hello to thomson'

someone.sayHello.apply(alex, ['thomson']); 
// 'Alex says hello to thomson'

첫번째 파라메터로 'alex' 객체를 넘겨주었다. 때문에 'sayHello' 메서드 내부의 'this'는 'alex' 객체이다.
따라서 위와 같은 결과가 출력된다

하지만 'bind' 메서드는, 위의 call이나 apply와는 조금 다르게 동작한다.

const bound = someone.sayHello.bind(alex, 'thomson'); 
bound();
// 'Alex says hello to thomson'

'bind' 메서드는 특정 함수의 'this'와 파라메터를 설정한 뒤, 해당 함수를 반환한다.
따라서 bound() 와 같은 방식으로 파라메터를 넘기지 않고 호출해도 call, apply와 같은 결과가
출력 되는 것이다.

또 한가지, bind 메서드는 아래와 같이도 동작한다.

function func(x, y) {
    return x + y;
}

const bound = func.bind(null, 10);
console.log(bound(20)); // 30

bind 메서드의 첫번째 파라메터로 특정 객체를 함수 내부의 this 로 설정할 수 있지만 위의 예제에선 필요 없으므로 'null' 을 넘겼다. 그리고 두번째 파라메터로 10을 넘겼는데, 이는 'func' 함수의 첫번째 파라메터인 'x' 값을 고정 시키겠다는 의미이다. 따라서 'bound(20)' 의 결과는 '30' 이 된다.


2017년 7월 5일 수요일

[Javascript] 생성자의 이해

시작에 앞서 잡설..



 과거 웹개발을 공부할때나, 아니면 커리어 초기에 개발업무를 진행 할때 '자바스크립트' 라는 언어는 그저 'onclick' 따위의 이벤트를 핸들링 하는 것과 Ajax를 이용한 비동기 요청을 구현하는 용도로 밖에 사용하지 않았다. 좀 더 활용했다라고 한다면 'JQuery UI' 를 이용해 캘린더 붙이는 정도...?

 하지만 새로운 회사로 이직하여 시작한 프로젝트는 npm 환경을 기반으로 하는 그야말로 트렌디한 온갖 자바스크립트 도구, 라이브러리, 프레임워크의 향연이었다. 또한 ECMAScript6 이후부터의 문법적 발전을 통해서, 객체의 해체할당, let과 const의 block scope, class 선언 방식 등장, 달라진 모듈 import와 export 문법 등등... 여러가지 새로운 개념을 익혀야 했다.

이러한 변화에 흐름에 당당히 맞서서(!?) 나는 요즈음 나비책, 부엉이책, 이름을알수없는새책 등을 탐독하고 있다 (코뿔소책은 그 두께에 엄두가 안나서 pass.. 그래도 필요한 부분은 틈틈히 보고 있다..)
모두 적어도 몇년전에 출판된, 비교적 과거의 문법과 이론을 담고 있는 책들이다.

그렇다고 변화에 흐름을 미련하게 역행 하겠다는 건 절대 아니다. 다만 우리가 역사를 연구 함으로 미래에 대한 통찰력을 얻는 것처럼, 자바스크립트의 과거 이론을 통해서 현재의 변화를 더 잘 이해하고 싶은 것 뿐이다.

생성자에 관해 정리하려다가 잡설이 길어졌다. 정리에 앞서, 자바스크립트 생성자에 대한 더 깊은 이해를 할 수 있도록 도와주신 실력자 'javarouka'님께 감사 드린다.


생성자 문법 정리



자바스크립트의 생성자는 다름아닌 '함수' 다. 자바의 경우 생성자는 일반 메소드와 시그니쳐가 비슷하지만, 자바의 생성자는 메소드처럼 호출 할 수 없다. 반드시 'new' 키워드와 함께 사용해야 한다.

하지만 자바스크립트의 생성자 함수는 'new'와 함께 사용할 수도, 그냥 일반적인 함수처럼 사용할 수도 있다. 아래의 예제 처럼 말이다.

function MyConstructor(prop) {
    this.prop = prop;
}

var obj1 = MyConstructor("prop value");
var obj2 = new MyConstructor("prop value");

하지만 obj1과 obj2 할당된 값은 아래와 같은 차이가 있다.

console.log(obj1) // undefined
console.log(obj2) // MyConstructor
console.log(obj2.prop) // "prop value"

obj1은 값이 정의되지 않은 'undefined' 값이고, obj2는 정상적으로 MyConstructor의 객체와 prop 속성값인 "prop value" 문자열이 할당 된 것을 볼 수 있다.

'new' 키워드와 함께 사용된 생성자 함수는 묵시적으로 객체를 생성하여 'this' 에 바인딩한다. 그리고 동적으로 속성을 정의한 뒤에 역시나 묵시적으로 'this' 를 반환한다. 따라서 obj2에는 정상적인 객체가 생성되어 메모리에 할당 되는 것이다.

하지만 생성자를 'new' 키워드와 사용하지 않으면 일반 함수 처럼 호출이 되고, 이때, 'this'는 브라우져의 자바스크립트 실행 환경에서 '머리객체(Head Object)'인 'window' 이다. 따라서  MyConstructor 생성자를 일반 함수로서 호출 하게 되면 머리객체인 'window'에 동적으로 'prop'속성이 생성되며, 아무것도 반환하지 않으므로 obj1는 'undefined'가 된다.

'window' 객체에 'prop' 속성이 생성되었는지 확인해보자.




.
.
.









 'window' 객체의 속성을 나열했더니 추가된 prop 이 보인다

이러한 생성자 함수를 'new'키워드와 사용하지 않을때 주의 사항은, 바로 위의 예제에서 'window' 머리객체에 'prop'이 추가 된 것처럼 절대 전역 유효범위를(global scope)를 어지럽혀선 안된다는 것이다. 이유는 자명하다. 전역 머리객체에 개발자가 의도하지 않은 속성이 추가 될 수 있고, 이는 찾아내기 어려운 버그 발생으로 이어지기 때문이다.


한편, 생성자 함수를 'new' 키워드와 함께 사용한다고 무조건 해당 생성자의 객체를 반환할까?
그것도 아닌것 같다.

function ReturnByObj() {
    this.key = "hello";
    return new Object();
}

function ReturnByString() {
    this.key = "hello";
    return "ret value";
}

const returnByObj = new ReturnByObj();
const returnByString = new ReturnByString();

console.log(returnByObj); // Object 객체
console.log(returnByObj.key); // undefined
console.log(returnByString); // ReturnByString 객체
console.log(returnByString.key); // "hello"

생성자 함수(처럼 보이는) ReturnByObj와 ReturnByString 에 'return' 문을 추가했다. 이 두 함수를 'new' 키워드와 함께 사용한 코드이다.

위의 console 문 주석을 보면 알 수 있듯이 'new' 키워드와 함께 사용해도, return 문의 값이 '객체' 라면,
해당 객체가 반환된다. 'ReturnByObj' 객체가 반환 되지 않는다는 말이다. 하지만 'ReturnByString' 생성자 함수 처럼 원시값(primitive type)을 리턴한다면, 우리가 기대했던대로 해당 생성자내부에서 생성된 'this' 객체('ReturnByString' 객체)가 반환된다.

중요한 사실은, 이러한 문법을 이해해야 생성자 함수를 일반 함수처럼 호출하거나, 일반 함수를 'new' 키워드와 같이 호출하는 등의 오용과 실수를 줄일 수 있다는 사실이다.



2017년 7월 3일 월요일

[Javascript] Closure의 변수 공유 문제

 무언가 난해한 개념으로 이해되는 'lexical scope'(어휘적 유효범위)는, 그냥 '유효범위' 라고 생각해도 상관이 없을 듯 싶다. (이하 유효범위라 하겠다) 자바스크립트는 함수를 실행할때, 해당 함수가 정의되었을 때의 유효범위를 사용하여 실행 한다. 아래의 코드를 보자.

function funcCreator() {
  const x = 10;
  function lexicalScopeFunc() {
    console.log(x);
  };
  return lexicalScopeFunc;
}

funcCreator()();

위의 코드는 'funcCreator' 함수를 실행하여 'lexicalScopeFunc' 가 반환되게 하고, 반환된 함수를 즉각 호출한다.  해당 함수를 호출 하면 console 창에 참조 'x' 의 값인 '10'이 출력된다. 

정작 'lexicalScopeFunc' 함수'만' 본다면, 참조 'x'는 해당 함수의 scope 에서 정의 되었다. 그런데 어떻게 console창에 참조 'x'의 값이 정상적으로 출력이 되는 것일까? 바로 해당 lexicalScopeFunc 함수가 실행될때 해당 함수가 정의 되었던 유효범위, 즉 'funcCreator' 함수의 scope 를 이용하여 실행하기 때문이다. 다시말해서, lexicalScopeFunc 함수는 자신이 선언된 funcCreator 함수 내부의 유효범위와 연결된 채 존재하는 함수인 것이다.

바로 이러한 개념을 '클로져(Closure)' 라고 한다.

이러한 클로져 개념에는 아주 고전적으로 논의되는 문제가 있는데, 바로 '변수 공유' 문제이다. 아래의 코드를 보면 문제가 무엇인지 명확해 진다.

var closureList = [];
for(var i = 0; i < 10; i++) {
  closureList.push(() => {
    console.log(i);
  });
}

for(var idx in closureList) {
    closureList[idx]();
}

closureList 배열안에 삽입한 요소는 각각 클로져 함수인데, 자신이 선언된 유효범위의 'i' 값을 출력한다. 때문에 위의 코드를 실행 했을때 우리는 console창에 0부터 9까지 출력되는 것을 기대할 것이다.

그러나 정작 코드를 실행해 보면 '10' 이 10번 출력된다. 이유를 살펴보자면, closureList 안에 삽입된 각각의 클로져 함수가 해당 유효범위(여기서는 전역범위가 될것이다)의 'i' 변수를 공유하기 때문이다. for문이 종료될때 변수 'i' 는 값이 10이 되고, 각각 클로져는 최종 값이 10인 변수 'i'를 출력하기때문에 그러한 결과가 나오는 것이다.

이를 해결하려면 어떻게 해야할까?

우선은 IIFE(Immediately Invoked Function Expressions)를 사용하여 유효범위를 강제한다. 
아래와 같은 코드이다.

var closureList = [];
for(var i = 0; i < 10; i++) {
  closureList.push(
    (function() {
      var localVar = i;
      return function() {
        console.log(localVar);
      };
    })()
  );
}

for(var i = 0; i < 10; i++) {
    closureList[i]();
}

closureList에 함수 객체를 삽입할때, IIFE를 통해 독립적인 유효범위를 생성하여 그안에서 함수를 반환하여 배열에 삽입한다. 이렇게 되면 IIFE의 유효범위 안에서 'i'는 'localVar' 라는 변수로 바인딩되고, 독립적인 유효범위 안의 변수가 된다.

그러나... 위 코드가 단순한 예제이기에 망정이지, 조금이라도 복잡한 비즈니스 코드로 작성한다면 IIFE는 읽기에 무척 짜증이 날 것 같다.

아주 단순한 해결 방법이 있는데, ECMAScript6 에서 제공하는 'let' 을 이용하여 변수를 선언하는 것이다.
아래의 코드를 다시 보자.

var closureList = [];
for(let i = 0; i < 10; i++) {
  closureList.push(function() {
    console.log(i);
  });
}

for(var idx in closureList) {
    closureList[idx]();
}

차이가 보이는가? closureList 배열에 함수를 삽입하는 for 구문안에서 인덱스를 'let'으로 선언했다. 다들 알겠지만 'var' 선언 방식의 scope는 함수의 유효범위를 사용한다. 하지만 'let'은 대부분의 프로그래밍 언어가 그렇듯이 '블록'의 유효범위이다. 때문에 반복되는 for 구문의 블록이 해당 let 변수의 독립적인 유효범위로 작용하기 때문에 우리가 원하는 0, 1, 2, .. 9 가 출력이 되는 것이다.