Please note, this is a STATIC archive of website developer.mozilla.org from November 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

이 장은 JavaScript의 기본 문법과 변수 선언, 데이터 형 및 리터럴을 다룹니다.

기본

JavaScript는 Java로부터 구문 대부분을 빌려온 것 뿐만 아니라 Awk, Perl 및 Python의 영향도 받았습니다.

JavaScript는 대소문자를 구별하며 Unicode 문자셋을 이용합니다.

JavaScript에서는 명령을 문(statement)이라고 부르며, 세미콜론(;)으로 분리됩니다. 스페이스, 탭, 줄바꿈 문자를 공백이라고 합니다. JavaScript의 스크립트 소스는 왼쪽에서 오른쪽으로 탐색되어 토큰, 제어 문자, 줄바꿈 문자, 주석이나 공백인 입력 요소의 열(sequence)로 바뀝니다. ECMAScript에서도 특정 키워드와 리터럴을 정의하고 문을 끝내는 세미콜론 자동 삽입 규칙(ASI)이 있습니다. 하지만, 항상 세미콜론을 추가해 문을 끝내기를 권합니다. 왜냐하면 그러면 부작용을 막습니다. 더 자세한 사항은, JavaScript의 어휘 문법(lexical grammar)에 관한 레퍼런스를 참고하세요.

주석

주석(Comments)의 구문은 C++ 및 다른 많은 언어와 똑같습니다.

// 한 줄 주석

/* 이건 더 긴,
   여러 줄 주석입니다.
 */

/* 그러나, /* 중첩된 주석은 쓸 수 없습니다 */ SyntaxError */

선언

JavaScript에서 선언은 3가지 방법이 있습니다.

var
변수를 선언. 추가로 동시에 값을 초기화.
let
블록 범위(scope) 지역 변수를 선언. 추가로 동시에 값을 초기화.
const
읽기 전용 상수를 선언.

변수

어플리케이션에서 값에 상징적인 이름으로 변수를 사용합니다. 변수명은 식별자(identifier)라고 불리며 특정 규칙을 따릅니다.

JavaScript 식별자는 문자, 밑줄(_) 혹은 달러 기호($)로 시작해야 하는 반면 이후는 숫자(0-9)일 수도 있습니다. JavaScript가 대소문자를 구분하기에, 문자는 "A"부터 "Z"(대문자)와 "a"부터 "z"(소문자)까지 모두 포함합니다.

ISO 8859-1 혹은 Unicode 문자(가령 å 나 ü)도 식별자에 사용할 수 있습니다. 또한 Unicode escape sequences도 식별자에 문자로 사용할 수 있습니다.

적절한 이름으로는 Number_hits, temp99_name등입니다.

변수 선언

변수 선언은 아래 3가지 방법으로 가능합니다.

  • var 키워드로. 예를 들어, var x = 42. 이 구문은 지역 및 전역 변수를 선언하는데 모두 사용될 수 있습니다.
  • 간단하게 값을 할당합니다. 예를 들어, x = 42 로 선언하면 이 변수는 전역 변수로 지정됩니다. 이 방식은 strict mode의  JavaScript에서 경고를 발생시켜 실행이 안됩니다. 이 방법은 strict mode의 JavaScript에서 사용해서는 안됩니다.
  • let 키워드로. 예를 들어, let y = 13. 이 구문은 블록 범위 지역 변수를 선언하는데 사용될 수 있습니다. 아래 변수 범위 참고하세요.

변수 평가

지정된 초기값 없이 var 혹은 let 문을 사용해서 선언된 변수는 undefined 값을 갖습니다.

선언되지 않은 변수에 접근을 시도하는 경우 ReferenceError 예외가 발생합니다.

var a;
console.log("a 값은 " + a); // "a 값은 undefined"로 로그가 남음.
console.log("b 값은 " + b); // ReferenceError 예외 던짐

undefined를 사용하여 변수값이 있는지 확인할 수 있습니다. 아래 코드에서, input 변수는 값이 할당되지 않았고 if문은 true로 평가합니다.

var input;
if(input === undefined) {
  doThis();
} else {
  doThat();
}

undefined 값은 boolean 문맥(context)에서 사용될 때 false로 동작합니다. 예를 들어, 아래 코드는 myArray 요소가 undefined이므로 myFunction 함수를 실행합니다.

var myArray = [];
if (!myArray[0]) myFunction();

undefined 값은 수치 문맥에서 사용될 때 NaN으로 변환됩니다.

var a;
a + 2; // NaN으로 평가

null 값을 평가할 때, 수치 문맥에서는 0으로, boolean 문맥에서는 false로 동작합니다. 예를 들면,

var n = null;
console.log(n * 32); // 콘솔에 0 으로 로그가 남음.

변수 범위

어떤 함수의 바깥에 변수를 선언하면, 현재 문서의 다른 코드에 해당 변수를 사용할 수 있기에 전역 변수라고 합니다. 만약 함수 내부에 변수를 선언하면, 오직 그 함수 내에서만 사용할 수 있기에 지역 변수라고 부릅니다.

ECMAScript 6 이전의 JavaScript는 block 문 범위가 없습니다. 그래서 오히려, 블록 내에 선언된 변수는 그 블록 내에 존재하는 함수(혹은 전역 범위)에 지역적입니다. 예를 들어서 아래의 코드는 5라는 로그를 남깁니다. x의 범위가 이 경우 if문 블록이 아니라 x가 선언된 함수(나 전역 문맥)이기 때문입니다.

if (true) {
  var x = 5;
}
console.log(x); // 5

ECMAScript 6에 도입된 let 선언을 사용했을 때, 이 동작은 바뀌었습니다.

if (true) {
  let y = 5;
}
console.log(y); // ReferenceError: y is not defined

변수 호이스팅

또 다른 JavaScript 변수의 특이한 점은 예외를 받지 않고도, 나중에 선언된 변수를 참조할 수 있다는 것입니다. 이 개념은 호이스팅(hoisting)으로 알려져 있습니다. 즉 JavaScript 변수가 어떤 의미에서 "끌어올려지거"나 함수나 문의 최상단으로 올려지는 것을 말합니다. 하지만, 끌어올려진 변수는 undefined 값을 반환합니다. 그래서 심지어 이 변수를 사용 혹은 참조한 후에 선언 및 초기화하더라도, 여전히 undefined를 반환합니다.

/**
 * Example 1
 */
console.log(x === undefined); // logs "true"
var x = 3;


/**
 * Example 2
 */
// undefined 값을 반환함.
var myvar = "my value";

(function() {
  console.log(myvar); // undefined
  var myvar = "local value";
})();

위 예제는 아래 예제와 동일하게 볼 수 있습니다.

/**
 * Example 1
 */
var x;
console.log(x === undefined); // logs "true"
x = 3;

/**
 * Example 2
 */
var myvar = "my value";

(function() {
  var myvar;
  console.log(myvar); // undefined
  myvar = "local value";
})();

호이스팅 때문에, 함수 내의 모든 var 문은 가능한 함수 상단 근처에 두는 것이 좋습니다. 이 방법은 코드를 더욱 명확하게 만들어줍니다.

전역 변수

전역 변수는 사실 global 객체의 속성(property)입니다. 웹 페이지에서 global 객체는 window 이므로, windows.variable 구문을 통해 전역 변수를 설정하고 접근할 수 있습니다.

그 결과, window 혹은 frame의 이름을 지정하여 한 window 혹은 frame에서 다른 window 혹은 frame에 선언된 전역 변수에 접근할 수 있습니다. 예를 들어, phoneNumber 라는 변수가 문서에 선언된 경우, iframe에서 parent.phoneNumber로 이 변수를 참조할 수 있습니다.

상수

const 키워드로 읽기 전용 상수를 만들 수 있습니다. 상수 식별자의 구문은 변수 식별자와 같습니다. 문자, 밑줄이나 달러 기호로 시작해야 하고 문자, 숫자나 밑줄을 포함할 수 있습니다.

const prefix = '212';

상수는 스크립트가 실행 중인 동안 대입을 통해 값을 바꾸거나 재선언될 수 없습니다. 값으로 초기화해야 합니다.

상수에 대한 범위 규칙은 let 블록 범위 변수와 동일합니다. 만약 const 키워드가 생략된 경우에는, 식별자는 변수를 나타내는 것으로 간주됩니다.

상수는 같은 범위에 있는 함수나 변수와 동일한 이름으로 선언할 수 없습니다. 예를 들어,

// 오류가 발생합니다
function f() {};
const f = 5;

// 역시 오류가 발생합니다
function f() {
  const g = 5;
  var g;

  //statements
}

데이터 구조 및 형

데이터 형

최신 ECMAScript 표준은 7가지 데이터 형을 정의합니다.

  • 6가지 원시 데이터 형
    • Boolean. true와 false
    • null. null 값을 나타내는 특별한 키워드. JavaScript는 대소문자를 구분하므로, null은 Null, NULL 혹은 다른 변형과도 다릅니다.
    • undefined. undefined 값인 최상위 속성.
    • Number. 42 혹은 3.14159.
    • String. "안녕"
    • Symbol. (ECMAScript 6에 도입) 인스턴스가 고유하고 불변인 데이터 형.
  • 그리고 Object

이 데이터 형이 비교적 적은 양이지만, 어플리케이션에 유용한 기능을 수행할 수 있습니다. 객체함수는 언어의 다른 기본 요소입니다. 객체는 값을 위한 컨테이너, 함수는 어플리케이션이 수행할 수 있는 절차(procedure)로 생각할 수 있습니다.

데이터 형 변환

JavaScript는 동적 형지정(정형) 언어입니다. 이는 변수를 선언할 때 데이터 형을 지정할 필요가 없음을 의미합니다. 또한 데이터 형이 스크립트 실행 도중 필요에 의해 자동으로 변환됨을 뜻합니다. 그래서, 예를 들어, 다음과 같이 변수를 정의할 수 있습니다.

var answer = 42;

그리고 나중에, 동일한 변수에 문자열 값을 할당할 수도 있습니다. 아래와 같이,

answer = "Thanks for all the fish...";

JavaScript는 동적 형지정 언어이므로, 이 할당은 오류 메시지가 발생하지 않습니다.

숫자와 문자열 값 사이에 + 연산자를 포함한 식에서, JavaScript는 숫자 값을 문자열로 변환합니다. 예를 들어, 아래와 같은 문이 있습니다.

x = "The answer is " + 42 // "The answer is 42"
y = 42 + " is the answer" // "42 is the answer"

다른 연산자를 포함한 식의 경우, JavaScript는 숫자 값을 문자열로 변환하지 않습니다. 예를 들면,

"37" - 7 // 30
"37" + 7 // 377

문자열을 숫자로 변환하기

숫자를 나타내는 값이 문자열로 메모리에 있는 경우, 변환을 위한 메서드가 있습니다.

parseInt는 오직 정수만 반환하므로, 소수에서는 사용성이 떨어집니다. 게다가 parseInt를 잘 사용하기 위해서는 항상 진법(Radix) 매개변수를 포함해야 합니다. 진법 매개변수는 변환에 사용될 진법을 지정하는데 사용됩니다.

문자열을 숫자로 변환하는 대안은 +(단항 더하기) 연산자입니다.

"1.1" + "1.1" = "1.11.1"
(+"1.1") + (+"1.1") = 2.2
// 참고: 괄호는 명확성을 위해 추가, 필요한 것은 아닙니다.

리터럴

JavaScript에서 값을 나타내기 위해 리터럴을 사용합니다. 이는 말 그대로 스크립트에 부여한 고정값으로, 변수가 아닙니다. 이 절에서는 다음과 같은 형태의 리터럴을 설명합니다.

배열 리터럴

배열 리터럴은 0개 이상의 식(expression) 목록입니다. 각 식은 배열 요소를 나타내고 대괄호([])로 묶입니다. 배열 리터럴을 사용하여 배열을 만들 때, 그 요소로 지정된 값으로 초기화되고, 그 길이는 지정된 인수의 갯수로 설정됩니다.

아래 예제는 요소가 3개로 길이가 3인 coffees 배열을 만듭니다.

var coffees = ["French Roast", "Colombian", "Kona"];

Note: 배열 리터럴은 일종의 객체 이니셜라이저(initialiizer)입니다. Using Object Initializers 참고.

배열이 최상단 스크립트에서 리터럴을 사용하여 만들어진 경우, JavaScript는 배열 리터럴을 포함한 식을 평가할 때마다 배열로 해석합니다. 게다가, 함수에서 사용되는 리터럴은 함수가 호출될 때마다 생성됩니다.

배열 리터럴은 배열 객체입니다. 배열 객체에 대한 자세한 내용은 ArrayIndexed collections 참고.

배열 리터럴의 추가 쉼표

배열 리터럴에서 모든 요소를 지정할 필요는 없습니다. 만약 잇달아 두 개의 쉼표를 두면, 배열은 지정되지 않은 요소를 undefined로 만듭니다. 다음 예제는 fish 배열을 만듭니다.

var fish = ["Lion", , "Angel"];

이 배열은 값이 있는 두 요소와 빈 요소 하나를 가집니다(fish[0]은 "Lion", fish[1]undefined, fish[2]는 "Angel").

만약 요소 목록을 후행(trailing) 쉼표로 끝낸다면, 그 쉼표는 무시됩니다. 다음 예제에서, 배열의 길이는 3입니다. myList[3]은 없습니다. 목록의 다른 모든 쉼표는 새로운 요소를 나타냅니다.

Note: 후행 쉼표는 구 버전 브라우저에서 오류를 일으킬 수 있고 제거하는 것이 최선입니다.

var myList = ['home', , 'school', ];

아래 예제에서, 배열의 길이는 4이며, myList[0]myList[2]는 값이 빠졌습니다.

var myList = [ , 'home', , 'school'];

아래 예제에서, 배열의 길이는 4이며, myList[1]myList[3]은 값이 빠졌습니다. 마지막 쉼표는 무시됩니다.

var myList = ['home', , 'school', , ];

추가 쉼표의 동작을 이해하는 것은 JavaScript를 언어로서 이해하는데 중요하지만, 코드를 작성할 때는 빠진 요소의 값을 명시적으로 undefined로 선언하는 것이 코드의 명확성과 유지보수성을 높입니다.

불린 리터럴

불린 형은 두 리터럴 값을 가집니다. truefalse.

원시 불린 값 truefalse와 Boolean 객체의 true 및 false 값을 혼동하지 마세요. Boolean 객체는 원시 불린 데이터 형을 감싸는 래퍼(wrapper)입니다. 더 많은 정보는 Boolean을 참고하세요.

정수

정수는 10진, 16진, 8진 및 2진수로 표현될 수 있습니다.

  • 10진 정수 리터럴은 선행 0(zero)이 아닌 숫자열로 이루어집니다.
  • 정수 리터럴에서 선행 0(zero)이나 선행 0o(혹은 0O)은 8진수임을 나타냅니다. 8진 정수는 오직 숫자 0-7만 포함할 수 있습니다.
  • 선행 0x(나 0X)는 16진수임을 나타냅니다. 16진 정수는 숫자 0-9 및 문자 a-f, A-F를 포함할 수 있습니다.
  • 선행 0b(나 0B)는 2진수임을 나타냅니다. 2진 정수는 오직 숫자 0과 1만 포함할 수 있습니다.

다음은 정수 리터럴 예제입니다.

0, 117 및 -345 (10진수)
015, 0001 및 -0o77 (8진수)
0x1123, 0x00111 및 -0xF1A7 (16진수)
0b11, 0b0011 및 -0b11 (2진수)

더 많은 정보는 Lexical grammar reference의 Numeric literals를 참고하세요.

부동 소수점 리터럴

부동 소수점 리터럴은 아래와 같은 부분으로 이루어집니다.

  • 부호("+"나 "-")가 달릴 수 있는 10진 정수,
  • 소수점("."),
  • 소수(또 다른 10진수),
  • 지수.

지수부는 "e"나 "E" 다음에 오며 부호("+"나 "-")가 달릴 수 있는 정수입니다. 부동 소수점 리터럴은 적어도 숫자 하나와 소수점 혹은 "e"(나 "E")가 있어야 합니다.

더 간결하게 설명하면, 구문은 다음과 같습니다.

[(+|-)][digits][.digits][(E|e)[(+|-)]digits]

예를 들면,

3.1415926
-.123456789
-3.1E+12
.1e-23

객체 리터럴

객체 리터럴은 중괄호({})로 묶인 0개 이상인 객체의 속성명과 관련 값 쌍 목록입니다. 문의 시작에 객체 리터럴을 사용해서는 안됩니다. 이는 {가 블록의 시작으로 해석되기 때문에 오류를 이끌거나 의도한 대로 동작하지 않습니다.

아래는 객체 리터럴의 예제입니다. car 객체의 첫째 요소는 myCar 속성을 정의하고 문자열 "Saturn"을 할당합니다. 반면 둘째 요소인 getCar 속성은 function (carTypes("Honda"))을 호출한 결과가 즉시 할당됩니다. 셋째 요소 special 속성은 기존 변수 sales를 사용합니다.

var sales = "Toyota";

function carTypes(name) {
  if (name === "Honda") {
    return name;
  } else {

  }
    return "Sorry, we don't sell " + name + ".";
}

var car = { myCar: "Saturn", getCar: carTypes("Honda"), special: sales };

console.log(car.myCar);   // Saturn
console.log(car.getCar);  // Honda
console.log(car.special); // Toyota

게다가, 속성명으로 숫자나 문자열 리터럴을 사용하거나 또다른 객체 리터럴 내부에 객체를 중첩할 수도 있습니다. 아래 예제는 이 옵션을 사용합니다.

var car = { manyCars: {a: "Saab", "b": "Jeep"}, 7: "Mazda" };

console.log(car.manyCars.b); // Jeep
console.log(car[7]); // Mazda

객체 속성명은 빈 문자열 포함 어떤 문자열도 될 수 있습니다. 속성명이 유효한 JavaScript 식별자나 숫자가 아닌 경우, 따옴표로 묶여야 합니다. 또한 유효한 식별자가 아닌 속성명은 점(.) 속성으로 접근할 수 없습니다. 대신 배열 같은 표기법("[]")으로 접근하고 값을 설정할 수 있습니다.

var unusualPropertyNames = {
  "": "An empty string",
  "!": "Bang!"
}
console.log(unusualPropertyNames."");   // SyntaxError: Unexpected string
console.log(unusualPropertyNames[""]);  // An empty string
console.log(unusualPropertyNames.!);    // SyntaxError: Unexpected token !
console.log(unusualPropertyNames["!"]); // Bang!

ES2015에서, 객체 리터럴은 구성에서 프로토타입 설정, foo: foo 할당을 위한 단축 표기, 메서드 정의, super 클래스 호출 및 식으로 동적인 속성명 계산을 지원하기 위해 확장됐습니다. 그에 따라 객체 리터럴 및 클래스 선언이 함께 더 가까워지고, 객체 기반 설계는 같은 일부 편의기능으로 득을 볼 수 있습니다.

var obj = {
    // __proto__
    __proto__: theProtoObj,
    // ‘handler: handler’의 단축 표기
    handler,
    // Methods
    toString() {
     // Super calls
     return "d " + super.toString();
    },
    // Computed (dynamic) property names
    [ 'prop_' + (() => 42)() ]: 42
};

아래를 참고하세요.

var foo = {a: "alpha", 2: "two"};
console.log(foo.a);    // alpha
console.log(foo[2]);   // two
//console.log(foo.2);  // Error: missing ) after argument list
//console.log(foo[a]); // Error: a is not defined
console.log(foo["a"]); // alpha
console.log(foo["2"]); // two

정규식 리터럴

정규식 리터럴은 슬래시 사이에 감싸인 패턴입니다. 다음은 정규식 리터럴 예제입니다.

var re = /ab+c/;

문자열 리터럴

문자열 리터럴은 큰 따옴표(") 혹은 작은 따옴표(')로 묶인 0개 이상의 문자입니다. 문자열은 같은 형 따옴표, 즉 큰 따옴표 쌍이나 작은 따옴표 쌍으로 구분되어야 합니다. 아래는 문자열 리터럴의 예제입니다.

"foo"
'bar'
"1234"
"one line \n another line"
"John's cat"

문자열 리터럴 값은 문자열 객체의 모든 메서드를 호출할 수 있습니다. JavaScript는 자동으로 문자열 리터럴을 임시 문자열 객체로 변환, 메서드를 호출하고 나서 임시 문자열 객체를 폐기합니다. 또한 문자열 리터럴에서도 String.length 속성을 사용할 수 있습니다.

console.log("John's cat".length)
// 공백을 포함한 문자열 내 심볼 갯수가 출력됩니다.
// 여기서는, 10.

ES2015에서는, 템플릿 리터럴도 사용할 수 있습니다. 템플릿 문자열은 문자열 구성을 위한 달콤한 구문을 제공합니다. 이는 Perl, Python 등에 있는 문자열 삽입(interpolation) 기능과 비슷합니다. 마음대로, 문자열 구성을 사용자 정의하고 인젝션 공격을 피하거나 문자열 콘텐츠로 더 고레벨 데이터 구조를 구성하기 위한 태그가 추가될 수 있습니다.

// 기본적인 문자열 리터럴 생성
`In JavaScript '\n' is a line-feed.`

// 여러 줄 문자열
`In JavaScript this is
 not legal.`

// 문자열 삽입
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// Construct an HTTP request prefix is used to interpret the replacements and construction
POST`https://foo.org/bar?a=${a}&b=${b}
     Content-Type: application/json
     X-Credentials: ${credentials}
     { "foo": ${foo},
       "bar": ${bar}}`(myOnReadyStateChangeHandler);

꼭 문자열 객체를 사용할 필요가 없는 경우 문자열 리터럴을 사용해야 합니다. 문자열 객체에 대해 자세한 사항은 String을 참고하세요.

문자열에서 특수 문자 사용

보통 문자에 더해서, 문자열에 아래 예제와 같이 특수 문자도 포함할 수 있습니다.

"one line \n another line"

다음 표는 JavaScript 문자열에 사용할 수 있는 특수 문자 목록입니다.

표: JavaScript 특수 문자
문자
\0 Null Byte
\b Backspace
\f Form feed
\n New line
\r Carriage return
\t Tab
\v Vertical tab
\' Apostrophe 혹은 작은 따옴표
\" 큰 따옴표
\\ 백슬래시
\XXX Latin-1 인코딩 문자는 0 - 377 사이 8진수 3자리까지 지정될 수 있습니다. 예를 들어, \251은 copyright 심볼을 표현하는 8진수 시퀀스입니다.
\xXX Latin-1 인코딩 문자는 00 - FF 사이의 16진수 2자리로 지정될 수 있습니다. 예를 들어, \xA9는 copyright 심볼을 표현하는 16진수 시퀀스입니다.
\uXXXX 유니코드 문자는 16진수 4자리로 지정될 수 있습니다. 예를 들어, \u00A9는 copyright 심볼을 표현하는 유니코드 열입니다. Unicode escape sequences를 참고하세요.
\u{XXXXX} 유니코드 코드 포인트 이스케이프. 예를 들어, \u{2F804}는 간단한 유니코드 이스케이프 \uD87E\uDC04와 같습니다.

문자 이스케이프

표에 없는 문자의 경우 전행 백슬래시는 무시되지만, 이 용법은 더 이상 사용되지 않으며, 사용을 피해야 합니다.

전행 백슬래시와 함께 문자열 안에 따옴표를 사용할 수 있습니다. 이것을 따옴표 이스케이프라고 합니다. 예를 들어,

var quote = "He read \"The Cremation of Sam McGee\" by R.W. Service.";
console.log(quote);

이 코드의 결과는,

He read "The Cremation of Sam McGee" by R.W. Service.

백슬래시를 문자열 내에 포함시키기 위해서는, 백슬래시 문자를 이스케이프 해야 합니다. 예를 들어, 파일 경로 c:\temp를 문자열에 할당하기 위해서는 아래와 같이 사용합니다.

var home = "c:\\temp";

또한 줄바꿈 역시 전행 백슬래시로 이스케이프할 수 있습니다. 백슬래시와 줄바꿈 모두 문자열 값에서는 사라집니다.

var str = "this string \
is broken \
across multiple\
lines."
console.log(str);   // this string is broken across multiplelines.

JavaScript에는 "heredoc" 구문은 없지만, 줄바꿈 이스케이프와 각 줄 끝 이스케이프된 줄바꿈을 추가하여 흉내낼 수 있습니다.

var poem =
"Roses are red,\n\
Violets are blue.\n\
I'm schizophrenic,\n\
And so am I."

추가 정보

이 장은 선언과 형에 대한 기본 구문에 초점을 맞춥니다. JavaScript 언어 구조에 대해 더 많이 배우려면, 다음 장을 참고하세요.

다음 장에서는, 흐름 제어 구조와 오류 처리를 살핍니다.

문서 태그 및 공헌자

태그: 
 최종 변경: jadestern,