프론트 개발을 하면서 웹 상에 돌아다니는 코드를 보면 배열의 마지막에 쉼표를 찍는 경우를 종종 봤었는데 왜 이걸 쓰는지 이유는 모르고 그냥 넘어갔던 것 같다. 의외로 큰 뜻이 있으면 어떡하나 싶어서 찾아봤는데, 그다지 중요한 문제는 아닌 것 같다... 이 글은 MDN에 있는 Trailing Commas를 번역한 자료이다.


Trailing commas

Trailing comma(후행 쉼표)는 새로운 배열 요소, 파라메터, 프로퍼티를 자바스크립트 코드에 추가할 때 유용하다. 만약 새 프로퍼티를 더하고 싶고, 마지막 줄에 쉼표가 있다면 이전의 마지막 줄을 수정하지 않고 새로운 라인을 간단하게 추가할 수 있다. 이것은 버전관리 도구에서의 비교를 명확하게 하고, 코드 편집을 덜 귀찮게 할 것이다.

자바스크립트는 태어날 때부터 리터럴의 후행 쉼표를 허용해줬다. 그리고 ECMAScript 5에서는 객체 리터럴, ECMAScript 2017에서는 함수 파라메터에 후행 쉼표를 추가하는 것이 허용됐다.

그러나 JSON은 후행 쉼표를 허용하지 않는다.



배열, 객체 리터럴의 후행 쉼표

배열

자바스크립트는 배열의 후행 쉼표를 무시한다.

var arr = [
    1,
    2,
    3,
];

arr; // [1, 2, 3]
arr.length; // 3

만약 후행 쉼표가 한 개이상 사용되면 배열에 구멍이 생긴다. 구멍이 있는 배열은 희소(sparse) 하다고 불린다. 조밀한(dense) 배열은 구멍이 없다. Array.prototype.forEach( ) 혹은 Array.prototype.map( )으로 배열을 순회할 때는 배열의 구멍을 건너뛴다.

객체

ECMAScript 5부터는 객체 리터럴에도 후행 쉼표가 유효하다.

var object = {
    foo: "bar",
    baz: "qwerty",
    age: 42,
}; 


함수의 후행 쉼표

ECMAScript 2017은 함수 파라메터 목록에 후행 쉼표를 허용한다.

파라메터 정의

다음의 함수 정의 쌍들은 유효하며 서로 같은 의미다. 후행 쉼표는 함수 정의의 length 프로퍼티 혹은 arguments 객체에 영향을 주지 않는다.

function f(p) {}
function f(p, ) {}

(p) => {};
(p, ) => {};

후행 쉼표는 클래스나 객체의 메서드 정의에도 동작한다.

class C {
    one(a,) {},
    two(a, b,) {},
}

var obj = {
    one(a,) {},
    two(a, b,) {},
};

함수 호출

다음의 함수 호출 쌍들은 유효하며 서로 같은 의미다.

f(p);
f(p, );

Math.max(10, 20);
Math.max(10, 20,);

유효하지 않은 후행 쉼표

함수 파라메터 정의나 함수 호출이 쉼표만을 포함한다면 SyntaxError가 발생한다. 그리고 나머지 매개변수(rest parameter)를 사용할 때 마지막 쉼표는 허용되지 않는다.

function f(,) {} // SyntaxError: missing formal parameter
(,) => {};       // SyntaxError: expected expression, got ','
f(,)             // SyntaxError: expected expression, got ','

function f(...p,) {} // SyntaxError: parameter after rest parameter
(...p,) => {}        // SyntaxError: expected closing parenthesis, got ','


비구조화(destructuring)에서 후행 쉼표

비구조화 할당을 사용할 때는 후행 쉼표가 왼편에서만 허용된다.

// 후행 쉼표로 배열 비구조화
[a, b,] = [1, 2];

// 후행 쉼표로 객체 비구조화
var o = {
    p: 42,
    q: true,
};

var {p, q,} = o;

나머지 요소(rest element)를 사용할 때는 SyntaxError가 발생한다.

var [a, ...b,] = [1, 2, 3];
// SyntaxError: rest element may not have a trailing comma


JSON에서 후행 쉼표

객체의 후행 쉼표는 ECMAScript 5에서 소개됐다. JSON은 ES5 이전의 자바스크립트 문법을 기반으로 하기 때문에, JSON에서는 후행 쉼표가 허용되지 않는다.

두 라인은 모두 SyntaxError가 발생한다.

JSON.parse('[1, 2, 3, 4, ]');
JSON.parse('{"foo" : 1, }');
// SyntaxError JSON.parse: unexpected character
// at line 1 column 14 of the JSON data

JSON을 정확하게 파싱하기 위해 후행 쉼표를 생략해라.

JSON.parse('[1, 2, 3, 4 ]');
JSON.parse('{"foo" : 1 }');


'JavaScript' 카테고리의 다른 글

[JavaScript] 변수 스코프  (0) 2017.04.13
[JavaScript] 생성자 함수  (0) 2017.02.27
[JavaScript] 배열 연결  (0) 2017.01.26
[JavaScript] 동등, 일치 연산자  (0) 2016.10.04

이 글은 EcmaScript5를 기준으로 작성되었습니다.

JavaScript 스코프

변수 스코프?

변수 스코프란, 변수가 선언된 지점을 기준으로 변수에 접근할 수 있는 범위를 말하며, 다른 말로 유효범위라고도 한다. 다른 언어에서는 블록({})을 기준으로 변수 스코프가 형성되는 경우가 있지만, 자바스크립트는 아니다.

if(true) {
    var aa = 1;
}

console.log(aa);

실행결과

> 1

블록 안에서 선언된 변수 aa를 블록 밖에서도 접근이 가능하다.

스코프의 종류

자바스크립트에서 형성될 수 있는 스코프는 두 가지이다.

1. 함수 스코프

말 그대로 함수를 기준으로 스코프가 형성되는 것을 말한다. 함수 내부에서 선언된 변수는 외부에서 접근할 수 없다.

function myFunc() {
    var aa = 1;
}

console.log(aa); // Error!!

반대로 외부의 변수는 내부 함수에서 접근할 수 있다.

var aa = 1;

function myFunc() {
    console.log(aa);
}

myFunc(); // 1

2. 전역 스코프

전역 스코프(global scope)는 함수 밖의 영역을 말한다. 전역 스코프의 변수는 모든 함수 스코프에서 참조할 수 있다.

/* global scope */
var aa = 1;

function myFunc() {
    /* function scope */
    console.log(aa);
}

function anotherFunc() {
    /* function scope */
    console.log(aa);
}

myFunc();
anotherFunc();

실행결과

> 1
> 1

전역 스코프에 선언된 변수는 다른 자바스크립트 파일에서도 접근할 수 있다.

/* index.html */
/*
    <html>
    <head>
        <script src='myModule.js'></script>
        <script src='script.js'></script>
    </head>
    </html>
*/

/* myModule.js */
var a = 1;
console.log(b);

/* script.js */
var b = 2;
console.log(a);

실행결과

> Error! b is not defined
> 1

<script> 태그로 로딩되는 순서에 따라서 파일 사이에 전역 스코프의 접근이 가능해진다. 전역 스코프에 변수를 선언하게 되면 자바스크립트 파일간 영향을 미칠 수 있기 때문에(특히 써드파티 모듈을 사용하는 경우), 신중하게 다뤄야 한다.

호이스팅(Hoisting)

호이스팅(Hoisting, 끌어올림)이란 변수 선언문이 끌어올려지는 현상을 말한다.

/* 호이스팅이 일어나기 전 */
console.log(aa);
if(true) {
    var aa = 1;
}
console.log(aa);

function myFunc() {
    console.log(bb);
    var bb = 2;
    console.log(bb);
}

myFunc();

위의 코드는 변수 호이스팅에 의해 자바스크립트 엔진에서 이렇게 해석된다.

/* 호이스팅이 일어난 후 */
var aa;
console.log(aa);
if(true) {
    aa = 1;
}
console.log(aa);

function myFunc() {
    var bb;
    console.log(bb);
    bb = 2;
    console.log(bb);
}

myFunc();

실행 결과

> undefined
> 1
> undefined
> 2

전역 스코프에 있는 변수의 선언문은 자신이 속한 자바스크립트 파일의 최상단으로, 함수 스코프에 있는 변수의 선언문은 함수 스코프의 최상단으로 끌어올려진다.

주의해야 할 점은 선언문만 끌어올려지고, 할당문은 그대로 있는 것이다. 때문에 첫 번째 aa에 접근할 때는 aa가 아직 초기화되지 않았으므로 undefined이 출력된다.

참고자료


'JavaScript' 카테고리의 다른 글

[JavaScript] Trailing Comma 번역자료  (0) 2017.10.02
[JavaScript] 생성자 함수  (0) 2017.02.27
[JavaScript] 배열 연결  (0) 2017.01.26
[JavaScript] 동등, 일치 연산자  (0) 2016.10.04

자바스크립트 생성자 함수

자바스크립트는 ‘생성자 함수’에 인스턴스에 만들어질 속성과 메서드를 정의하고, ‘생성자 함수’를 이용해서 인스턴스를 생성할 수 있다.

function Person(name) [
    this.name = name;
}

Person은 생성자 함수이다. 자바스크립트의 함수는 반복되는 로직을 모아놓은 함수가 될 수도, 인스턴스의 모양(속성이나 메서드)이 정의된 생성자가 될 수도 있다. 다른 언어에서는 클래스와 생성자가 구분되지만, 자바스크립트에서는 생성자 함수가 클래스의 역할을 한다. 그리고 생성자 함수의 이름은 Person처럼 첫 글자를 대문자로 쓰는 것이 관례이다.

인스턴스를 만들 때는 new 연산자와 함께 생성자를 호출한다.

var tarzan = new Person('Tarzan');
tarzan.name; // 'Tarzan'

주의할 것은 new 연산자가 빠지면 안되는 점이다. 그냥 Person() 으로 호출하면 Person은 일반 함수로서 동작한다. 일반 함수의 this는 기본적으로 글로벌 객체를 참조하며 strict-mode에서는 undefined이 된다.

이어서 메서드의 정의는 생성자 안에서 할 수도 있다. 하지만 비효율적이다.

function Person(name) {
    this.name = name;
    this.describe = function() {
        return 'my name is ' + this.name;
    }
}

new 연산자는 생성자의 this가 새로운 인스턴스를 가르키게 한다. 그리고 this로 참조하는 인스턴스에 속성과 메서드를 추가할 것이다. 위의 예제에서도 Person으로 생성하는 모든 인스턴스에서 name 속성과 describe 메서드를 추가한다. 그런데 name 속성은 인스턴스마다 모두 달라서 인스턴스에 추가되는 것이 맞지만, describe 메서드는 모든 인스턴스에 추가될 필요가 없다. 한 공간에 메서드를 한번만 정의하고, 모든 인스턴스에서 그 공간을 참조할 수 있게 하는 방법이 더 효율적이다.

function Person(name) {
    this.name = name;
}

Person.prototype.describe = function() {
     return 'my name is ' + this.name;
}

var tarzan = new Person('tarzan');
var jane = new Person('jane');

tarzan.describe(); // 'my name is tarzan'
jane.describeS(); // 'my name is jane' 

Person.prototype  객체에 메서드를 추가하면 모든 인스턴스에서 메서드를 공유할 수 있다. 

프로토타입

자바스크립트의 원시 값을 제외한 모든 객체는 코드 상에서 접근할 수 없는 내부 속성인 [[Prototype]] 속성을 가진다. [[Prototype]] 속성은 자신의 프로토타입 객체를 가르킨다. 프로토타입 객체란 자신의 부모 객체를 말한다. 그리고 [[Prototype]] 속성으로 연결된 두 객체의 관계를 프로토타입 관계(혹은 상속 관계)라고 부르며, 하나 이상의 프로토타입 관계는 프로토타입 체인을 형성한다. 그리고 자식 객체에서 동일한 프로토타입 체인에 있는 부모 객체의 속성이나 메서드에 접근할 수 있다.

Person의 인스턴스인 tarzan과 jane에도 역시 [[Prototype]]속성이 있다. 그리고 생성자가 new 연산자와 함께 실행될 때, 새로운 인스턴스인 tarzan과 jane의 [[Prototype]]속성은  Person.prototype 객체를 참조하게 된다. 즉, 인스턴스와 Person.prototype 객체는 프로토타입 관계에 있다. 인스턴스에는 describe 메서드가 직접 선언되어 있지 않지만, 프로토타입 체인을 통해 Person.prototype의 describe 메서드에 접근할 수 있다.

Person.prototype은 이름만 봤을 때 Person의 프로토타입 객체로 착각하기 쉽다. Person의 프로토타입 객체는 생성자 함수이기 이전에 일반 함수이기 때문에 자바스크립트 내장 객체인 Function이며, Person.prototype은 Person 인스턴스들의 프로토타입 객체(인스턴스 프로토타입)이다.

참고

  • 악셀 라우슈마이어의 자바스크립트를 말하다
  • MDN - 객체 지향 자바스크립트 소개
  • 생활코딩 - 생성자와 new


'JavaScript' 카테고리의 다른 글

[JavaScript] Trailing Comma 번역자료  (0) 2017.10.02
[JavaScript] 변수 스코프  (0) 2017.04.13
[JavaScript] 배열 연결  (0) 2017.01.26
[JavaScript] 동등, 일치 연산자  (0) 2016.10.04

자바스크립트 배열 연결하기.

 JavaScript의 Array 객체는 두 개의 배열을 연결할 수 있는 concat 메서드를 제공한다.

var arr1 = [1, 2, 3];
var arr2 = ['a', 'b', 'c'];

var new_arr = arr.concat(arr2);
// arr1 뒤에 arr2를 이은 새로운 배열을 반환한다.

// arr1 : [1, 2, 3];
// arr2 : ['a', 'b', 'c'];
// new_arr : [1, 2, 3, 'a', 'b', 'c'];

 정확히 말하면 두 배열의 모든 요소를 복사한 새로운 배열을 만들고, 기존 배열의 내용은 바뀌지 않는다. 그리고 concat은 세 개 이상의 배열을 연결한 새로운 배열을 만들 수도 있다.

 하지만 굳이 원래 배열들의 형태를 유지할 필요가 없고, 새로운 배열이 선언되는 낭비를 줄이고 싶다면 push와 apply를 이용해서 배열을 연결할 수 있다.

arr1.push.apply(arr1, arr2);

// arr1 : [1, 2, 3, 'a', 'b', 'c'];
// arr2 : ['a', 'b', 'c'];

 push 메서드는 두 개 이상의 인자를 넘겨 배열의 맨 뒤에 붙일 수 있다.

arr1.push(4, 5);

// arr1 : [1, 2, 3, 4, 5];

 apply 메서드는 수신자 함수에서 참조하게 될 this값을 첫 번째 인자로 넘기고, 수신자 함수로 넘어갈 인자들을 배열 형태로 넘길 수 있다.

arr1.push.apply(arr1, arr2)

// 아래와 동일한 방법이다.
arr1.push('a', 'b', 'c');


 

'JavaScript' 카테고리의 다른 글

[JavaScript] Trailing Comma 번역자료  (0) 2017.10.02
[JavaScript] 변수 스코프  (0) 2017.04.13
[JavaScript] 생성자 함수  (0) 2017.02.27
[JavaScript] 동등, 일치 연산자  (0) 2016.10.04

 자바스크립트의 동일 연산자에는 두 가지가 있다.

  • 관대한 동등/비동등 연산자 "==" "!="
  • 엄격한 일치/불일치 연산자 "===" "!=="

책에서는 동등 연산자는 너무 많은 값을 같다고 판단하므로, 일치 연산자를 사용할 것을 권장하고 있다.

그렇다면 어떤 값들을 동등 연산자로 비교했을 때, 기대한 것과 다르게 값이 나올까 궁금했다.

그리고 Stack Overflow에서 관련된 질문을 찾았고, 가장 많은 명성(?)을 얻은 답변은 다음과 같다.



 일치 연산자  ===  는 타입 변환이 안되는 것을 제외하고 동등 연산자  ==  와 동일하게 동작하며, 일치하는 것으로 간주되려면 타입이 항상 같아야 합니다.

 동등 연산자  ==  는 모든 필수적인 타입 변환을 한 후에 동등함을 비교할 것입니다. 일치 연산자  === 는 타입 변환을 하지 않기 때문에, 두 값이 같은 타입이 아니면  === 는 단순히 false를 반환합니다. 둘 다 속도는 같습니다.

 더글라스 크록포드(JSON의 아버지!)의 JavaScript: The Good Parts를 인용해보겠습니다.


 자바스크립트는 두 세트의 동일 연산자를 가집니다.  ===  와  !== , 그리고 그들의 나쁜 쌍둥이  ==  와  != . 좋은 것들은 당신이 예상하는대로 동작하는 것입니다. 만약 두 개의 피연산자가 동일한 타입, 동일한 값이면  ===  는 true를 만들고,  !== 는 false를 만듭니다. 나쁜 쌍둥이는 피연산자가 같은 타입일 때 올바르게 동작하지만, 다른 타입이라면 그들은 피연산자의 값을 마음대로 하려 할겁니다. 그들의 행동으로 형성된 법칙은 복잡하고 기억하기 어렵습니다. 여기에 몇 가지 흥미로운 케이스가 있습니다.


"" == '0' //false
0 == "" //true
0 == '0' //true

false == 'false' //false
false == '0' // true

false == undefined //false
false == null //false
null == undefined // true

'\t\r\n' == 0 // true

 타당성이 부족한 것이 놀랍습니다. 내 조언은 나쁜 쌍둥이들을 절대 사용하지 말라는 겁니다. 대신, 항상  ===  와  !== 를 사용하세요. 위의 모든 비교들은  ===  로 비교하면 false를 만듭니다.

 레퍼런스 타입에 대해서는  ==  와  ===  는 특별한 경우를 제외하고, 서로 일관성을 갖고 동작합니다.


var a = [1,2,3];
var b = [1,2,3];

var c = {x: 1, y: 2};
var d = {x: 1, y: 2};

var e = "text";
var f = "te" + "xt";

a == b // false
a === b // false

c == d // false
c === d // false

e == f // true
e === f // true


"abc" == new Striing("abc") // true
"abc" === new String("abc") // false

 여기  ==  연산자는 두 객체의 값을 검사하고, true를 반환하지만, 그들이 같은 타입인지 보는  ===  는 false를 반환합니다. 무엇이 맞을까요? 그것은 정말 당신이 비교하려는 것에 따라 달렸습니다. 나의 조언은, 질문을 완전히 돌아가서, String 생성자를 문자열 객체를 생성하는데 사용하지 말라는 것입니다.


'JavaScript' 카테고리의 다른 글

[JavaScript] Trailing Comma 번역자료  (0) 2017.10.02
[JavaScript] 변수 스코프  (0) 2017.04.13
[JavaScript] 생성자 함수  (0) 2017.02.27
[JavaScript] 배열 연결  (0) 2017.01.26

+ Recent posts