ECMAScript 6 문자열과 정규 표현식

문자열과 정규 표현식

논쟁의 여지는 있지만 String은 프로그래밍에서 가장 중요한 데이터 타입중 하나입니다. 문자열은 거의 모든 고급 프로그래밍 언어에 포함되어 있고, 개발자가 유용한 프로그램을 만드는데 중요한 요소입니다. 정규 표현식은 개발자가 문자열을 다양하게 다룰수 있도록 하기 때문에 매우 중요합니다. 이러한 내용을 고려한 ECMAScript 6의 제작자는 새로운 기능과 오랫동안 누락된 기능들을 추가하여 문자열과 정규 표현식을 향상 시켰습니다. 이 장에서는 이러한 두가지 타입의 변경 사항에 대해 설명합니다.

더 나은 Unicode 지원

ECMAScript 6 이전에는 JavaScript 문자열이 16-bit 문자 인코딩(UTF-16)이 중심이었습니다. 각 16-bit 시퀀스는 문자를 나타내는 코드 단위입니다. length 속성과 charAt() 메서드와 같은 모든 문자열 속성과 메서드는 이 16-bit 코드 단위를 기반으로 했습니다. 물론 16-bit는 모든 문자를 포함하기에 충분했습니다. 하지만 Unicode가 도입 한 확장 문자 세트 덕분에 더 이상 충분하지 않습니다.

UTF-16 Code Point

문자 길이를 16-bit로 제한하는 것은 세계의 모든 문자를 전 세계적으로 고유 한 식별자로 제공하겠다는 Unicode의 목표를 이룰수 없습니다. Code Point라고 하는 이러한 전역적으로 고유 한 식별자는 단순히 0부터 시작하는 숫자일 뿐입니다. Code Point는 문자 코드로 생각할 수 있습니다. 숫자는 문자를 나타냅니다. 문자 인코딩은 Code Point를 내부적으로 일관된 Code Unit으로 인코딩해야 합니다. UTF-16의 경우 Code Point를 많은 Code Unit으로 구성할 수 있습니다.

UTF-16의 첫 2^16 개 Code Point는 하나의 16-bit Code Unit으로 표현됩니다. 이 범위를 BMP (Basic Multilingual Plane)이라고 합니다. 그 이상에 있는 것은 Supplementary plane 중 하나에있는 것으로 생각되어 더 이상 16-bit만으로 Code Point를 표현할 수 없습니다. UTF-16은 단일 Code Point가 두 개의 16-bit Code Unit으로 표현되는 Surrogate pair을 도입하여 이 문제를 해결했습니다. 즉, 문자열의 모든 단일 문자는, BMP 문자의 경우 하나의 Code Unit이 되어 총 16-bit를 제공하거나 Supplementary plane 문자의 경우 총 32-bit를 제공 합니다.

ECMAScript5에서 모든 문자열 연산은 16-bit Code Unit에서 작동합니다. 즉, 아래 코드에서와 같이 Surrogate pair가 포함된 UTF-16 인코딩된 문자열에서 예기치 않은 결과가 발생할 수 있습니다.

1
2
3
4
5
6
7
8
var text = "𠮷";
console.log(text.length); // 2
console.log(/^.$/.test(text)); // false
console.log(text.charAt(0)); // ""
console.log(text.charAt(1)); // ""
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271

Unicode 문자"𠮷"Surrogate pair를 사용하여 표현되며 위의 JavaScript 문자열 연산은 문자열을 두 개의 16-bit 문자로 취급합니다. 이러한 의미는 다음을 나타냅니다.

  • textlength는 1이어야 할 때 2입니다.
  • 한 문자와 일치하는 정규식은 두 문자가 있다고 생각하기 때문에 실패합니다.
  • charAt() 메서드는 유효한 문자열을 반환 할 수 없습니다. 왜냐하면 16-bit의 문자는 출력 가능한 문자가 아니기 때문입니다.

또한 charCodeAt() 메서드는 문자를 제대로 식별 할 수 없습니다. 각 Code Unit에 대해 적절한 16-bit 숫자를 반환하지만 ECMAScript5에서 text의 실제 값에 유사한 값이기 때문입니다.

반면 ECMAScript 6은 이러한 Address Problem을 해결하기 위해 UTF-16 문자열 인코딩을 시행합니다. 이 문자 인코딩을 기반으로 문자열 연산을 표준화하면 JavaScript가 Surrogate pair와 함께 작동하도록 설계된 기능을 사용할 수 있습니다. 이 절의 나머지 부분에서는 해당 기능의 몇 가지 주요한 예제를 설명합니다.

codePointAt() 메서드

완벽하게 UTF-16을 지원하기 위해 추가된 ECMAScript 6 메서드 중 하나는 codePointAt() 메서드입니다.이 메서드는 문자열의 주어진 위치에 매핑되는 유니 코드 Code Point를 검색합니다. 이 메서드는 문자 위치가 아닌 Code Unit 위치를 받아 들여 정수 값을 반환합니다. 아래 예제를 살펴 보겠습니다.

1
2
3
4
5
6
7
8
9
var text = "𠮷a";
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
console.log(text.charCodeAt(2)); // 97
console.log(text.codePointAt(0)); // 134071
console.log(text.codePointAt(1)); // 57271
console.log(text.codePointAt(2)); // 97

codePointAt() 메서드는 문자가 BMP이면 charCodeAt()메서드와 같은 값을 반환합니다. 변수 text의 첫 번째 문자는 BMP가 아니기 때문에 두 개의 Code Unit으로 구성됩니다. 즉, length 값은 2가 아니라 3입니다. charCodeAt() 메서드는 0 번 위치의 첫 번째 Code Unit만 반환하지만 codePointAt()Code Point가 여러 개의 Code Unit에 걸쳐 있더라도 전체 Code Point를 반환합니다. 두 메서드는 모두 위치 1 (첫 번째 문자의 두 번째 Code Unit)과 2 ( "a"문자)에 대해 동일한 값을 반환합니다.

문자에 대해 codePointAt() 메서드를 호출하는 것은 그 문자가 하나 또는 두 개의 코드 포인트로 표현되는지를 결정하는 가장 쉬운 방법이다. 이러한 확인을 위해 다음 코드를 사용할 수 있습니다.

1
2
3
4
5
6
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit("𠮷")); // true
console.log(is32Bit("a")); // false

16-bit로 표시가능한 최대 숫자는 16 진수로 FFFF로 표현되므로, 그 숫자 위의 모든 Code Point는 총 32-bit의 두 Code Unit으로 표현되어야 합니다.

String.fromCodePoint() 메서드

ECMAScript는 어떤 일을 할 수있는 방법을 제공 할 때, 또한 반대로도 할 수있는 방법을 제공하는 경향이 있습니다. codePointAt() 메서드를 사용하여 문자열의 문자에 대한 Code Point를 찾을 수 있고, `String.fromCodePoint()’ 메서드는 주어진 Code Point에서 단일 문자열을 생성합니다.

1
console.log(String.fromCodePoint(134071)); // "𠮷"

String.fromCharCode() 메서드의 보다 완벽한 버전 인 String.fromCodePoint()를 생각해 보십시오. 둘 다 BMP의 모든 문자에 대해 동일한 결과를 제공합니다. 하지만 BMP 외부의 문자에 대한 Code Point를 전달할 때 차이가 있습니다.

normalize() 메서드

Unicode의 또 다른 흥미로운 점은 정렬이나 다른 비교 기반 작업을 위해 서로 다른 문자를 비교할수 있다는 것입니다. 비교를 위한 이러한 연관 관계를 정의하는 두 가지 방법이 있습니다. 첫째 Canonical equivalence는 모든 점에서 Code Point의 두 시퀀스가 상호 교환 가능하다고 간주됨을 의미합니다. 예를 들어, 두 문자의 조합은 기본적으로 한 문자와 같을 수 있습니다. 두 번째는 Compatibility(호환성)입니다. Code point의 시퀀스는 서로 다르게 보이지만 특정 상황에서는 서로 바꿔서 사용할 수 있습니다.

이러한 관계로 인해 근본적으로 동일한 텍스트를 나타내는 두 개의 문자열은 서로 다른 Code point 시퀀스를 포함 할 수 있습니다. 예를 들어, 문자 "æ"와 두 자의 문자열 "ae"는 어떤 방식으로든 표준화를 하지 않으면 엄격하게 동등하지 않기 때문에 서로 바꿔서 사용할 수 없습니다.

ECMAScript 6는 문자열에 normalize()가 있어서 Unicode 정규화 형식을 지원합니다. 이 메서드에 사용할 정규화 타입중 하나를 나타내는 하나의 문자열을 파라미터로 사용할 수 있습니다.

  • Normalization Form Canonical Composition ("NFC") - 기본값
  • Normalization Form Canonical Decomposition ("NFD")
  • Normalization Form Compatibility Composition ("NFKC")
  • Normalization Form Compatibility Decomposition ("NFKD")

이 네 가지 형식의 차이점을 설명하는 것은이 책의 범위를 벗어납니다. 문자열을 비교할 때 두 문자열을 같은 형식으로 정규화해야한다는 점을 명심하십시오.

1
2
3
4
5
6
7
8
9
10
11
12
13
var normalized = values.map(function(text) {
return text.normalize();
});
normalized.sort(function(first, second) {
if (first < second) {
return -1;
} else if (first === second) {
return 0;
} else {
return 1;
}
});

위 코드는 변수 values Array의 문자열을 정규화된 형식으로 변환하여 Array을 적절히 정렬 할 수 있도록 합니다. 아래 코드와 같이 Comparator의 일부로서normalize() 를 호출하여 원래의 Array을 정렬 할 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
values.sort(function(first, second) {
var firstNormalized = first.normalize(),
secondNormalized = second.normalize();
if (firstNormalized < secondNormalized) {
return -1;
} else if (firstNormalized === secondNormalized) {
return 0;
} else {
return 1;
}
});

위 코드의 중요한 점은 firstsecond가 같은 방식으로 표준화 되어야 한다는 것입니다. 이 예제는 기본 NFC를 사용했지만 다음과 같이 나머지 중 하나를 쉽게 지정할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
values.sort(function(first, second) {
var firstNormalized = first.normalize("NFD"),
secondNormalized = second.normalize("NFD");
if (firstNormalized < secondNormalized) {
return -1;
} else if (firstNormalized === secondNormalized) {
return 0;
} else {
return 1;
}
});

이전에 Unicode 표준화에 대해 걱정 해 본 적이 없다면 아마 이 방법을 사용하지 않을 것입니다. 하지만 국제화된 응용 프로그램에서 작업한다면 normalize() 메서드가 도움이 될 것입니다.

normalize() 메서드가 ECMAScript 6에서 Unicode 문자열 작업에 제공하는 유일한 메서드가 아닙니다. ECMAScript 6은 또한 두가지의 유용한 구문을 추가 했습니다.

Regular Expression의 u 플래그

정규 표현식을 통해 일반적인 많은 문자열 연산을 수행 할 수 있습니다. 그러나 정규 표현식은 16-bit Code unit를 가정하고 각 Code unit은 하나의 문자를 나타냅니다. 이 문제를 해결하기 위해 ECMAScript 6에서 Unicode를 나타내는 정규 표현식에 대해 u 플래그를 정의합니다.

실행중일 때의 u 플래그

정규 표현식에 u 플래그가 설정되면, Code unit이 아닌 문자에 대한 작업 모드로 전환됩니다. 즉 정규 표현식이 문자열의 Surrogate pair에 대해 더 이상 혼동하지 않아야 하며 예상대로 작동해야합니다. 예를 들어 다음 코드를 생각해 보겠습니다.

1
2
3
4
5
var text = "𠮷";
console.log(text.length); // 2
console.log(/^.$/.test(text)); // false
console.log(/^.$/u.test(text)); // true

정규 표현식 /^.$/은 입력 문자열을 단일 문자와 일치시킵니다. u 플래그없이 사용하면 이 정규 표현식이 Code unit으로 일치를 판단 하기 때문에 한자(두개의 Code unit으로 표현됨)가 정규 표현식과 일치하지 않습니다. u 플래그와 함께 사용하면 정규식은 Code unit 대신 문자들을 비교하므로 한자 문자가 일치합니다.

Code Point 카운트

유감스럽게도 ECMAScript 6은 문자열의 Code point 수를 알려주는 메서드를 추가하지 않았습니다. 하지만 u 플래그를 사용하면 정규 표현식을 사용하여 다음과 같이 계산할 수 있습니다.

1
2
3
4
5
6
7
function codePointLength(text) {
var result = text.match(/[\s\S]/gu);
return result ? result.length : 0;
}
console.log(codePointLength("abc")); // 3
console.log(codePointLength("𠮷bc")); // 3

위 코드는 match()를 호출할 때 u 옵션을 이용하여 전체 문자열에서 공백 문자와 공백 문자가 아닌 문자([\s\S]는 개행문자도 포함합니다.)를 text에서 둘다 확인합니다. result는 일치가 발생한 문자의 Array이기 때문에 Array의 길이는 문자열의 Code point 수가 됩니다. Unicode에서 문자열 "abc""𠮷bc"는 둘 다 3 개의 문자를 가지므로 Array 길이는 3입니다.

위의 예제는 효과적 일지는 몰라도, 특히 긴 문자열에 적용 할 때 빠르지는 않습니다. 문자열 Iterator(나중에 설명)를 사용할 수 있습니다. Code point 카운트를 최소화하는게 좋습니다.

u 플래그 지원 여부 확인하기

u 플래그는 구문 변경이므로 ECMAScript 6과 호환되지 않는 JavaScript 엔진에서 사용하려고 시도하면 구문 오류가 발생합니다. u 플래그가 지원되는지를 결정하는 가장 안전한 방법은 다음과 같은 함수를 사용하는 것입니다.

1
2
3
4
5
6
7
8
function hasRegExpU() {
try {
var pattern = new RegExp(".", "u");
return true;
} catch (ex) {
return false;
}
}

이 함수는 RegExp 생성자를 사용하여 u 플래그를 인수로 전달합니다. 이 구문은 구형 JavaScript 엔진에서도 유효하지만, ‘u’가 지원되지 않으면 생성자는 오류를 발생시킵니다.

코드가 오래된 JavaScript 엔진에서 작동해야하는 경우, u 플래그를 사용할 때 항상 RegExp 생성자를 사용하십시오. 이렇게하면 구문 오류가 방지되고, 실행을 중단하지 않고 선택적으로 u플래그를 감지하여 사용할 수 있습니다.

String의 다른 변경사항

JavaScript 문자열 관련 기능들은 다른 언어보다 항상 뒤떨어져 있습니다. 예를 들어 ECMAScript 5에서 문자열의 trim() 메서드가 추가 되었으며 ECMAScript 6는 JavaScript의 기능을 확장하여 새로운 기능으로 문자열을 파싱합니다.

Substring 문자열 식별 메서드

개발자들은 JavaScript가 처음 소개된 이래로 indexOf() 메서드를 사용하여 문자열 내부에서 어떤 텍스트가 있는지 판단 했습니다. ECMAScript 6에는 다음과 같은 세 가지 방법이 추가 되었습니다.

  • includes() 메서드는 주어진 텍스트가 문자열 내의 어느 위치에서든 발견되면 true를 반환합니다. 그렇지 않으면 false를 반환합니다.
  • startsWith() 메서드는 지정된 텍스트가 문자열의 시작 부분에서 발견되면 true를 반환합니다. 그렇지 않으면 false를 반환합니다.
  • endsWith() 메서드는 주어진 텍스트가 문자열의 끝에서 발견되면 true를 반환합니다. 그렇지 않으면 false를 반환합니다.

각 메서드는 검색 할 텍스트와 검색을 시작할 인덱스(Optional)라는 두 개의 파라미터를 사용할 수 있습니다. 두 번째 파라미터가 입력 되면 includes()는 입력된 파라미터 이후에 일치하는 텍스트가 있는지 확인하고, startsWith()는 입력받은 파라미터의 인덱스에 첫 번째 입력 받은 텍스트가 일치 하는지 검사합니다. 그리고 endsWith ()는 두 번째 파라미터를 문자열의 마지막으로 설정해서 문자열에서 마지막 텍스트가 일치하는지 확인합니다. 두 번째 인수가 생략되면 endsWith()가 맨 끝에서 시작하고, includes ()startsWith ()는 문자열의 처음부터 검색합니다.결과적으로 두 번째 인수는 검색되는 문자열의 양을 최소화합니다. 다음은이 세 가지 방법을 보여주는 몇 가지 예입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
var msg = "Hello world!";
console.log(msg.startsWith("Hello")); // true
console.log(msg.endsWith("!")); // true
console.log(msg.includes("o")); // true
console.log(msg.startsWith("o")); // false
console.log(msg.endsWith("world!")); // true
console.log(msg.includes("x")); // false
console.log(msg.startsWith("o", 4)); // msg: "o world!" => true
console.log(msg.endsWith("o", 8)); // msg: "Hello wo" => true
console.log(msg.includes("o", 8)); // msg: "rld!" => false

처음 6 개 호출에는 두 번째 파라미터가 없으므로 전체 문자열을 검색에 사용합니다. 마지막 세 건의 호출은 문자열의 일부만 검사합니다. msg.startsWith( "o", 4)를 호출하면 “Hello”에서 “o”인 msg 문자열의 4개의 문자를 건너뛰고 일치를 시작합니다. msg.endsWith("o", 8) 호출은 전체 문자열 길이(12)중에서 처음부터 입력받은 8개까지 문자열의 마지막에서 텍스트 일치를 시작합니다. msg.includes("o", 8)호출은 “world”의 “r”인 8개의 문자를 건너뛰고 일치를 시작합니다.

이 세 가지 방법을 사용하면 하위 문자열의 존재를 다 쉽게 식별 할 수 있지만 각각 true/false 값만 반환합니다. 한 문자열의 실제 위치를 다른 문자열 내에서 찾으려면 indexOf() 또는 lastIndexOf () 메서드를 사용하십시오.

startsWith(), endsWith()includes() 메서드는 문자열 대신 정규 표현식을 파라미터로 전달하면 오류가 발생합니다. 정규 표현식이 포함된 파라미터를 문자열로 변환 한 다음 해당 문자열을 검색하는 indexOf(), lastIndexOf()와는 대조적입니다.

repeat() 메서드

ECMAScript 6은 String에 repeat () 메서드를 추가했습니다. 이 메서드는 파라미터로 입력한 회수 만큼 문자열을 반복합니다.

1
2
3
console.log("x".repeat(3)); // "xxx"
console.log("hello".repeat(2)); // "hellohello"
console.log("abc".repeat(4)); // "abcabcabcabc"

이 메서드는 텍스트를 조작 할 때 특히 유용하고 편리한 함수입니다. 다음과 같이 들여 쓰기 수준을 만들어야 하는 코드 서식 유틸리티에서 특히 유용합니다.

1
2
3
4
5
6
// indent using a specified number of spaces
var indent = " ".repeat(4),
indentLevel = 0;
// whenever you increase the indent
var newIndent = indent.repeat(++indentLevel);

첫 번째 repeat() 호출은 4개의 공백이 포함된 문자열을 생성하고, indentLevel 변수는 들여 쓰기 레벨을 추적합니다. 그런 다음, repeat()를 증가된 indentLevel과 함께 호출하여 공백(“ “)의 수를 변경할 수 있습니다.

또한 ECMAScript 6은 특정 범주에 맞지 않는 정규 표현식 기능을 일부 변경했습니다. 다음 섹션에서는 몇 가지 사항을 집중적으로 설명합니다.

정규 표현식(Regular Expression)의 다른 변경 사항

정규 표현식은 JavaScript로 문자열을 다루는 중요한 부분이며, JavaScript의 다른 많은 부분과 마찬가지로 최근 버전까지 많이 변경되지 않았습니다. 하지만 ECMAScript 6에서 문자열에 대한 몇몇 업데이트와 함께 정규 표현식이 몇 가지 개선되었습니다.

정규 표현식(Regular Expression) y 플래그

ECMAScript 6은 파이어 폭스에서 정규 표현식을 독점적으로 확장 구현 후 y 플래그를 표준화 했습니다. y 플래그는 정규 표현식 검색의 sticky 속성에 영향을 미치며 정규 표현식의 lastIndex 속성에 의해 지정된 위치의 문자열에서 일치하는 문자를 검색하도록 검색을 지시합니다. 해당 위치에서 일치하는 항목이 없으면 정규식은 매칭을 중지합니다. 이것이 어떻게 작동하는지 다음 코드를 보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var text = "hello1 hello2 hello3",
pattern = /hello\d\s?/,
result = pattern.exec(text),
globalPattern = /hello\d\s?/g,
globalResult = globalPattern.exec(text),
stickyPattern = /hello\d\s?/y,
stickyResult = stickyPattern.exec(text);
console.log(result[0]); // "hello1 "
console.log(globalResult[0]); // "hello1 "
console.log(stickyResult[0]); // "hello1 "
pattern.lastIndex = 1;
globalPattern.lastIndex = 1;
stickyPattern.lastIndex = 1;
result = pattern.exec(text);
globalResult = globalPattern.exec(text);
stickyResult = stickyPattern.exec(text);
console.log(result[0]); // "hello1 "
console.log(globalResult[0]); // "hello2 "
console.log(stickyResult[0]); // Error! stickyResult is null

이 예제에는 3 개의 정규 표현식이 있습니다. pattern의 표현식에는 플래그가 없으며 globalPattern의 표현식은 g 플래그를 사용하고stickyPattern의 표현식은 y 플래그를 사용합니다. 첫 번째의 3개 console.log()에서 세 정규식은 모두 끝에 스페이스가 포함된 "hello1 "를 반환 합니다.

그런 다음 lastIndex 속성은 세 패턴 모두에서 1로 변경됩니다. 즉 정규 표현식이 두 번째에서 일치를 시작합니다. 플래그가 없는 정규 표현식은lastIndex에 대한 변경 사항을 완전히 무시하고 "hello1 "과 아무런 문제없이 일치합니다. g 플래그를 사용하는 정규 표현식은 문자열의 두 번째 문자("e")에서 검색을 시작하기 때문에 "hello2 "와 일치합니다. sticky 정규 표현은 두 번째 문자에서 시작하는 것과 일치하지 않으므로stickyResultnull입니다.

sticky 플래그는 작업이 수행될 때마다 lastIndex에서 마지막으로 일치 한 다음 문자 다음의 인덱스를 저장합니다. 연산 결과가 일치하지 않으면 lastIndex가 0으로 다시 설정됩니다. 전역 stickyPattern.lastIndex는 다음과 같이 동일한 방식으로 동작합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var text = "hello1 hello2 hello3",
pattern = /hello\d\s?/,
result = pattern.exec(text),
globalPattern = /hello\d\s?/g,
globalResult = globalPattern.exec(text),
stickyPattern = /hello\d\s?/y,
stickyResult = stickyPattern.exec(text);
console.log(result[0]); // "hello1 "
console.log(globalResult[0]); // "hello1 "
console.log(stickyResult[0]); // "hello1 "
console.log(pattern.lastIndex); // 0
console.log(globalPattern.lastIndex); // 7
console.log(stickyPattern.lastIndex); // 7
result = pattern.exec(text);
globalResult = globalPattern.exec(text);
stickyResult = stickyPattern.exec(text);
console.log(result[0]); // "hello1 "
console.log(globalResult[0]); // "hello2 "
console.log(stickyResult[0]); // "hello2 "
console.log(pattern.lastIndex); // 0
console.log(globalPattern.lastIndex); // 14
console.log(stickyPattern.lastIndex); // 14

lastIndex의 값은 stickyPattern 변수와 globalPattern 변수 모두에 대해 exec()의 첫 번째 호출 후에 7로 변경되고 두 번째 호출 후에 14로 변경됩니다.

염두에 두어야 할 sticky 플래그에 대한 두 가지 미묘한 세부 사항이 있습니다.

  1. lastIndex 속성은 exec()test() 메서드와 같이 정규 표현식 객체에 존재하는 메서드를 호출 할 때만 적용됩니다. 정규 표현식을 match ()와 같은 문자열 메서드에 전달하면 sticky를 실행하지 않습니다.
  2. 문자열의 시작과 일치시키기 위해^문자를 사용할 때, sticky 정규 표현식은 문자열의 시작 부분 또는 여러 줄 모드의 줄 시작 부분에서만 일치합니다. lastIndex가 0 일때 ^는 non-sticky 정규 표현식과 sticky 정규 표현식의 결과가 같습니다. lastIndex가 단일 행 모드에서 문자열의 시작이나 다중 행 모드에서 행의 시작과 일치하지 않으면 sticky 정규 표현식은 절대로 매칭하지 않습니다.

일반 다른 정규식 플래그와 마찬가지로 프로퍼티를 사용하여 sticky 플래그의 존재를 확인 할 수 있습니다. 다음 코드는 sticky 속성을 검사 하는 예제입니다.

1
2
3
var pattern = /hello\d/y;
console.log(pattern.sticky); // true

sticky 속성은 sticky 플래그가 있으면 true로 설정되고, 그렇지 않으면 false로 설정됩니다. sticky 속성은 플래그의 존재에 기초한 읽기 전용이며 코드라서 변경할 수 없습니다.

u 플래그와 비슷하게, y 플래그는 구문 변경이므로 오래된 JavaScript 엔진에서는 구문 오류가 발생할 수 있습니다. 다음 방법을 사용하여 sticky 지원을 검사 할 수 있습니다.

1
2
3
4
5
6
7
8
function hasRegExpY() {
try {
var pattern = new RegExp(".", "y");
return true;
} catch (ex) {
return false;
}
}

u 검사와 마찬가지로, y 플래그를 가진 정규 표현식을 만들 수 없다면 false를 반환합니다. u 플래그와 유사하게 오래된 JavaScript 엔진에서 실행되는 코드 내부에 y를 사용해야 할 경우, 정규 표현식을 정의 할 때 RegExp 생성자를 사용하여 구문 오류를 피하십시오.

정규 표현식 복제(Duplicating Regular Expressions)

ECMAScript 5에서는 다음과 같이 RegExp 생성자에 정규 표현식을 전달하여 복제할 수 있습니다.

1
2
var re1 = /ab/i,
re2 = new RegExp(re1);

re2 변수는 re1 변수의 복사본입니다. 그러나 정규식에 대한 플래그를 지정하는 RegExp 생성자에 두 번째 파라미터를 제공하면 다음 코드와 같이 정상 작동하지 않습니다.

1
2
3
4
var re1 = /ab/i,
// ES5에서는 error 발생, ES6에서는 OK
re2 = new RegExp(re1, "g");

ECMAScript 5 환경에서 이 코드를 실행하면 첫 번째 파라미터가 정규 표현식 일 때 두 번째 파라미터를 사용할 수 없다는 오류가 발생합니다. ECMAScript 6은 두 번째 파라미터가 허용되고 첫 번째 파라미터에있는 플래그를 무시하도록이 동작이 변경 되었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var re1 = /ab/i,
// ES5에서는 error 발생, ES6에서는 OK
re2 = new RegExp(re1, "g");
console.log(re1.toString()); // "/ab/i"
console.log(re2.toString()); // "/ab/g"
console.log(re1.test("ab")); // true
console.log(re2.test("ab")); // true
console.log(re1.test("AB")); // true
console.log(re2.test("AB")); // false

이 코드에서,re1은 대소 문자를 구분하지 않는 i 플래그를, re2는 전역 g 플래그만을 가지고 있습니다. RegExp 생성자는 re1에서 패턴을 복사하고 i 플래그를 g 플래그로 대체했습니다. 두 번째 인수가 없으면 re2re1과 같은 플래그를가집니다.

플래그 프로퍼티

새 플래그를 추가하고 플래그를 사용하여 작업하는 방법을 변경하는 것과 함께 ECMAScript 6은 이와 관련된 프로퍼티를 추가했습니다. ECMAScript 5에서는 source 프로퍼티를 사용하여 정규 표현식의 텍스트를 얻을 수 있지만 플래그 문자열을 얻으려면 아래와 같이toString ()메서드의 출력을 파싱해야합니다.

1
2
3
4
5
6
7
8
9
function getFlags(re) {
var text = re.toString();
return text.substring(text.lastIndexOf("/") + 1, text.length);
}
// toString() is "/ab/g"
var re = /ab/g;
console.log(getFlags(re)); // "g"

정규식을 문자열로 변환 한 다음 마지막 / 다음에 나오는 문자를 반환합니다. / 이후의 문자가 플래그입니다.

ECMAScript 6는 flags 프로퍼티를 source 프로퍼티와 함께 추가하여 플래그를 쉽게 가져옵니다. 두 프로퍼티는 모두 getter 만 할당된 프로토 타입 접근자이므로 읽기 전용입니다. flags 프로퍼티는 디버깅과 상속 목적을 위해 정규 표현식을 더 쉽게 검사하게 합니다.

ECMAScript 6에 추가된 flags 프로퍼티는 정규 표현식에 적용된 플래그의 문자열 표현을 반환합니다. 예 :

1
2
3
4
var re = /ab/g;
console.log(re.source); // "ab"
console.log(re.flags); // "g"

위 코드는 re에 있는 모든 플래그를 가져 와서 toString () 기법보다 훨씬 적은 코드로 결과를 콘솔에 출력합니다. sourceflags를 함께 사용하면 정규식 문자열을 직접 파싱하지 않고도 필요한 정규 표현식을 추출할 수 있습니다.

이 장에서 지금까지 다루었던 일반 문자열과 정규 표현식에 대한 변경 사항은 분명 강력하지만 ECMAScript 6는 훨씬 더 강력한 방식의 문자열을 지원합니다.

Template literal(템플릿 리터럴)

항상 JavaScript의 문자열 처리 기능은 다른 언어와 비교하면 기능이 제한적이었습니다. 예를 들어, ECMAScript 6까지 문자열은 지금까지 위에서 다루었던 메서드가 없었으며 문자열 연결은 가능한 간단했습니다. 개발자가 보다 복잡한 문제를 해결할 수 있도록 ECMAScript 6의 Template literal은 ECMAScript 5 이하에서 사용할 수있는 솔루션보다 안전한 방법으로 콘텐츠 작업을 위한 도메인 별 언어 (DSL) 생성 구문을 제공합니다. (DSL은 JavaScript와 같은 범용 언어와 달리 특정 목적을 위해 설계된 프로그래밍 언어입니다.) ECMAScript wiki는 template literal strawman에 대해 다음과 같이 설명합니다.

이 스키마는 Syntactic sugar를 활용한 ECMAScript 구문을 확장하여 라이브러리가 다른 언어의 내용을 쉽게 생성, 쿼리 및 조작할 수있는 DSL을 제공 하여 XSS, SQL Injection 등과 같이 Injection attack에 대항하거나 면역 기능을 부여 합니다.

실제로 Template literal은 JavaScript가 ECMAScript 5를 통해 부족했던 다음 기능에 대한 ECMAScript 6의 대답입니다.

  • Multiline strings 여러 줄 문자열의 공식 개념.
  • Basic string formatting 변수값으로 문자열의 일부를 대체할 수있는 기능.
  • HTML escaping HTML에 삽입하는 것이 안전하도록 문자열을 변환하는 기능.

Template literal은 JavaScript의 기존 문자열에 더 많은 기능을 추가하기 보다는 이러한 문제를 해결하기 위한 완전히 새로운 접근 방식입니다.

기본 문법 (Basic Syntax)

가장 단순한 Template literal은 이중 따옴표(") 나 작은 따옴표(') 대신 백팅(` )으로 문자열을 감싸면 됩니다.

1
2
3
4
5
let message = `Hello world!`;
console.log(message); // "Hello world!"
console.log(typeof message); // "string"
console.log(message.length); // 12

위 코드는 message 변수가 일반적인 JavaScript 문자열을 포함하고 있음을 보여줍니다. Template literal 구문은 문자열의 Code point을 생성하는 데 사용되며, 이 Code pointmessage 변수에 할당됩니다.

문자열에서 백틱을 사용하려면 아래의 코드의 message 변수 처럼 백 슬래시 (\)를 사용하여 이스케이프합니다.

1
2
3
4
5
let message = `\`Hello\` world!`;
console.log(message); // "`Hello` world!"
console.log(typeof message); // "string"
console.log(message.length); // 14

Template literal 안의 이중 따옴표 나 작은 따옴표는 이스케이프할 필요가 없습니다.

여러 줄의 문자열 (Multiline String)

JavaScript 개발자는 첫 번째 버전 이후로 항상 여러 줄을 만드는 방법을 원했습니다. 그러나 이중 따옴표 나 작은 따옴표를 사용하는 경우 문자열은 한줄로 완전히 포함되어야합니다.

ECMAScript 6 이전 방법

하지만 오래 지속되는 문법 버그 덕분에 JavaScript는 해결 방법이 있습니다. 개행 전에 백 슬래시 (\)가 있으면 여러 줄을 만들 수 있습니다. 다음은 그 예입니다.

1
2
3
4
var message = "Multiline \
string";
console.log(message); // "Multiline string"

하지만 백 슬래시가 개행이 아닌 연속으로 처리되기 때문에 message 문자열에는 콘솔에 출력할 때 개행 문자가 없습니다. 출력에 개행 문자를 표시하려면 수동으로 포함시켜야합니다.

1
2
3
4
5
var message = "Multiline \n\
string";
console.log(message); // "Multiline
// string"

이것은 모든 주요한 JavaScript 엔진에서 두줄로된 Multiline String을 출력 하는 방법입니다. 그러나 이 동작은 버그로 정의되어 있으므로 많은 개발자들은 이를 피하는 것을 권장합니다.

일반적으로 ECMAScript 6 이전 버전으로 다중 문자열을 만들기 위한 방법은 다음과 같이 Array이나 문자열 연결에 의존합니다.

1
2
3
4
5
6
7
var message = [
"Multiline ",
"string"
].join("\n");
let message = "Multiline \n" +
"string";

JavaScript에서 여러 줄을 사용하지 못하게하는 하는 문제는 개발자의 오랜 숙원이었습니다.

쉽게 여러줄의 문자열 만드는 방법

ECMAScript 6의 Template literal은 특수 구문이 없기 때문에 여러 줄 문자열을 쉽게 만듭니다. 원하는 위치에 개행을 포함하면 결과에 나타납니다. 예 :

1
2
3
4
5
6
let message = `Multiline
string`;
console.log(message); // "Multiline
// string"
console.log(message.length); // 16

백틱의 모든 공백은 문자열의 일부이므로 들여 쓰기에 주의하십시오.

1
2
3
4
5
6
let message = `Multiline
string`;
console.log(message); // "Multiline
// string"
console.log(message.length); // 31

이 코드에서 Template literal의 두 번째 줄 앞에있는 공백은 모두 문자열 자체의 일부로 간주됩니다. 적절한 들여 쓰기로 텍스트 줄을 만드는 것이 중요하다면 다음과 같이 여러 줄 Template literal의 첫 줄에 아무 것도 남기지 말고 그 다음에 들여 쓰기하는 것을 생각해 볼수 있습니다.

1
2
3
4
let html = `
<div>
<h1>Title</h1>
</div>`.trim();

이 코드는 첫 번째 줄에서 Template literal을 시작하지만 두 번째 줄까지는 텍스트가 없습니다. HTML 태그는 올바른 모양으로 들여 쓰여지고 trim() 메서드가 호출되어 초기 빈 줄이 제거됩니다.

원하는 경우, Template literal에서 \n을 사용하여 개행을 삽입할 위치를 지정할 수도 있습니다.

1
2
3
4
5
let message = `Multiline\nstring`;
console.log(message); // "Multiline
// string"
console.log(message.length); // 16

문자열 치환 하기

이 시점에서 Template literal은 일반적인 JavaScript 문자열보다 더 멋진 버전처럼 보일 수 있습니다. 하지만 일반 JavaScript의 문자열과 Template literal의 실제 차이는 Substitution(치환)에 있습니다. 치환 기능을 사용하면 유효한 JavaScript 표현식을 Template literal에 포함시키고 결과를 문자열의 일부로 출력할 수 있습니다.

문자열 치환은 문자열 내부에 임의의 JavaScript 표현식이 포함된 여는 ${ 및 닫는}로 처리 됩니다. 가장 간단한 치환은 다음과 같이 결과 문자열에 지역 변수를 직접 포함시킬 수 있습니다.

1
2
3
4
let name = "Nicholas",
message = `Hello, ${name}.`;
console.log(message); // "Hello, Nicholas."

치환 ${name}은 로컬 변수 name에 접근하여 namemessage 문자열에 삽입합니다. message 변수는 즉시 치환 결과를 저장합니다.

Template literal은 정의된 Scope에서 액세스할 수있는 모든 변수를 액세스할 수 있습니다. Template literal에서 선언되지 않은 변수를 사용하려고 시도하면 엄격 모드와 비 엄격 모드 모두에서 오류가 발생합니다.

모든 치환은 JavaScript 표현식이므로 간단한 변수 값의 치환 그 이상을 할 수 있습니다. 계산, 함수 호출 등도 쉽게 포함할 수 있습니다.

1
2
3
4
5
let count = 10,
price = 0.25,
message = `${count} items cost $${(count * price).toFixed(2)}.`;
console.log(message); // "10 items cost $2.50."

이 코드는 Template literal의 일부로 계산 기능을 수행합니다. countprice 변수를 곱해서 결과를 얻은 다음.toFixed()를 사용하여 소수점 두 자리까지 포맷합니다. 두 번째 치환 이전의 달러 기호는 여는 중괄호가 없기 때문에 그대로 출력됩니다.

Template literal은 JavaScript 표현식이기도합니다. 즉,이 예제에서와 같이 다른 Template literal의 치환 내부에 Template literal을 배치할 수 있습니다.

1
2
3
4
5
6
let name = "Nicholas",
message = `Hello, ${
`my name is ${ name }`
}.`;
console.log(message); // "Hello, my name is Nicholas."

이 예제는 첫 번째 템플릿 안에 두 번째 Template literal을 중첩합니다. 첫 번째 ${ 다음에 다른 Template literal이 시작됩니다. 두 번째 ${는 내부 Template literal 안에 삽입된 표현식의 시작을 나타냅니다. 그 표현식은 결과에 삽입되는 변수 name입니다.

템플릿 Tag(Tagged Template)

지금까지 Template literal이 여러 줄을 만들고 따로 연결없이(+) 문자열에 값을 삽입하는 방법을 살펴 보았습니다. 그러나 Template literal의 진정한 힘은 태그가있는 템플릿에서 비롯됩니다. Template tagTemplate literal에 대한 치환을 수행하고 최종 문자열 값을 반환합니다. 이 태그는 첫 번째` 문자 바로 앞에있는 템플릿의 시작 부분에 다음과 같이 지정합니다.

1
let message = tag`Hello world`;

이 예제에서 tag`Hello world` Template literal에 적용할 템플릿 태그입니다.

Tag 정의하기

Tag는 단순히 처리된 Template literal 데이터가 호출되는 함수입니다. TagTemplate literal에 대한 데이터를 개별 조각으로 수신하고 조각을 결합하여 결과를 만들어야 합니다. 첫 번째 파라미터는 JavaScript에 의해 해석되는 리터럴 문자열을 포함하는 Array입니다. 후속 파라미터는 각 치환에 사용될 값입니다.

Tag 함수는 일반적으로 다음과 같은 파라미터를 사용하여 정의되어 데이터를 보다 쉽게 처리할 수 있습니다.

1
2
3
function tag(literals, ...substitutions) {
// return a string
}

무엇이 Tag로 전달되는지 더 잘 이해하기 위해 다음 코드를 살펴 보겠습니다.

1
2
3
let count = 10,
price = 0.25,
message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;

위 코드를 보면, passthru() 함수는 3 개의 파라미터를 받습니다. 첫 번째 파라미터는 아래의 요소를 포함하는 literals Array입니다.

  • 첫 번째 치환 전의 빈 문자열 ("")
  • 첫 번째 치환 후 두 번째 문자열 앞에있는 문자열 ( "items cost $")
  • 두 번째 치환 후의 문자열 (".")

두 번째 파라미터는 count 변수에 대한 해석된 값인 10입니다. 이것은 substitutions Array의 첫 번째 요소가됩니다. 마지막 인수는(count * price).toFixed(2)의 해석된 값이고 substitutions Array의 두 번째 요소인 "2.50"이 될 것입니다.

literals의 첫 번째 항목은 빈 문자열입니다. 이것은 literals[literals.length - 1]이 항상 문자열의 끝인 것처럼 literals[0]이 항상 문자열의 시작임을 보장합니다. 항상 substitutions.length === literals.length - 1이라는 표현이 true라는 것은 substitutionsliterals보다 항상 하나 더 적다는 것을 의미합니다.

이 패턴을 사용하면 literalssubstitutions Array을 결합하여 결과 문자열을 만들 수 있습니다. literals의 첫 번째 항목이 먼저오고, substitutions의 첫 번째 항목은 다음 항목이며, 이런 순서는 문자열이 완료될 때까지 계속됩니다. 예를 들어, 다음 두 Array의 값을 교대로 사용하여 Template literal의 기본 동작을 모방할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function passthru(literals, ...substitutions) {
let result = "";
// loop는 substitutions 카운트 만큼 실행합니다.
for (let i = 0; i < substitutions.length; i++) {
result += literals[i];
result += substitutions[i];
}
// 마지막 literal
result += literals[literals.length - 1];
return result;
}
let count = 10,
price = 0.25,
message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;
console.log(message); // "10 items cost $2.50."

위 예제는 Template literal의 기본 액션과 동일한 치환을 수행하는 passthru Tag를 정의합니다. 유일한 트릭은 substitutions Array의 끝을 지나치는 것을 피하기 위해 literalals.length가 아닌 substitutions.length를 loop에서 사용하는 것입니다. 이것은 literalalssubstitutions 사이의 관계가 ECMAScript 6에서 잘 정의되어 있기 때문에 가능합니다.

‘substitutions’에 포함된 값은 반드시 문자열 일 필요는 없습니다. 앞의 예에서와 같이 표현식이 숫자로 평가되면 숫자 값이 전달됩니다. 이러한 값이 결과에 어떻게 출력되어야 하는지를 결정하는 것은 Tag 작업의 일부입니다.

Template literal의 Raw Value 사용하기

템플릿 TagRaw string 정보에도 접근할 수 있습니다. 이 정보는 주로 문자 이스케이프에 대한 액세스를 의미합니다. Raw string 값으로 작업하는 가장 간단한 방법은 내장된 String.raw() Tag를 사용하는 것입니다.

1
2
3
4
5
6
let message1 = `Multiline\nstring`,
message2 = String.raw`Multiline\nstring`;
console.log(message1); // "Multiline
// string"
console.log(message2); // "Multiline\\nstring"

이 코드에서 message1\n은 개행 문자로 해석되고 message2\n"\\n"(역슬래시 및 문자 n)의 Raw string으로 해석됩니다. 이와 같이 Raw string 정보를 찾을려면 생각보다 복잡한 처리가 필요합니다.

Raw string 정보는 또한 템플릿 Tag로 전달됩니다. Tag 함수의 첫 번째 파라미터는 raw라는 추가 프로퍼티가 있는 Array입니다. raw 프로퍼티는 각 리터럴 값과 정확히 일치하는 Raw을 포함하는 Array입니다. 예를 들어, literals[0]의 값은 항상 Raw string 정보를 포함하는 literals.raw[0]과 일치합니다. 다음 코드를 사용하여 String.raw()을 모방할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function raw(literals, ...substitutions) {
let result = "";
// loop는 substitutions 카운트 만큼 실행합니다.
for (let i = 0; i < substitutions.length; i++) {
result += literals.raw[i]; // 대신 raw 값을 사용하세요.
result += substitutions[i];
}
// 마지막 리터럴을 더합니다.
result += literals.raw[literals.length - 1];
return result;
}
let message = raw`Multiline\nstring`;
console.log(message); // "Multiline\\nstring"
console.log(message.length); // 17

result를 만들기 위해 위해 literalals대신에 literals.raw를 사용합니다. 즉, Unicode Code point 이스케이프를 비롯한 모든 문자 이스케이프는 Raw 형식으로 반환되어야합니다. Raw string은 이스케이프 문자를 포함해야 하는 코드가 포함된 문자열을 출력하려는 경우 유용합니다. (예를 들어, 일부 코드에 대한 설명서를 생성하려면 실제 코드가 나타나는대로 출력하고 싶을 수 있습니다.)

요약

완전한 Unicode 지원을 통해 JavaScript는 논리적 인 방식으로 UTF-16 문자를 처리할 수 있습니다. codePointAt()String.fromCodePoint()를 통해 Code point와 캐릭터 사이를 옮기는 능력은 문자열 조작을 위한 중요한 단계입니다. 정규 표현식 u 플래그를 추가하면 16-bit 문자 대신 Code point에서 연산을 수행할 수 있으며, normalize() 메서드는 보다 적절한 문자열 비교에 유용합니다.

ECMAScript 6는 문자열 작업을위한 새로운 방법을 추가하여 부모 문자열의 위치에 관계없이 하위 문자열을 보다 쉽게 식별할 수 있도록합니다. 정규 표현식에 더 많은 기능이 추가되었습니다.

Template literal은 ECMAScript 6에 중요한 추가 기능으로 문자열 작성을 쉽게하기 위해 도메인 별 언어 (DSL)를 만들 수 있습니다. 변수를 Template literal에 직접 포함시킬 수 있다는 것은 개발자가 변수가 있는 긴 문자열을 작성하기 위해 문자열 연결보다 안전한 도구라는 것을 의미합니다.

여러 줄 문자열에 대한 지원 기능이 내장되어 있어 Template literal은 일반적인 JavaScript 문자열보다 유용합니다. Template literal 내부에 개행을 직접 허용하더라도 여전히 \n과 다른 문자 이스케이프 시퀀스를 사용할 수 있습니다.

템플릿 Tag는 DSL을 생성할 때 가장 중요한 부분입니다. TagTemplate literal 조각을 인수로받는 함수입니다. 그런 다음 해당 데이터를 사용하여 적절한 문자열 값을 반환할 수 있습니다. 제공된 데이터에는 리터럴, raw 값 및 치환 값이 포함됩니다. 그런 다음 이러한 정보를 사용하여 태그의 정확한 리턴을 결정할 수 있습니다.


이 내용은 나중에 참고하기 위해 제가 공부하며 정리한 내용입니다.
의역, 오역, 직역이 있을 수 있음을 알려드립니다.
This post is a translation of this original article [https://leanpub.com/understandinges6/read#leanpub-auto-strings-and-regular-expressions]

참고

공유하기