Monthly Archives: January 2016

[ES6] var, let 그리고 const

지금까지 Javscript 에서 변수를 선언하는 방법은 var 를 이용하는 것 뿐이었습니다. 물론 Javascript 특성상 var 없이 변수를 선언하는 것도 가능하지만 부작용이 심각하기 때문에 실무에서는 적용하지 않는 것이 보통입니다. 일이 아니더라도 var 선언 없이 변수를 사용하는 것은 정신건강에 좋지 않기 때문에 절대로 사용하지 말 것을 강력하게 권장합니다.

본론으로 넘어가서, ES6로 넘어오면서 let과 const라는 새로운 선언방법이 생겼습니다. 새롭게 탄생한 let와 const는 도대체 어떤 녀석이며, 어떤 특징이 있는지 한번 짚어보고자 합니다.

1. 이미 선언 하셨는데요?

var 는 상당히 너그러운 친구입니다.

var foo = 'bar1';
var foo = 'bar2';

같은 이름의 변수를 두번 선언하고 있습니다. foo라는 변수에는 ‘bar1′ 이라는 문자열이 할당되었다가 다음 선언에서 ‘bar2’라는 변수가 할당됩니다. 별다른 에러도 발생시키지 않습니다.

하지만 let과 const는 엄격합니다.

let foo = 'bar1';
let foo = 'bar2';

// ERROR: Uncaught SyntaxError: Identifier 'foo' has already been declared

앞서 선언한 변수를 다시 선언하게 되면 칼 같은 오류를 발생시킵니다. 규모가 큰 코드에서 버그를 방지 할 수 있는 매우 바람직한 특징입니다.

2. 난 그때 거기 없었어요

뜬금 없이 변수 하나를 호출해 보겠습니다.

console.log(foo);
// Error: Uncaught ReferenceError: foo is not defined

어디에도 존재하지 않는 변수인 foo를 호출하면 당연히 에러가 발생합니다. 하지만 var를 이용해 아래와 같이 변수를 선언하면 조금 재미있는 현상이 발생합니다.

console.log(foo); // undefined

var foo;

더 이상 에러가 발생하지 않습니다. 변수 foo는 값이 정의되지 않는 형(type)인 undefined가 되어 있을 뿐입니다. 선언보다 호출이 먼저 있었음에도 불구하고 이 코드는 정상적으로 작동합니다. 왜 이런 현상이 발생하는지는 Hoisting(호이스팅) 이라는 키워드로 검색해 보시길 바랍니다.

그럼 let과 const는 어떨까요?

console.log(foo);
// Error: Uncaught ReferenceError: foo is not defined

let foo;

호출한 시점에서 변수가 선언되어 있지 않음을 알리는 에러가 발생합니다. 일시적 사각지대(Temporal Dead Zone; TDZ) 라는 개념인데, 개인적인으로는 특정 개념을 정의해서 설명하지 않더라도 let과 const의 동작방식이 직관적이고 자연스럽다고 생각합니다.

3. 이 블록의 변수는 나야!

변수의 유효 범위에 대한 부분입니다. var의 경우에는 Function-scope 라고 합니다. 유효 범위가 함수 단위라는 이야기죠.

var foo = 'bar1';
cosole.log(foo); // bar1

if (true) {
  var foo = 'bar2';
  console.log(foo); // bar2
}

console.log(foo); // bar2

위 코드가 하나의 함수 구문 안에 존재한다고 가정했을 때 if문 밖의 변수 foo와 if문 안의 변수 foo는 동일한 변수가 됩니다. 중복 선언을 했지만 앞서 말한바와 같이 별다른 에러를 발생시키지 않고, 값마저 ‘bar2’로 변경해 버렸습니다.

하지만 let과 const Block-scope 라고 합니다. 유효 범위가 블록, 즉 {}로 감싸지 범위라는 이야기 입니다.

let foo = 'bar1';
console.log(foo); // bar1

if (true) {
  let foo = 'bar2';
  console.log(foo) // bar2
}

console.log(foo); // bar1

위 코드에서는 var를 사용한 경우와는 달리 if문 밖의 foo와 if문 안의 foo는 서로 다른 변수 입니다. 따라서 중복 선언으로 인한 에러도 발생하지 않으며, if문 안쪽에서 선언한 foo의 경우는 if문이 닫히는 시점에서 유효범위가 끝납니다.

그런데 여기서 의문이 생깁니다. if문 안에서 foo를 먼저 호출한 다음 let으로 foo를 다시 선언하게 되면 어떤일이 발생할까요? 일단 if 문에서 단순히 foo를 호출만 해보면

let foo = 'bar1';
console.log(foo); // bar1

if (true) {
  console.log(foo) // bar1
  foo = 'bar2';
  console.log(foo) // bar2
}

console.log(foo); // bar2

정상적으로 호출도되고 값의 변경에도 아무 문제가 없습니다. 그런 foo 호출 이후에 let으로 foo를 선언해 보겠습니다.

let foo = 'bar1';
console.log(foo); // bar1

if (true) {
  console.log(foo);
  // Uncaught ReferenceError: foo is not defined

  let foo = 'bar2';
}

console.log(foo);

foo는 정의되지 않았다는 에러가 발생합니다. 앞에서 말한 임시적 사각지대(TDZ)의 정체가 이것입니다. 어떤 변수가 호출되었을 때 블록 안에 같은 이름의 변수가 없으면 상위 블록에서 선언된 같은 이름의 변수를 호출합니다. 하지만 블록 안에서 let이나 const로 변수 선언이 있었다면 그 이름의 변수는 변수가 선언되기 이전까지 그 블록안에서는 정의되지 않은 변수로 간주되는 것이죠.

4. let과 const는 적절한 관계

그럼 let과 const는 무슨 차이가 있는 걸까요. 일단 이름으로만 봐서는 const는 상수 선언으로 보이는데 말입니다. 실제로 원시형(Primitives type: string, number, boolean, null, undefined)에서 const는 상수로 동작합니다. 따라서 const 로 선언되면 값을 재할당 할 경우 에러가 발생합니다(당연하지만 초기값을 설정하지 않아도 에러가 발생합니다)

const foo = 0;
foo = 1;
// Error: Uncaught TypeError: Assignment to constant variable.

따라서 단순형의 경우 값의 변경이 있는 경우에는 let으로, 상수로 사용하는 경우에는 const로 선언하는 것이 바람직하겠습니다.

하지만, 참조형(Complex type: array, object, function)의 경우 결론부터 말씀드리면 const 로 선언하는 것이 바람직합니다. 참조형은 const로 선언하더라도 멤버값을 조작하는 것이 가능합니다.

const foo = [0, 1];
const bar = foo;

foo.push(2);
bar[0] = 10;

console.log(foo, bar)
// [10, 1, 2] [10, 1, 2]

위의 결과를 보시면 아시겠지만 const bar = foo; 의 선언으로 bar는 foo를 참조합니다. 참조가 아니라 값을 복사(copy)하는 경우에는 array는 … 연산자를 사용하고, object는 assign()함수를 사용합니다.

const arg = [0, 1];
const obj = {foo: 'bar'};

const newArg = [...arg];
const newObj = Ojbect.assign({}, obj);

newArg[0] = 10;
newObj.foo = 'rab';

console.log(arg, obj);
// [0, 1], {foo: 'bar'}

console.log(newArg, newObj);
// [10, 1], {foo: 'rab'}

5. 결론
– ES6 에서는 var는 지양하고 가급적 let과 const를 사용하자
– 원시형에서 변수는 let, 상수는 const로 선언한다
– 참조형은 const로 선언한다

Lavral Elixir로 Bower 모듈 가져오기

레퍼런스
NPM Package: Laravel elixir wiredep
NPM Package: Laravel elixir useref

bower로 설치한 모듈들을 elixir로 관리하는 방법은 여러가지가 있겠지만 여기서는 제가 쓰는 방법을 소개 합니다. 현재 Laravel은 최초 설정된 npm 모듈만 설치되어 있고 아직 bower 모듈은 설치되지 않은 상태로 가정합니다. bower_components 에서 가져오는 모듈 이외에 사용자가 작성한 파일들에 대한 처리는 편의상 생략하도록 하겠습니다.

1. 준비
먼저 루트에 .bowerrc라는 파일을 생성하고 아래의 내용을 입력해 줍니다. bower 모듈은 public/bower_components 디렉토리에 설치하라는 의미죠.

{
  "directory": "public/bower_components"
}

그리고 필요한 elixir 모듈을 설치합니다. 가장 기본적으로 사용되는 모듈은 laravel-elixir-wiredep과 laravel-elixir-useref죠. npm으로 이를 설치합니다.

  npm install laravel-elixir-wirdep laravel-elixir-useref --save-dev

그리고 자신이 사용하고자하는 bower 모듈들을 인스톨합니다.

2. bower_componet 모듈 가져오기
views 디렉토리의 blade 템플릿 파일 중에서 script와 stylesheet를 로드하는 역할을 하는 파일을 불러옵니다. 저는 layout/master.blade.php 라는 파일이네요. 파일을 열고 <head>태그 안쪽에 다음과 같은 내용을 입력합니다.

(주: js를 로드하는 script 태그의 위치는 body 태그가 끝나기 직전에 위치하는 것을 개인적으로 선호하고 그렇게 하는 것이 바람직하다고 생각하지만 여기서는 편의상 head 태그 안쪽에 배치하겠습니다. 또한 프로젝트에 AngularJS를 적용하고 블레이드 템플릿을 사용하는 경우 Angular에서 사용하는 {{ }}는 @를 붙여서 @{{ }} 이런 형태로 사용하여야 합니다. 그런데 Angular보다 html이 먼저 로딩되는 경우 짧은 순간이지만 Angular에서 렌더링하지 않은 상태의 @{{ }} 가 그대로 나타나는 경우가 있습니다. 이 경우에는 부득이하게 Angular 관련 스크립트는 head 쪽에서 로드하는 것이 바람직합니다.)

<!-- bower:css -->
<!-- endbower -->

<!-- bower:js -->
<!-- endbower -->

이제 잠시 gulpfile.js로 넘어와서 스크립트를 작성해보겠습니다.

var elixir = require('laravel-elixir');

// 아까 설치한 두 모듈을 로드합니다.
require('laravel-elixir-wiredep');
require('laravel-elixir-useref');

elixir(function (mix) {
  mix.sass('app.scss')
    .wiredep();
});

mix.sass(‘app.scss’)는 처음부터 작성되어 있는 내용입니다. 여기에 wiredep()을 추가하고 gulp를 돌려봅니다. 그리고 아까 bower로 시작하는 주석문을 달아준 master.blade.php를 열어보면 …

<!-- bower:css -->
  <link rel="stylesheet" href="/bower_components/sweetalert/dist/sweetalert.css" />
  // 이하생략
<!-- endbower -->

<!-- bower:js -->
  <script src="/bower_components/sweetalert/dist/sweetalert.min.js"></script>
  // 이하생략
<!-- endbower -->

신박하게도 주석문과 주석문 사이에 bower로 설치한 js 파일과 css 파일을 로드하는 스크립트가 자동으로 생성됐습니다.

3. 가져온 모듈 빌드하기

여기서 한발 더 낳아가 이 모듈들을 js의 경우에는 vendor.js, css의 경우에는 vendor.css라는 이름으로 병합해보겠습니다. 아까 열어놓은 master.blade.php 를 다시 편집합니다.

<!-- build:css(public) css/vendor.css -->
<!-- bower:css -->
  <link rel="stylesheet" href="/bower_components/sweetalert/dist/sweetalert.css" />
  // 이하생략
<!-- endbower -->
<!-- endbuild -->

<!-- build:js(public) js/vendor.js -->
<!-- bower:js -->
  <script src="/bower_components/sweetalert/dist/sweetalert.min.js"></script>
  // 이하생략
<!-- endbower -->
<!-- endbuild -->

그리고 gulpfile.js를 열어서 편집을 합니다.

var elixir = require('laravel-elixir');

// 아까 설치한 두 모듈을 로드합니다.
require('laravel-elixir-wiredep');
require('laravel-elixir-useref');

elixir(function (mix) {
  mix.sass('app.scss')
    .wiredep()
    .useref({
      src: 'layouts/master.blade.php'
    }, {
      searchPath: 'public'
    });
});

이제 gulp를 실행해보겠습니다. public/js 와 public/css 에 각각 vendor.js, vendor.css가 생성된 것을 알 수 있습니다. 하지만 정작 브라우저에서 개발자 도구를 열어 확인해 보면 vendor 파일을 가져오는 것이 아니라 합쳐지기 전의 bower_components 에 있는 파일들을 로드하고 있음을 확인할 수 있습니다.

4. 마무리

다시 master.blade.php로 돌아가서 약간의 수정을 가해봅니다.

@if(config('app.debug'))
  <!-- build:css(public) css/vendor.css -->
  <!-- bower:css -->
    <link rel="stylesheet" href="/bower_components/sweetalert/dist/sweetalert.css" />
    // 이하생략
  <!-- endbower -->
  <!-- endbuild -->
@else
  <script src="{{elixir('css/vendor.css'}}">
@endif

@if(config('app.debug'))
  <!-- build:css(public) js/vendor.js -->
  <!-- bower:js -->
  <script src="/bower_components/sweetalert/dist/sweetalert.min.js"></script>
  // 이하생략
  <!-- endbower -->
  <!-- endbuild -->
@else
  <script src="{{elixir('js/vendor.js'}}"></script>
@endif

Laravel이 Debug 모드일 때는 bower_components에서 파일을 불러오지만 Debug 모드가 아닐때는 elixir() 핼퍼 함수에서 생성한 경로에서 소스 파일을 가져오게 됩니다. 하지만 elixir() 핼퍼 함수로부터 해싱된 경로를 반환받기 위해서는 gulpfile.js 에서 먼저 해줘야 할 것이 있습니다.

var elixir = require('laravel-elixir');

// 아까 설치한 두 모듈을 로드합니다.
require('laravel-elixir-wiredep');
require('laravel-elixir-useref');

elixir(function (mix) {
  mix.sass('app.scss')
    .wiredep()
    .useref({
      src: 'layouts/master.blade.php'
    }, {
      searchPath: 'public'
    })
    .version(['js/vendor.js', 'css/vendor.css']);
});

이제 gulp를 돌려보겠습니다. Laravel의 Debug 모드는 루트의 .env 파일에서 변경할 수 있는데(config/app.php 가 아니라), 디버그 모드의 설정 여부에 따라 빌드된 vendor 파일을 불러오거나, bower_components 에서 파일을 불러오는 것을 확인 할 수 있습니다. 각 elixir 모듈의 세부적인 옵션들은 상단 링크에서 확인 하실 수 있습니다.

Windows에서 npm 버전업 하기…

npm 으로 laravel-elixir-wiredep을 설치하려고 하니 npm 버전이 낮다며 설치가 안된다. npm 3.0.0 이상이 설치되어 있어야 한다고 하는데 지금 설치되었 있는데 2.x 버전이니 정말 어지간히도 버전업 안했구나라는 자기반성(?)과 함께

npm install -g npm

커맨드를 실행시켰다. 업데이트가 끝나고 다시 wiredep을 설치하려고 보니 같은 에러가 나면서 설치가 되지 않았다. npm -v 로 확인해 보니 여전히 예전 버전이 깔려있다. 중간에 한 삽질은 생략하고 결론만 말하면 nodejs가 설치된 폴더에 가서 npm을 설치해주면 된다. 나의 경우에는 c:\Program Files\nodejs 에 설치가 되어 있으므로 해당 폴더로 이동후 다음 커맨드를 입력한다.

c:\Program Files\nodejs> npm install npm@latest

-g 옵션이 붙지 않은 것에 주의하자. npm -v 로 확인해보니 이번에는 제대로 버전업 되어 있다.

angular-masonry-directive 모듈 imagesLoaded 에러

angular-masonry-directive 라는 모듈이 있다. Masonry 배치를 jQuery 없이 구현할 수 있고 무엇보다도 사용이 무척 간단하다. 그런데 이 directive 모듈을 로드하고 앱을 실행시키면 function undefined 에러가 발생하는데 소스파일을 수정해 주면 말끔히 해결된다. 글을 쓰는 시점을 기준으로 60행을 보면 directive(‘masonryTile’) 로 시작하는 코드가 보인다. 코드를 아래와 같이 수정하자.

}).directive('masonryTile', function () {
  return {
    restrict: 'AC',
    link: function(scope, elem) {
    // 생략
    if (update) {
      // 원래 소스 
      // imagesLoaded( elem.get(0), update);

      // 수정된 소스
      imagesLoaded( elem.eq(0), update);
    }
    if (appendBricks) {
      // 원래 소스
      // imagesLoaded( elem.get(0), appendBricks(elem));

      // 수정된 소스
      imagesLoaded( elem.eq(0), appendBricks(elem));
    }

    // 이하 생략

제대로 작동할 것이다. 하지만, bower를 이용해서 설치한 경우라면 또 다른 에러가 발생한다. Dependency 모듈인 masonry 문제인데 기본 파일이 masonry.js 로 되어있다. 같은 디렉토리에 있는 masonry.pkgd.js 로 바꿔서 로드하면 해결된다. gulp나 grunt를 이용해서 injection 을 자동으로 수행하고 있는 경우라면, bower.json 을 수정하면 된다. bower.json 에 아래 내용을 추가하자.

"overrides" : {
  "masonry": {
    "main": "./dist/masonry.pkgd.js",
    "dependencies": {}
  }
}

gulp나 grunt에 설치된 모듈(보통 main-bower-files 일 것이다)이 메인 파일을 masonry.pkgd.js 로 변경해서 injection 해 줄것이다. 아 근데 정말 사람을 왜 이렇게 괴롭혀요…