1. Maven 프로젝트 구성 후 아래 처럼 오류가 발생하는 경우

 

Description	Resource	Path	Location	Type
cvc-elt.1.a: Cannot find the declaration of element 'project'.	pom.xml	/mvn	line 1	Language Servers

Description	Resource	Path	Location	Type
Downloading external resources is disabled.	pom.xml	/mvn	line 3	Language Servers


# 방법1

오류 부분을 마우스 over하면 나오는 tooltip에서 "Force download of 'https://maven.apache.org/xsd/maven-4.0.0.xsd'" 링크를 클릭하면 자동 다운로드 됨




# 방법2
https => http 로 변경

# 기존
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

# 변경
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">







출처 : https://noritersand.github.io/java/2013/12/05/java-%EC%9E%90%EB%B0%94-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98-reflection/

관련 문서



리플렉션이란 인스턴스로 클래스의 정보를 분석하기 위한 기법이면서, 자바에서 해당 기법을 구현한 클래스와 패키지의 통칭이다.

변수의 이름을 파라미터로 다루기

public class Test {
    String person1 = "사람1";
    String person2 = "사람2";

    public static void main(String[] args) throws Exception {
        Test test = new Test();
    }
    void getValue(String who) throws Exception {
        System.out.println("show something..");
    }
}


일반적으로는 다음처럼 가장 간단하고 고전적인 방식인, 비교에 의한 분기문으로 구현할 수 있을것이다:



String 타입의 인스턴스 변수인 person1, person2가 선언되어 있는 클래스가 있다. 그리고 그 필드의 값을 파라미터에 따라 선택하여 출력하는
 getValue() 메서드를 구현한다고 하자.

void getValue(String who) throws Exception {
    if (who.equals("person1")) {
        System.out.println(this.person1);
    } else if (who.equals("person2")) {
        System.out.println(this.person2);
    }
}


만약 분기문을 사용하지 않고 Field 클래스를 이용해 변수명을 문자열로 취급하려면 다음처럼 작성한다:

import java.lang.reflect.Field;

public class Test {
    String person1 = "사람1";
    String person2 = "사람2";

    public static void main(String[] args) throws Exception {
        Test test = new Test();
        test.getValue("person2");
    }

    void getValue(String who) throws Exception {
        Class<?> cls = this.getClass();
        Field person = cls.getDeclaredField(who);
        System.out.println(person.getName()); // person2
        System.out.println(person.get(this)); // 사람2
    }
}
Test test = new Test();
Field target = test.getDeclaredField("내가 찾아야 할 녀섴");


인스턴스의 클래스값을 getClass로 가져와 getDeclaredField 메서드를 이용하면 해당 필드에 관한 정보를 가져올 수 있다.

target.get(test);


이 정보는 Field 클래스 타입이며 여기서 getName 혹은 get을 이용하면 프로퍼티의 이름과 값을 구할 수 있다. 여기서 get메서드의 인자값으로는 같은 타입의 인스턴스 객체를 넘겨줘야한다.

setAccessible()

public void setAccessible(boolean flag)

setAccessible()은 필드나 메서드의 접근제어 지시자에 의한 제어를 변경한다.

일반적으로 private 인스턴스 변수나 메서드는 해당 클래스의 외부에서는 접근할 수 없다. 가령 다음처럼 private으로 지정된 some 변수에 접근하려고 하면 예외가 발생할 것이다.

class AccessTest {
    private String some = "yo";
    private String getSome() {
        return this.some;
    }
}

public class MainClass {
    public static void main(String[] args) throws Exception {
      AccessTest accessTest = new AccessTest();

      System.out.println(accessTest.some); // compile error: The field AccessTest.some is not visible

      Field field = accessTest.getClass().getDeclaredField("some");
      System.out.println("field: " + field.get(accessTest));
      /*
       * runtime error:
       *     java.lang.IllegalAccessException:
       *     Class MainClass can not access a member of class AccessTest with modifiers "private"
       */
    }
}

이때 setAccessible(true)를 사용하면 문제 없이 접근할 수 있게 된다:

class AccessTest {
    private String some = "yo";
    private String getSome() {
        return this.some;
    }
}

public class MainClass {
    public static void main(String[] args) throws Exception {
      AccessTest accessTest = new AccessTest();

      Field field = accessTest.getClass().getDeclaredField("some");
      field.setAccessible(true);
      System.out.println("field: " + field.get(accessTest));

//    System.out.println(accessTest.some); // 이것은 여전히 불가능하다.

      Method method = accessTest.getClass().getDeclaredMethod("getSome");
      method.setAccessible(true);
      System.out.println("method: " + method.invoke(accessTest, (Object[]) null));

//    System.out.println(accessTest.getSome()); // compile error: The method getSome() from the type AccessTest is not visible

      method.setAccessible(false);
      System.out.println("method: " + method.invoke(accessTest, (Object[]) null));
      /*
       * setAccessible(false)는 접근제어를 원래 상태로 돌려놓기 때문에 다음과 같은 런타임 에러가 발생할 것이다.
       * runtime error:
       *     java.lang.IllegalAccessException:
       *     Class PrivateVariableAcc can not access a member of class AccessTest with modifiers "private"
       */
    }
}

단, 이 방법은 reflect를 통한 필드나 메서드 접근에 한하며 접근연산자(.)를 통한 방식은 불가능하다.

example

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ReflectionTest {
    @SuppressWarnings("unused")
    private static final Logger log = LoggerFactory.getLogger(ReflectionTest.class);

    @Test
    public void test() throws Exception {
        Object instance = new MyClass();
        Class<?> clazz = instance.getClass();

        // invoke method
        Method method = clazz.getDeclaredMethod("myMethod");
        Assert.assertNotNull(method);
        Assert.assertEquals("finally you found me!", method.invoke(instance));

        // access field
        Field field = clazz.getDeclaredField("myField");
        Assert.assertNotNull(field);
        Assert.assertEquals(0, field.get(instance));
    }
}

class MyClass {
    public int myField = 0;

    public String myMethod() {
        return "finally you found me!";
    }
}



출처 : http://zero-gravity.tistory.com/270

참고 : http://www.happyjung.com/lecture/2471


집에 서버를 구성하고나서 불과 며칠이 지나지 않아 로그인 시도가 7천번 가까이 있었다. ㄷㄷ

   /var/log/secure로 접속 아이피를 찾아서 조회해보니 죄다 중국쪽으로 찍힌다..;; 우회한 건지 직접 접속 시도한 건지는 모르겠지만... 중국 정말 싫다 ......




   앗 뜨거 하고, 이래선 안되겠다는 생각에 바로 보안 설정에 들어갔다.



   1. SSH 포트 & 프로토콜 변경


vi /etc/ssh/sshd_config


# If you want to change the port on a SELinux system, you have to tell

# SELinux about this change.

# semanage port -a -t ssh_port_t -p tcp #PORTNUMBER

#

Port 1234

#AddressFamily any

#ListenAddress 0.0.0.0

#ListenAddress ::


   Port 부분을 22에서 다른 번호로 변경한다.


# The default requires explicit activation of protocol 1

Protocol 2


   Protocol을 2로 변경한다. (보안상 2로 하는 게 좋다고 한다.)




   2. root로 로그인이 불가능하도록 설정


#LoginGraceTime 2m

PermitRootLogin no

#StrictModes yes

MaxAuthTries 6

#MaxSessions 10


   PermitRootLogin을 주석 해제하고 no로 기입하면 root로는 직접 로그인이 불가능하다. root로 로그인하고 싶다면, 일반 유저로 로그인한 뒤에 "su - root"로 바꿔줘야 함.


   MaxAuthTries는 로그인 시도 횟수를 제한한다. 6으로 해놓을 경우, 6번 로그인 실패하면 자동으로 차단.



   # systemctl reload sshd.service


   설정을 마치고 SSH 서비스를 재시작한다.



# firewall-cmd --permanent --zone=public --add-port=1980/tcp

# firewall-cmd --reload


   마지막으로 1234포트 방화벽 열어주면, 1234 포트로만 SSH 접속이 가능할 것이다.





   만약 되지 않는다?


   내 경우, 여기까지 했는데 포트가 바뀌지 않았다. ㅡㅡ; 그래서 이것저것 찾아본 결과... 뭔가 더 해줘야 했다.


   내껀 semanage로 SSH 포트를 변경해줘야 완벽하게 변경이 됐다.


   CentOS를 최소 설치를 하면 "semanage" 명령어가 실행되지 않는데,


# yum install policycoreutils-python


   이럴 땐, policycoreutils-python을 설치해준다.



# semanage port -a -t ssh_port_t -p tcp 1234


   그리고나서 semanage 명령어로 SSH 포트를 아까 변경해줬던 포트 번호로 설정한다.



# semanage port -l | grep ssh


   위 명령어로 확인해보면 22번 포트가 1234로 잘 변경됐음을 확인할 수 있다.



# systemctl restart sshd.service


   다시 ssh서비스 재시작을 해주면 완료!





   3. Fail2ban 설치 (로그인 시도 아이피 차단)


   Fail2ban은 SSH 로그인 시도가 일정 수 이상일 경우 자동으로 차단해주는 녀석이다. 얼른 설치하자!


# yum install fail2ban jwhois



   설치만 한다고 해서 다 되는 건 아니고, 몇 가지 설정이 필요하다.


# vi /etc/fail2ban/jail.conf


# External command that will take an tagged arguments to ignore, e.g. <ip>,

# and return true if the IP is to be ignored. False otherwise.

#

# ignorecommand = /path/to/command <ip>

ignorecommand =     #123.111.0.444/24 이런식으로 아이피를 적어놓으면 접근 시도를 몇 번을 하든 차단되지 않는다.


# "bantime" is the number of seconds that a host is banned.

bantime  = 600    #일정 횟수를 초과하여 접근 시도 시, 접근 거부 시간. 600초


# A host is banned if it has generated "maxretry" during the last "findtime"

# seconds.

findtime  = 600    #입력한 시간 사이에 지정 횟수 초과 시 차단. 600초


# "maxretry" is the number of failures before a host get banned.

maxretry = 3    #최대 접근 횟수. 3회를 초과할 경우 접근 차단.


# Provide customizations in a jail.local file or a jail.d/customisation.local.

# For example to change the default bantime for all jails and to enable the

# ssh-iptables jail the following (uncommented) would appear in the .local file.

# See man 5 jail.conf for details.

#

# [DEFAULT]

# bantime = 3600

#

 [sshd]

enabled = true  #이걸 true로 해줘야 SSH 접근 시도 시, fail2ban이 동작함.

#

# See jail.conf(5) man page for more information


   설정을 마쳤으면,



# service fail2ban start


   서비스 재시작. 끝!



출처 : http://egloos.zum.com/einmong/v/5885619

UniteTTC-20100221_TTC폰트에서TTF추출.zip


- 사용법

# ttc 에서 ttf 추출

$> UniteTTC gulim.ttc
$> UniteTTC batang.ttc


# ttf 파일을 ttc로 묶음
$> UniteTTC gulim.ttc gulim001.TTF gulim002.TTF gulim003.TTF gulim004.TTF
$> UniteTTC batang.ttc batang001.TTF batang002.TTF batang003.TTF batang004.TTF


※ c:/windows/fonts 에서 ttc 파일에 포함되어 있는 폰트
gulim.ttc : 굴림(001), 굴림체(002), 돋움(003), 돋움체(004)
batang.ttc : 바탕(001), 바탕체(002), 궁서(003), 궁서체(004) 



[구글 어스의 메뉴 및 부가창 폰트 - 아리따 돋움]


이유도 방법도 여러가지가 있습니다. 생각날 때(화면의 저 굴림/돋움 폰트 좀 없애버리고 싶다고 느낄 때) 마다 검색 해 보고 또 따라 해 보기도 했지만, 여러가지 이유로 다시금 원상복귀시키곤 했습니다. 그러던 중 우연히 "사실 굴림체를 없애는 방법은 아주 간단합니다."라는 글을 보고, 방법을 정리해 둡니다.

굴림(체), 바탕(체) 폰트는 폰트 폴더(C:\Windows\Fonts)에 .ttc파일 형태로 제공되어 있습니다. 이는 하나의 파일에 복수의 폰트를 제공하기 위해 사용되는 TTF폰트 컬렉션 파일입니다.

- batang.ttc 파일에는 가변폭 글꼴인 바탕, 궁서, 고정폭 글꼴인 바탕체, 궁서체, 이렇게 4개의 TTF파일이 포함되어 있습니다.
- gulim.ttc 파일에는 가변폭 글꼴인 굴림, 돋움, 고정폭 글꼴인 굴림체, 돋움체 , 이렇게 4개의 TTF파일이 포함되어 있습니다.

적당한 고정폭 글꼴(특히, 셰리프 폰트)은 찾기가 어려워, 바탕은 바탕, 궁서 2종을 "아리따-부리"로, 굴림은 굴림, 돋움 2종을 "아리따-돋움"으로 바꿨습니다. 아래 보이는 UltraISO 스크린샷 처럼, 굴림으로 표기되는 프로그램들의 변화를 확인할 수 있습니다.

- Gulim & GulimChe & Dotum & DotumChe (TrueType) : gulim.ttc

- Batang & BatangChe & Gungsuh & GungsuhChe (TrueType) : batang.ttc

[# 1] 사용 도구

1) UniteTTC (TTC파일에서 TTF파일을 추출해주고, TTF파일 여러개를 TTC파일로 만들어 줍니다)
2) ttfname3 (TTF파일의 폰트 정보를 XML로 추출해주고, 추출한 폰트 정보를 다른 TTF파일에 덮어 씌워 줍니다)

[# 2] 사용 방법

UniteTTC
- TTF추출 : UniteTTC [추출할 폰트.TTC]
- TTC제작 : UniteTTC [생성할 폰트.TTC] [합칠 폰트1.TTF] [합칠 폰트2.TTF] ...

굴림 글꼴 모음 파일(gulim.ttc)에서 TTF 추출
- UniteTTC gulim.ttc
변경 완료된 폰트를 TTC파일로 제작
- UniteTTC gulim.ttc gulim001.TTF gulim002.TTF gulim003.TTF gulim004.TTF

TTFName
- TTF파일의 폰트 정보 추출 : ttfname3.exe [정보를 추출할 폰트.TTF] -o [저장될 정보 파일.XML]
- 정보파일을 원하는 폰트에 입히기 : ttfname3.exe [정보를 입힐 폰트.TTF] [폰트 정보파일.XML] -o [생성될 폰트.TTF]

추출한 굴림폰트(TTF)에서 폰트 정보를 추출. 굴림폰트(TTF)의 정보파일(XML)이 생성됨
- ttfname3.exe gulim001.TTF -o gulim001.xml
추출한 정보파일(XML)을 아리따-돋움 폰트(TTF)에 씌워 새로운 TTF파일 생성
- ttfname3.exe AritaDotumMedium.ttf gulim001.xml -o gulim001.ttf

[# 3] 다운로드

1. UniteTTC (2010.02.21) UniteTTC.zip
http://yozvox.web.fc2.com/556E697465545443.html

2. TTFName (3.0.2.0) TTFName 3.0.2.0.zip 

[# 4] TTF 추출

굴림 글꼴그룹(gulim.ttc)파일을 UniteTTC를 이용해 해체하면 gulim001.TTF / gulim002.TTF / gulim003.TTF / gulim004.TTF의 4개의 파일이 생성됩니다. 2, 4번 글꼴은 고정폭 글꼴인 굴림체와 돋움체입니다.

UniteTTC gulim.ttc

여기서는 가변폭 글꼴인 1번(굴림), 3번(돋움)의 정보를 추출합니다. ttfname을 이용해 gulim001.xml, gulim003.xml을 추출/생성합니다.

ttfname3.exe gulim001.TTF -o gulim001.xml
ttfname3.exe gulim003.TTF -o gulim003.xml


[# 5] TTF 생성

기존의 굴림, 돋움 TTF 폰트(gulim001.TTF, gulim003.TTF)는 삭제하고 아리따-돋움(AritaDotumMedium.ttf)폰트에 xml파일은 씌워 새로운 gulim001.TTF, gulim003.TTF를 생성합니다. 굴림, 돋움 모두 아리따-돋움으로 대체합니다.

del gulim001.TTF
del gulim003.TTF
ttfname3.exe AritaDotumMedium.ttf gulim001.xml -o gulim001.ttf
ttfname3.exe AritaDotumMedium.ttf gulim003.xml -o gulim003.ttf


[# 6] TTC 생성

기존의 굴림 글꼴그룹(gulim.ttc)파일을 삭제하고, 변경된 TTF파일로 새로운 TTC파일 생성합니다.

del gulim.ttc
UniteTTC gulim.ttc gulim001.TTF gulim002.TTF gulim003.TTF gulim004.TTF


[# 7] 시스템의 굴림 폰트 제거

아래와 같은 방법으로 윈도우 상의 (기존에 설치된) 굴림 폰트를 제거할 수 있습니다. (원본 파일은 C:\에 이동 저장)

takeown /F %windir%\Fonts\gulim.ttc /A
icacls %windir%\Fonts\gulim.ttc /grant Administrators:F
move %windir%\Fonts\gulim.ttc C:\


[# 8] 굴림 폰트 설치 & 폰트 캐시 제거 & 리부팅

새롭게 만든 굴림폰트(gulim.ttc)를 설치한 후, FNTCACHE.DAT파일을 제거, 리부팅을 하면 완벽하게 적용됩니다.

del C:\Windows\System32\FNTCACHE.DAT

구글어스의 메뉴폰트 바꾼게 제일 시원합니다.


Polyfill

홈페이지 : https://polyfill.io


- 특정 기능이 지원되지 않은 브라우저를 위해 사용할 수 있는 코드 조각이나 플러그인을 말함. Polyfill은 HTML5 및 CSS3를 사용하는데 있어 오래된 브라우저 사이의 간격을 메꾸는 역할을 함

자동으로 로드되는 polyfill : https://cdn.polyfill.io/v2/docs/
        

아래처럼 polyfill 을 로드하면 브라우저에 맞는 스크립트가 출력된다. 크롬이나 파이어폭스가 로딩하면 코드는 거의 없고 IE에서 로드하면 코드가 많이 나온다.

<!-- 브라우저 환경에 맞게 자동으로 스크립트 로딩 -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>

<!-- 브라우저에 상관없이 전체 소스 로딩 -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=default|always"></script>

<!-- Promise 만 로딩 -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Promise|always"></script>



HTML5 Cross Browser Polyfills
Modernizr 측에서 정리하여 제공하고 있는 각종 폴리필 목록을 참고하면 원하는 기술을 선택하는데 도움이 될 것이다.

html5shiv : HTML5의 섹셔닝 요소(예: <header>, <nav>)를 지원하지 않는 브라우저를 위한 라이브러리
mediaelement.js : HTML5의 <video>와 <audio> 요소들을 모든 브라우저에서 하나의 파일로 같은 UI를 제공하기 위한 라이브러리
Placeholder : HTML5의 플레이스홀더(placeholder)의 지원을 통일하기 위해서 제공하는 라이브러리
h5Validate : HTML5의 폼검증 기능을 구현하기 위해 제공하는 라이브러리
selectivizr : CSS3의 의사 클래스(pseudo-class)와 속성 선택자들을 IE6~8에서 지원하기 위한 라이브러리
css3pie : CSS3의 border-radius와 box-shadow, liner-gradient를 IE6~9 브라우저에서 지원하기 위한 라이브러리
Respond : 반응형 웹의 필수 속성중에 하나인 미디어쿼리를 IE6~8에서 지원하기 위한 라이브러리

더 많은 polyfill : https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills



Modernizr
- 사용자의 브라우저가 현재 가지고 있는 HTML5, CSS 기능들을 감지하고 지원여부를 판별하는 Javascript 라이브러리 임. 다양한 브라우저에서 지원되는 기능을 하나씩 확인해가면서 개발하는 것은 현실적으로 불가능하기 때문에 Modernizr와 같은 라이브러리를 통해 필요기능을 감지하고 지원 여부에 따라 개발자가 동적으로 처리를 달리할 수 있음

홈페이지 : https://modernizr.com/
Github : https://github.com/Modernizr/Modernizr


========================================================================================================

Object doesn't support property or method 'assign'

IE Object.assign 오류 시 polyfill

if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
            enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
    'use strict';
    if (target === undefined || target === null) {
    throw new TypeError('Cannot convert first argument to object');
    }
    var to = Object(target);
    for (var i = 1; i < arguments.length; i++) {
    var nextSource = arguments[i];
    if (nextSource === undefined || nextSource === null) {
    continue;
    }
    nextSource = Object(nextSource);
    var keysArray = Object.keys(Object(nextSource));
    for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
    var nextKey = keysArray[nextIndex];
    var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
    if (desc !== undefined && desc.enumerable) {
    to[nextKey] = nextSource[nextKey];
    }
    }
    }
    return to;
    }
});
}


출처 : https://chanspark.github.io/2017/07/13/%ED%81%AC%EB%A1%AC-%EC%9E%90%EB%8F%99%EC%99%84%EC%84%B1-%EA%B8%B0%EB%8A%A5-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0.html


회원가입, 로그인 폼에는 꼭 패스워드와 아이디, 이름, 주소, 카드번호, 카드 비밀번호 등등 다양한 개인정보를 작성해야하는 페이지가 존재합니다. 구글 크롬은 같은 정보를 여러 사이트에서 쉽게 입력할 수 있도록 자동완성기능(autocomplete)이란것을 지원합니다. 문제는 이 자동완성기능이 사이트에서 필요로 하지 않는 정보까지 포함하고 있기때문에 보안 문제가 많다고 하네요.크롬의 자동완성기능의 보안 문제와 해결법-영문그래서 저는 개인적으로 이 기능을 off 하고 사용하고 있지만 저희 고객분들은 그런 기능을 잘 모르는 분들이 많겠죠!! 그렇다고 일일이 고객분들께 사용하지 말아달라고 부탁할 수도 없는 노릇이고… 하니 사이트에서 자체적으로 이 기능을 block 해버리는 방법을 찾아보게 되었습니다.

방법 1 (X)

input  form autocomplete="off"라는 속성값을 부여합니다.

<input type="text" id="..." name="..." autocomplete="off">

인터넷 익스플로러에서는 이 방법이 먹히지만 크롬과 같은 최신 브라우저에서는 통하지 않습니다. 몇년전에 사용하던 방법이었는데 크롬에서 막아버렸다고 하죠. 사유는 뭐 개발자들이 무분별하게 사용해서 이를 방지하는 차원이라는데… 그냥 없애달라고 ㅠㅠㅠ

방법 2 (X)

방법 1이 안된다면, autocomplete="false" 등으로 바꿔보는 방법도 있습니다.

방법 3 (X)

방법 2도 안된다면… 비밀번호 입력칸에 autocomplete="new-password"라고 입력해서 항상 새로운 비밀번호를 입력받는다는 꼼수를 부릴 수도 있습니다.

방법 4

이쯤 와서 정말 화가나기 시작했습니다. 다른일 해야하는데 이거때문에 시간 질질끌리고…. 몇번 더 구글링한 결과 다른 방법을 찾았습니다!!!

먼저 자동완성 기능이 활성화 되는 시점은 form 이나 전체 페이지에서 처음으로 오는 <input type="password"> 요소를 찾아서 바로 앞에 위치하는 input과 자동완성 기능을 띄워주는 방식입니다. 이를 활용하면, 더미 입력폼을 만들어서 해결할 수 있습니다.

<!--remove autocomplete-->
<input style="display:none" aria-hidden="true">
<input type="password" style="display:none" aria-hidden="true">
<!--remove autocomplete end-->
<!--real input start-->
<input type="text"  autocomplete="false" required>
<input type="password" name="password" autocomplete="new-password">
<!--real input end-->

실제로 저는 이렇게 해서 효과를 봤습니다. 완벽하게는 아니지만 password 전에 위치한 인풋창에는 자동완성 기능이 뜨지 않습니다(제 원래 목표). 여전히 password인풋에는 자동완성 창이 뜨네요… ㅠㅠ

번외 - 모바일 인풋 대문자 입력 방지

모바일(특히 iOS)에서는 인풋에서 입력을 시작하면 첫 글자가 대문자로 입력이 됩니다. 하지만 아이디나 이메일 입력창에서 첫 글자가 대문자인 경우는 드물기 때문에 아래 코드릴 입력하시면 좋습니다. 은근히 UX를 개선하는 방법중 하나랄까..

<input type="email" autocapitalize="off">

참고

관련 문제에 대한 스택오버플로 질문글



출처 : http://webframeworks.kr/tutorials/translate/explanation-of-this-in-javascript-1/

참고 : https://hyunseob.github.io/2016/03/10/javascript-this/

1. this에 대한 미스터리

많은 시간 동안 this 키워드는 자바스크립트 개발자들에게 미스터리의 대상이었다. this는 강력한 기능임에도 불구하고 쉽게 이해하기 힘든 부분이다.

Java, PHP와 같은 언어에서 this는 클래스로부터 생성되는 인스턴스 중 현재 객체를 의미한다. 그 이상 그 이하도 아니다. 대부분 클래스 밖에서는 사용될 수 없으며 이러한 접근 방법으로는 혼란이 생기지 않는다.

자바스크립트에서 this는 함수의 현재 실행 문맥이다. 자바스크립트에는 4가지의 함수 실행 타입이 있기 때문이다.

  • 함수 실행: alert('Hello World!')
  • 메소드 실행: console.log('Hello World!')
  • 생성자 실행: new RegExp('\d')
  • 간접 실행: alert.call(undefined, 'Hello World!')

각각의 타입은 서로 다른 각각의 문맥을 가진다. 이 부분에서 개발자들의 예상과 다른 부분이 생긴다.

더욱이 엄격 모드 역시 실행 문맥에 영향을 미친다.

this 키워드를 이해할 수 있는 방법은 함수 실행, 함수 실행이 문맥에 어떤 영향을 미치는지에 대해 확실히 이해하는 것이다.

이 글은 실행에 대한 설명에 초점을 맞췄다. 그리고 함수 호출이 this에 어떤 영향을 미치는지, 그리고 문맥을 식별하는 과정에서 저지르는 일반적인 실수들을 보여줄 것이다.

시작하기 전에 아래의 용어들과 친숙해지자.

  • 실행이란, 함수 내부에 있는 코드를 수행하는 것이다. (쉽게 말해 함수 호출이라고 보면 됨) 예를 들어 parseInt라는 함수는 parseInt('15', 10)로 실행된다.
  • 실행 시점에서의 문맥은 함수 내부에서의 값이다.
  • 함수의 스코프는 변수, 객체, 내부 함수들의 집합이다.

2. 함수 실행

함수 실행은 함수 객체로 계산 될 표현식이 열림 괄호, 콤마로 구분되는 인자들, 그리고 닫힘 괄호와 함께 수행된다. parseInt('18')가 함수 실행의 예제다.

이 표현식은 myObject.myFunction와 같이 메소드를 실행하기 위해 사용하는 속성 접근자가 될 수 없다. 예를 들어 [1,5].join(',')는 함수 실행이 아니라 메소드 호출이다.

함수 실행에 대한 간단한 예제

function hello(name) {
  return 'Hello ' + name + '!';
}
// 함수 실행
var message = hello('World');
console.log(message); // => 'Hello World!'

hello('World')은 함수 실행이다. hello표현식은 뒤따라 오는 'World' 인자와 함께 함수 객체로 계산 될 것이다.

A more advanced example is the IIFE (immediately-invoked function expression): 심화 예제로 IIFE(즉시실행함수)가 있다.

var message = (function(name) {
   return 'Hello ' + name + '!';
})('World');
console.log(message) // => 'Hello World!'

즉시실행함수 역시 함수 실행의 종류 중 하나다. 앞 부분에 위치한 괄호 한 쌍은 함수 객체로 바뀔 표현식이고, 뒤에 위치한 괄호 한 쌍은 함수에 전달될 인자들이다. 여기에서는 'World'가 인자다.

2.1 함수 실행에서의 this

함수 실행에서의 this는 전역 객체다.

전역 객체는 실행 환경에 따라 결정된다. 웹 브라우저에서는 window 객체가 전역 객체다.

함수 실행에서의 실행 문맥은 전역 객체다.

아래의 함수 예제를 통해 문맥을 체크해보자.

function sum(a, b) {
   console.log(this === window); // => true
   this.myNumber = 20; // 전역 객체에 'myNumber'라는 속성을 추가
   return a + b;
}
// sum()은 함수 호출이다.
// sum()에서의 this는 전역 객체다. (window)
sum(15, 16);     // => 31
window.myNumber; // => 20

sum(15, 16)이 실행되자마자, 자바스크립트는 자동으로 this를 전역 객체로 가진다. 웹 브라우저 환경에서의 this는 window다.

this가 함수 스코프 밖(최상단: 전역 실행 문맥)에서 사용되었을 경우, 여기서의 this 역시 전역 객체를 참조하게 된다.

console.log(this === window); // => true
this.myString = 'Hello World!';
console.log(window.myString); // => 'Hello World!'
<!-- In an html file -->
<script type="text/javascript">
   console.log(this === window); // => true
</script>

2.2 엄격 모드에서 함수 실행에서의 this

엄격 모드에서 함수 실행에서의 this는 undefined다.

엄격 모드는 코드 안정성과 더 나은 오류 검증을 제공하기 위해 ECMA Script 5.1 버전에서 처음 소개 되었다. 엄격 모드로 작성하기 위해 함수 내부의 최상단에 'use strict'라는 예약어를 적는다. 이 모드는 실행 문맥인 this를 undefined로 만든다. 실행 문맥은 더이상 전역 객체로 되지 않고, 위의 2.1 케이스와 반대의 상황이 된다.

엄격 모드로 실행되는 예제 코드

function multiply(a, b) {
  'use strict'; // 엄격 모드
  console.log(this === undefined); // => true
  return a * b;
}
// multiply() 함수는 엄격 모드로 실행됨
// multiply()에서의 this는 undefined
multiply(2, 5); // => 10

multiply(2, 5) 함수가 실행될 때 this는 undefined다.

엄격 모드는 현재 스코프 뿐만 아니라 내부 스코프에서도 적용된다. (내부에 정의된 모든 함수에 적용됨)

function execute() {
   'use strict'; // 엄격 모드
   function concat(str1, str2) {
     // 이곳에서도 마찬가지로 엄격 모드
     console.log(this === undefined); // => true
     return str1 + str2;
   }
   // concat() 함수는 엄격 모드
   // concat() 함수 안에서의 this는 undefined
   concat('Hello', ' World!'); // => "Hello World!"
}
execute();

'use strict'은 최상단에 위치하고, 해당 스코프에 엄격 모드를 실행해준다. concat은 실행 스코프 내에 정의되어있기 때문에 엄격 모드를 상속 받는다. 그리고 concat('Hello', ' World!') 실행은 this를 undefined로 만든다.

자바스크립트 파일에는 엄격 모드, 비 엄격 모드 두 가지 모두 포함되어있다. 그래서 같은 실행 타입에서 서로 다른 모드를 적용할 수 있다.

function nonStrictSum(a, b) {
  // 비 엄격 모드
  console.log(this === window); // => true
  return a + b;
}
function strictSum(a, b) {
  'use strict';
  // 엄격 모드
  console.log(this === undefined); // => true
  return a + b;
}
// nonStrictSum() 함수는 비 엄격 모드로 실행
// nonStrictSum()에서의 this는 window 객체
nonStrictSum(5, 6); // => 11
// strictSum() 함수는 엄격 모드로 실행
// strictSum()에서의 this는 undefined
strictSum(8, 12); // => 20

2.3. 실수: 내부 함수에서의 this를 사용할 때

함수를 실행할 때 흔히 하는 실수가 외부 함수에서의 this와 내부 함수에서의 this를 동일하게 생각하는 것이다.

사실 내부 함수의 문맥은 외부 함수의 문맥에 의존되는 게 아니라 오직 실행 환경에 좌우된다. 기대하는 되로 this가 동작되려면 수정이 필요하다. (call이라 apply 메소드를 사용하는 간접 실행, 또는 바인딩 함수를 적용)

아래의 예제는 두 수의 합계를 계산해준다.

var numbers = {
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       // this는 window, 엄격 모드였으면 undefined
       console.log(this === numbers); // => false
       return this.numberA + this.numberB;
     }
     return calculate();
   }
};
numbers.sum(); // NaN, 엄격 모드였으면 TypeError

numbers.sum()은 객체 내에 있는 메소드를 실행하는 것이다. 그래서 sum 메소드 내의 문맥은 numbers 객체다. calculate 함수는 sum 내부에 정의되어있다. 그래서 아마도 calculate() 역시 this를 numbers 객체로 바라보고 있을 거라고 예상한다. 하지만 calculate()은 메소드 실행이 아닌 함수 실행이다. 그리고 이 함수에서의 this는 전역 객체인 window다. (만약, 엄격 모드였다면 undefined) 비록 외부 함수의 문맥이 numbers 객체지만, calculate 함수에는 영향을 미치지 않는다.

numbers.sum()의 실행 결과는 NaN, 혹은 엄격 모드에서 numberA 속성이 undefined이므로 접근할 수 없어서 TypeError다. calculate 함수는 제대로 실행되지 않았기 때문에 실행 결과는 기대한대로 5 + 10 = 15가 되지 않는다.

이 문제를 해결하기 위해, calculate 함수 역시 sum method와 동일한 문맥으로 되어야 한다. 그래야 numberA와 numberB 속성에 접근할 수 있기 때문이다. 해결책 중 하나로 .call 메소드를 사용하는 것이다.

var numbers = {
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       console.log(this === numbers); // => true
       return this.numberA + this.numberB;
     }
     // 문맥을 수정하기 위해 .call() 메소드를 적용
     return calculate.call(this);
   }
};
numbers.sum(); // => 15

calculate.call(this)는 이전과 동일하게 계산된다. 하지만 추가로, 첫 번째 파라미터로 들어온 인자로 실행 문맥을 수정해준다. 이제 this.numberA + this.numberB의 결과는 numbers.numberA + numbers.numberB와 동일하게 된다. 그래서 결과값도 예상했던대로 5 + 10 = 15가 된다.

3. 메소드 실행

메소드는 객체의 속성으로 있는 함수다. 예를 들어

var myObject = {
  // helloFunction is a method
  helloFunction: function() {
    return 'Hello World!';
  }
};
var message = myObject.helloFunction();

helloFunction is a method in myObject. To get the method, use a property accessor: myObject.helloFunction. 여기서 helloFunction는 myOjbect의 메소드다. 메소드에 접근하기 위해서는 myObject.helloFunction와 같은 속성 접근자를 이용하면 된다.

메소드 실행은 속성 접근자 형태의 표현식이 함수 객체로 계산되면서 실행된다. 이 표현식은 함수와 마찬가지로 열림 괄호, 함수에 전달할 인자, 닫힘 괄호의 구조다. 이전 예제를 활용하면 myObject.helloFunction()은 myObject라는 객체의 helloFunction라는 메소드 실행이다. [1, 2].join(',') 혹은 /\s/.test('beautiful world') 역시 메소드 실행의 종류다.

앞서 설명한 함수 실행과 메소드 실행이 다르다는 점은 중요하다. 왜냐하면 둘은 서로 다른 타입이기 때문이다. 둘의 가장 큰 차이점은 메소드 실행은 속성 접근자를 통해 function (.functionProperty() or 'functionProperty')를 호출한다. 반면에 함수 실행은 속성 접근자를 사용하지 않고, (())와 같이 바로 호출한다

['Hello', 'World'].join(', '); // 메소드 실행
({ ten: function() { return 10; } }).ten(); // 메소드 실행
var obj = {};
obj.myFunction = function() {
  return new Date().toString();
};
obj.myFunction(); // 메소드 실행

var otherFunction = obj.myFunction;
otherFunction();     // 함수 실행
parseFloat('16.60'); // 함수 실행
isNaN(0);            // 함수 실행

3.1 메소드 실행에서의 this

this는 메소드 실행에서 메소드를 소유하고 있는 객체다

객체 내에 있는 메소드를 실행할 때, 여기서의 this는 객체 자신이다.

숫자를 증가하는 메소드를 가진 객체를 하나 만들어보자.

var calc = {
  num: 0,
  increment: function() {
    console.log(this === calc); // => true
    this.num += 1;
    return this.num;
  }
};
// 메소드 실행. 여기서의 this는 calc.
calc.increment(); // => 1
calc.increment(); // => 2

calc.increment() 호출은 increment 함수의 문맥을 calc 객체로 만들어준다. 그래서 this.num 참조로 number 속성을 증가시하는 게 가능하도록 해준다. 자바스크립트 객체는 프로토타입에 있는 메소드를 상속 받는다. 상속 받은 메소드를 객체 내에서 실행한다면 메소드에서의 문맥은 객체 자신을 가리키게 된다.

var myDog = Object.create({
  sayName: function() {
     console.log(this === myDog); // => true
     return this.name;
  }
});
myDog.name = 'Milo';
// 메소드 실행. 여기서의 this는 myDog.
myDog.sayName(); // => 'Milo'

Object.create()는 myDog라는 새로운 객체를 만들고, 프로토타입을 설정한다. myDog 객체는 sayName이라는 메소드를 상속받는다. myDog.sayName()이 실행될 때, myDog가 실행 문맥이다.

ECMAScript 6의 class 예약어에서 메소드 실행 문맥은 위와 마찬가지로 인스턴스 자신을 가리킨다.

class Planet {
  constructor(name) {
    this.name = name;
  }
  getName() {
    console.log(this === earth); // => true
    return this.name;
  }
}
var earth = new Planet('Earth');
// 메소드 실행. 여기서의 this는 earth.
earth.getName(); // => 'Earth'

3.2 실수: 객체로부터 메소드를 분리할 때

객체 내에 있는 메소드는 별도의 변수로 분리할 수 있다. 이 변수를 통해 메소드를 호출할 때, 당신은 아마도 여기서의 this가 메소드가 정의되어있는 객체라고 생각할 것이다.

사실 객체 밖에 있는 메소드를 호출할 경우, 함수 실행을 한 결과와 같다. 함수 실행을 할 경우 this는 전역 객체인 window를 가리킨다. (엄격 모드에서는 undefined) .bind() 바인딩 함수를 사용해서 문맥을 수정할 경우, 메소드를 객체에 포함시킬 수 있다.

아래는 Animal 생성자로 myCat이라는 인스턴스를 생성하는 예제다. 그리고 setTimeout() 함수로 1초 뒤 myCat 객체의 정보를 출력한다.

function Animal(type, legs) {
  this.type = type;
  this.legs = legs;
  this.logInfo = function() {
    console.log(this === myCat); // => false
    console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  }
}
var myCat = new Animal('Cat', 4);
// "The undefined has undefined legs" 출력
// 혹은 엄격모드라면 TypeError 출력
setTimeout(myCat.logInfo, 1000);

아마도 setTimeout으로 myCat.logInfo()를 호출할 때, myCat 객체가 출력될 거라고 예상할 것이다. 하지만 setTimeout의 매개변수로 전달되었기 때문에 메소드는 객체로부터 분리 되어있고, 1초 뒤 함수 실행이 된다. logInfo가 함수로써 실행되기 때문에 여기서의 this는 전역 객체이거나 엄격 모드에서라면 undefined다. 그렇기 때문에 객체의 정보를 기대한 것대로 출력하지 못한다.

함수는 .bind 메소드를 사용해 문맥을 강제로 지정시킬 수 있다. 만약 분리된 메소드가 myCat 객체로 바인딩 된다면 이 문제는 해결된다.

function Animal(type, legs) {
  this.type = type;
  this.legs = legs;
  this.logInfo = function() {
    console.log(this === myCat); // => true
    console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  };
}
var myCat = new Animal('Cat', 4);
// "The Cat has 4 legs" 출력
setTimeout(myCat.logInfo.bind(myCat), 1000);

myCat.logInfo.bind(myCat)는 객체의 메소드가 logInfo라는 새로운 함수로 실행된다. 하지만 바인딩 메소드 덕분에 함수 실행임에도 불구하고, 여기서의 this는 myCat을 가리키게 된다.

4. 생성자 실행

생성자 실행은 표현식 앞에 new라는 키워드가 붙었을 때, 함수 객체로 계산되어 수행된다. 이 표현식은 함수와 마찬가지로 열림 괄호, 함수에 전달할 인자, 닫힘 괄호의 구조다. 예를 들어 new RegExp('\d')와 같다.

아래의 예제는 Country라는 함수가 생성자로 실행되는 내용이다.

function Country(name, traveled) {
   this.name = name ? name : 'United Kingdom';
   this.traveled = Boolean(traveled); // boolean으로 타입 변환
}
Country.prototype.travel = function() {
  this.traveled = true;
};
// Constructor invocation
var france = new Country('France', false);
// Constructor invocation
var unitedKingdom = new Country;

france.travel(); // Travel to France

new Country('France', false)은 Country 함수의 생성자 실행이다. 이것의 실행 결과는 France라는 이름을 가진 새로운 객체다. 만약 생성자에 아무런 매개 변수 없이 실행 된다면, new Country처럼 괄호가 생략되어도 된다.

ECMAScript 6에서는 생성자를 class라는 키워드로 정의할 수 있게 해준다.

class City {
  constructor(name, traveled) {
    this.name = name;
    this.traveled = false;
  }
  travel() {
    this.traveled = true;
  }
}
// 생성자 실행
var paris = new City('Paris', false);
paris.travel();

new City('Paris')은 생성자 실행이다. 이 객체에서 초기 값은 constructor라는 특수 메소드로 설정할 수 있다. 이 메소드 내에서의 this는 새로 만들어지는 객체를 바라보게 된다.

생성자 호출은 생성자의 프로토타입으로부터 속성을 상속받는 새로운 빈 객체를 만든다. 생성자 함수의 역할은 객체를 초기화하는 것이다. 이미 알고 있을지도 모르지만, 이 타입에서 this는 인스턴스를 가리킨다.

myObject.myFunction과 같은 속성 접근자가 new 키워드 뒤에 오게되면, 자바스크립트는 메소드 실행이 아닌 생성자 실행으로 계산한다. 예를 들어 new myObject.myFunction()의 경우, 첫 번째로 extractedFunction = myObject.myFunction과 같이 함수가 추출되고, 그 다음으로 new extractedFunction()와 같이 생성자 실행으로 새로운 객체가 만들어진다.

4.1 생성자 실행에서의 this

생성자 실행에서의 this는 새롭게 만들어진 객체이다.

생성자 실행에서의 문맥은 새롭게 만들어진 객체다. 생성자 실행은 객체에 초기값을 셋팅하기 위해 사용된다. 초기값 셋팅의 예로는 생성자 함수의 매개 변수로 받은 데이터, 속성을 위한 환경 변수, 이벤드 핸들러 등이 있다.

아래 예제를 통해 문맥을 체크해보자.

function Foo () {
  console.log(this instanceof Foo); // => true
  this.property = 'Default Value';
}
// 생성자 실행
var fooInstance = new Foo();
fooInstance.property; // => 'Default Value'

new Foo()은 생성자 실행이다. 여기서의 문맥은 Foo의 인스턴스가 된다. Foo의 내부에서는 초기값이 셋팅되었다. this.property는 default value라는 값을 가진다.

ES6에서 사용 가능한 class 문법 역시 같은 형식이다. 초기값 셋팅은 오직 생성자 메소드에서 할 수 있다.

class Bar {
  constructor() {
    console.log(this instanceof Bar); // => true
    this.property = 'Default Value';
  }
}
// Constructor invocation
var barInstance = new Bar();
barInstance.property; // => 'Default Value'

new Bar()가 실행되면서, 자바스크립트는 생성자 메소드를 통해 문맥을 설정한 빈 객체를 만든다. this.property = 'Default Value'와 같이 this 키워드를 통해 객체에 속성 값을 추가할 수 있다.

4.2 실수: new 깜빡할 때

몇몇 자바스크립트 함수는 생성자 실행 형태로 실행됐을 때 뿐만 아니라 함수 실행으로도 인스턴스를 생성한다. 예를 들어 RegExp가 있다.

var reg1 = new RegExp('\\w+');
var reg2 = RegExp('\\w+');

reg1 instanceof RegExp;      // => true
reg2 instanceof RegExp;      // => true
reg1.source === reg2.source; // => true

new RegExp('\w+')와 RegExp('\w+')가 실행될 때, 자바스크립트는 동일한 정규식 객체를 생성한다.

객체 생성을 위해 함수 실행을 사용하는 것(팩토리 패턴을 제외)은 잠재적 문제를 만들게 된다. 왜냐하면 new 키워드가 생략되었을 때 생성자 함수는 객체를 초기화하는 로직을 생략할지도 모른다.

아래는 해당 문제점이 나타나는 예제다.

function Vehicle(type, wheelsCount) {
  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}
// Function invocation
var car = Vehicle('Car', 4);
car.type;       // => 'Car'
car.wheelsCount // => 4
car === window  // => true

Vehicle은 타입과 바퀴개수 속성을 가지는 객체를 만들어주는 함수다.

Vehicle('Car', 4)를 실행하게 되면 객체가 반환된다. 이 객체는 올바른 속성을 가지고 있다. car.type으로 'Car'를, car.wheelCount로 4를 나타낸다. 아마도 초기값을 가진 새로운 객체가 잘 생성되었으리라 예상할 것이다.

하지만 여기서의 this는 함수 실행이 되므로 window 객체를 가리키게 된다. 그리고 Vehicle('Car', 4)은 속성을 window 객체에 추가한다. 잘못된 사용이다. 새로운 객체가 만들어지지 않았다.

생성자를 호출할 때에는 꼭 new 연산자를 사용해야 한다.

function Vehicle(type, wheelsCount) {
  if (!(this instanceof Vehicle)) {
    throw Error('Error: Incorrect invocation');
  }
  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}
// 생성자 실행
var car = new Vehicle('Car', 4);
car.type               // => 'Car'
car.wheelsCount        // => 4
car instanceof Vehicle // => true

// 함수 실행. 잘못된 방식.
var brokenCar = Vehicle('Broken Car', 3);

new Vehicle('Car', 4)은 정상 동작한다. 초기 값을 가진 새로운 객체가 생성되었다. 왜냐하면 생성자 실행 앞에 new 키워드를 썼기 때문이다.

검증하는 방법은 생성자 함수에 추가되어 있다. this instanceof Vehicle로 실행 문맥으로 올바른 객체 타입이 맞는지 체크한다. 만약 여기서의 this가 Vehicle이 아니라면 에러가 발생한다. 이와 같은 경우 만약 Vehicle('Broken Car', 3)가 new 키워드 없이 실행된다면, 올바른 실행이 아니라는 에러 메세지를 반환하게 된다.

참고 : http://ujinbot.blogspot.kr/2013/10/blog-post.html


한글 자모 유니코드 : http://www.hipenpal.com/tool/characters-to-unicode-charts-in-korean.php?unicode=94
한글 글자 유니코드 : http://www.hipenpal.com/tool/characters-to-unicode-charts-in-korean.php?unicode=110&nowpage=1

유니코드 변환 테스트 : http://kor.pe.kr/convert.htm

* 이 글은 Mathias Bynens가 쓴 JavaScript has a Unicode problem을 번역한 내용입니다.

자바스크립트가 유니코드를 처리하는 방식은 참... 놀랍다고 할 수 있습니다. 이 글에서는 자바스크립트의 유니코드와 관련된 난점들을 설명하고 흔히 겪는 문제에 대한 해결책을 제시하고자 합니다. 다가오는 ES6가 어떻게 상황을 개선시켰는지에 대해서도 설명합니다.

주의: 이런 말은 정말 하기 싫지만 이 문서는 파이어폭스, 사파리, IE 등 그림문자(emoji)를 렌더링할 수 있는 브라우저에서 보시는 게 좋습니다. OS X의 블링크 브라우저(크롬/오페라)는 이런 글자들을 전혀 렌더링하지 않기 때문에 이 페이지의 코드 예제 일부를 알아보기가 어려울 수 있습니다. 경고했습니다!

유니코드 기본 개념

자바스크립트를 자세히 들여다보기 전에, 먼저 유니코드에 관해 알고 있는 것들을 함께 확실히 해두죠.

유니코드가 뭔지 가장 쉽게 설명하자면, 여러분이 생각할 수 있는 어떤 기호든 코드포인트라 불리는 숫자와 유일한 이름에 매핑시켜주는 데이터베이스라고 할 수 있습니다. 덕분에 특정 기호를, 그 기호를 실제로 사용하지 않고도 쉽게 언급할 수 있는 거죠. 예를 들면

코드포인트는 보통 0을 붙여서 최소 네자리를 가지는 16진수로 표현하고 앞에 U+를 붙입니다.

코드포인트 값은 U+0000에서 U+10FFFF까지 쓸 수 있습니다. 110만 개 이상의 기호를 쓸 수 있는 거죠. 유니코드는 이 코드포인트 범위를 17개의 평면(plane)들로 나누어 정리해놓았습니다. 각각의 평면들은 약 6만 5천자로 이루어집니다.

첫번째 평면은 다국어 기본 평면(Basic Multilingual Plane) 또는 BMP라고 불립니다. 자주 사용되는 글자 대부분이 들어있는 가장 중요한 평면입니다. 대개의 경우 영문 텍스트 문서에 한해서라면 BMP에 들어있지 않은 코드포인트를 쓸 일은 거의 없을 겁니다. 다른 유니코드 평면과 마찬가지로 약 6만 5천자를 묶어놓았습니다.

BMP를 제외하면 약 1백만 자 정도 남았죠. 이 코드포인트가 속한 평면들은 보충 평면들(supplementary planes) 또는 아스트랄 평면들(astral planes)이라고 불립니다.

아스트랄 코드포인트는 쉽게 알아볼 수 있습니다. 코드포인트를 표현하는 16진수가 4자리를 넘어가면 아스트랄 코드포인트입니다.

자 이제 유니코드에 대한 기본적인 이해를 갖췄습니다. 이제 이것이 자바스크립트의 문자열 처리에 어떻게 적용되는지 보겠습니다.

이스케이프 시퀀스

이런 걸 보셨을 거에요.

>> '\x41\x42\x43'
'ABC'
>> '\x61\x62\x63'
'abc'

16진수 이스케이프 시퀀스라고 부릅니다. 대응하는 코드포인트를 가리키는 두 자리 16진수로 이루어져 있죠. 즉 \x41U+0041 라틴 대문자 A를 가리킵니다. 이 이스케이프 시퀀스는 U+0000부터 U+00FF까지의 코드포인트에 사용할 수 있습니다.

다음과 같은 형식의 이스케이프도 많이 사용됩니다.

>> '\u0041\u0042\u0043'
'ABC'
>> 'I \u2661 JavaScript!'
'I ♡ JavaScript!'

이것은 유니코드 이스케이프 시퀀스라고 부릅니다. 4자리의 16진수로 코드포인트를 표현합니다. \u2661U+2661 흰색 하트 수트가 되는 거죠. U+0000부터 U+FFFF까지, 즉 다국어 기본 평면에 들어있는 모든 코드포인트에 사용할 수 있습니다.

하지만 나머지 아스트랄 평면들은 어쩌죠? 이 코드포인트를 가리키려면 16진수가 4자리 이상 필요한데요. 나머지 글자들은 어떻게 이스케이프해야 할까요?

ES6에서는 쉬워요. 유니코드 코드포인트 이스케이프라는 새로운 형식의 이스케이프 시퀀스를 도입했기 때문입니다.

>> '\u{41}\u{42}\u{43}'
'ABC'
>> '\u{1F4A9}'
'💩' // U+1F4A9 개똥

위와 같이 중괄호 안에 16진수를 6자리까지 쓸 수 있기 때문에 모든 유니코드 코드포인트를 사용하는 데 충분합니다. 따라서 이 이스케이프 시퀀스를 쓰면 어떤 유니코드 기호든 코드포인트를 사용해 간단히 이스케이프할 수 있어요.

ES5와 구환경 하위호환시 쓸 수 있는 유감스러운 대책은 대체쌍(surrogate pairs)을 쓰는 것인데요.

>> '\uD83D\uDCA9'
'💩' // U+1F4A9 개똥

이 경우에는 각각의 이스케이프가 코드포인트의 대체물을 반씩 가리키는 거에요. 대체쌍 반쪽 두 개가 하나의 아스트랄 기호를 이루게 됩니다.

대체 코드포인트는 원본 코드포인트랑 전혀 다르게 생겼다는 데 주의하세요. 주어진 아스트랄 코드포인트의 대체쌍을 계산하는 공식이 있어요. 거꾸로 대체쌍을 가지고 아스트랄 코드포인트를 얻어내는 것도 가능하고요.

대체쌍을 사용하면 U+010000부터 U+10FFFF까지 모든 아스트랄 코드포인트를 표현할 수 있습니다...만 BMP 글자를 가리킬 때는 하나의 이스케이프를 사용하고 아스트랄 글자에는 두 개의 이스케이프를 사용한다는 개념 자체가 좀 당황스럽죠. 이것 때문에 짜증나는 일도 많이 생깁니다.

자바스크립트 문자열에서 글자 수 세기

예를 들어 주어진 문자열에서 글자 수를 센다고 합시다. 어떻게 하시겠어요?

일단은 간단하게 length 프로퍼티를 사용하겠죠.

>> 'A'.length // U+0041 라틴 대문자 A
1

>> 'A' == '\u0041'
true

>> 'B'.length // U+0042 라틴 대문자 B
1

>> 'B' == '\u0042'
true

위 예제에서 문자열의 length 프로퍼티는 글자 수를 나타냅니다. 자연스러운 일이죠. 이스케이프 시퀀스를 사용해서 이 글자들을 표현한다면, 글자 하나당 하나씩의 이스케이프가 있으면 됩니다. 하지만 항상 이런 건 아니에요! 약간 다른 예제를 보시죠.

>> '𝐀'.length // U+1D400 수학 볼드체 대문자 A
2

>> '𝐀' == '\uD835\uDC00'
true

>> '𝐁'.length // U+1D401 수학 볼드체 대문자 B
2

>> '𝐁' == '\uD835\uDC01'
true

>> '💩'.length // U+1F4A9 개똥
2

>> '💩' == '\uD83D\uDCA9'
true

내부적으로 자바스크립트는 아스트랄 기호를 대체쌍으로 표현합니다. 그리고 이 대체쌍의 반쪽들을 각각 별개의 '글자'들로 취급해요. ES5-호환 방식의 이스케이프 시퀀스만으로 글자를 표현한다면, 아스트랄 기호 하나마다 두 개의 이스케이프가 필요한 거에요. 이게 엄청 헷갈려요. 사람들은 당연히 유니코드 글자들 기준으로 내지는 자소 단위로 생각하니까요.

아스트랄 기호 계산

다시 질문으로 돌아갑시다. 자바스크립트 문자열의 글자 수를 제대로 세려면 어떻게 해야 할까요? 대체쌍을 적절한 방법으로 계산한 다음, 하나의 쌍을 한 글자로 세면 됩니다. 다음과 같은 방법을 사용할 수 있습니다.

var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
function countSymbols(string) {
    return string
        // 모든 대체쌍을 BMP 기호로 교체하고
        .replace(regexAstralSymbols, '_')
        // length를 구한다
        .length;
}

Punycode.js를 사용하신다면 (Node.js에 들어있어요) 제공되는 유틸리티 메서드를 사용해 자바스크립트 문자열과 유니코드 코드포인트를 상호 변환할 수 있습니다. punycode.ucs2.decode 메서드는 문자열을 받아 유니코드 코드포인트의 배열을 반환해줍니다. 배열의 요소 하나가 글자 하나에 대응합니다.

function countSymbols(string) {
    return punycode.ucs2.decode(string).length;
}

어느 쪽을 사용하든 이제 코드포인트를 제대로 계산하여 정확한 결과를 얻을 수 있게 되었습니다.

>> countSymbols('A') // U+0041 라틴 대문자 A
1

>> countSymbols('𝐀') // U+1D400 수학 볼드체 대문자 A
1

>> countSymbols('💩') // U+1F4A9 개똥
1

똑같이 생긴 글자 계산

하지만 더 따지고 들면 문자열의 글자 수 세기 문제는 훨씬 더 복잡합니다. 다음 예제를 보세요.

>> 'mañana' == 'mañana'
false

자바스크립트는 두 문자열이 다르다고 말하지만 보기엔 똑같아요! 어떻게 된 일일까요?

제가 만든 자바스크립트 이스케이프 도구의 설명에 따르면 이유는 이렇습니다.

>> 'ma\xF1ana' == 'man\u0303ana'
false

>> 'ma\xF1ana'.length
6

>> 'man\u0303ana'.length
7

첫번째 문자열에는 U+00F1 틸데가 붙은 라틴 소문자 N이 들어있는데, 두번째 문자열에서는 같은 글자를 만들기 위해 두 개의 코드포인트 즉 U+006E 라틴 소문자 NU+0303 틸데 결합자를 사용했습니다. 두 글자가 동일하지 않고 length 값도 다른 것은 이 때문입니다.

어쨌거나 사람과 동일하게 이 문자열에서 글자 수를 센다면 답은 두 문자열 모두 6이 되어야 합니다. 각각의 문자열에서 구별되는 글자가 6개니까요.

ES6에서는 꽤 간단합니다.

function countSymbolsPedantically(string) {
    // 모양이 같은 글자들을 NFC로 정규화한다 (정준 분해한 뒤에 다시 정준 결합)
    var normalized = string.normalize('NFC');
    // 아까처럼 아스트랄 기호/대체쌍을 계산한다
    return punycode.ucs2.decode(normalized).length;
}

String.prototype에 포함된 normalize 메서드가 이러한 차이들을 고려하여 유니코드 정규화를 해줍니다. 하나의 글자를 표현하는 코드포인트 뒤에 결합기호가 붙어있으면 이것을 하나의 코드포인트 형태로 정규화해주는 거죠.

>> countSymbolsPedantically('mañana') // U+00F1
6

>> countSymbolsPedantically('mañana') // U+006E + U+0303
6

ES5와 구 환경에서의 하위 호환을 위해서는 String.prototype.normalize 폴리필을 사용할 수 있습니다.

나머지 결합자 계산

하지만 여전히 완벽하진 않습니다. 코드포인트에 결합자를 여러 개 붙이면, 시각적으로는 항상 한 글자가 되지만 정규화된 형태가 존재하지 않을 수도 있어요. 즉 정규화로 해결할 수 없는 거죠.

>> 'q\u0307\u0323'.normalize('NFC') // q̣̇
'q\u0307\u0323'

>> countSymbolsPedantically('q\u0307\u0323')
3 // 1이어야 함

>> countSymbolsPedantically('Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞');
74 // 6이어야 함

보다 정확한 계산이 필요하다면 정규식으로 입력 문자열에서 이런 결합자들을 모두 제거하면 됩니다.

// export-data.js로 생성한 정규식
var regexSymbolWithCombiningMarks = /([\0-\u02FF\u0370-\u1DBF\u1E00-\u20CF\u2100-\uD7FF\uDC00-\uFE1F\uFE30-\uFFFF]|[\uD800-\uDBFF]
[\uDC00-\uDFFF]|[\uD800-\uDBFF])([\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]+)/g; function countSymbolsIgnoringCombiningMarks(string) { // 결합자 기호를 제거하고 원래 글자만 남긴다 var stripped = string.replace(regexSymbolWithCombiningMarks, function($0, symbol, combiningMarks) { return symbol; }); // 아까처럼 아스트랄 기호/대체쌍을 계산한다 return punycode.ucs2.decode(stripped).length; }

이 함수는 결합자는 모두 제외하고 결합자가 속해있는 글자들만 남깁니다. 결합자가 아닌 것들은 건드리지 않습니다. 이 방법은 심지어 ES3 환경에서도 동작합니다. 지금까지 얘기된 것 중 가장 정확한 결과를 제공하죠.

>> countSymbolsIgnoringCombiningMarks('q\u0307\u0323')
1

>> countSymbolsIgnoringCombiningMarks('Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞')
6

자바스크립트에서 문자열 뒤집기

비슷한 문제가 또 있어요. 자바스크립트에서 문자열 뒤집기입니다. 이게 뭐 얼마나 어렵겠어요, 그죠? 흔히 쓰이는, 아주 간단한 방법은 다음과 같습니다.

// 소박한 방법
function reverse(string) {
    return string.split('').reverse().join('');
}

대부분의 경우 잘 동작하는 것처럼 보입니다.

>> reverse('abc')
'cba'

>> reverse('mañana') // U+00F1
'anañam'

하지만 결합자나 아스트랄 기호가 포함되면 문자열이 완전 엉망이 돼요.

>> reverse('mañana') // U+006E + U+0303
'anãnam' // 틸데가 n이 아니라 a에 적용됨
>> reverse('💩') // U+1F4A9
'��' // 💩의 대체쌍 순서가 맞지 않음

다행히도 미시 엘리엇이라는 똑똑한 컴퓨터 과학자가 이 문제를 해결할 빈틈 없는 알고리즘을 생각해냈습니다. (랩퍼 미시 엘리엇의 'Work It' 가사임ㅎㅎ)

난 내 꺼 내려놓고, 뒤집고, 거꾸로 하지. 난 내 꺼 내려놓고, 뒤집고, 거꾸로 하지.
(I put my thang down, flip it, and reverse it. I put my thang down, flip it, and reverse it.)

실로 그렇습니다. 문자열을 처리하기 전에 특정 문자에 속한 결합자들의 위치를 모두 맞바꾸고, 대체쌍 순서도 모두 뒤집으면 이 문제를 성공적으로 해결할 수 있습니다. 고마워요, 미시!

// Esrever 사용
>> esrever.reverse('mañana') // U+006E + U+0303
'anañam'
>> esrever.reverse('💩') // U+1F4A9
'💩' // U+1F4A9

문자열 메서드에서 유니코드 관련 문제들

이러한 동작 방식은 문자열의 다른 메서드에도 영향을 미칩니다.

코드포인트를 글자로 변환하기

String.fromCharCode 메서드를 사용해 유니코드 코드포인트로부터 문자열을 얻어낼 수 있는데요. 단 BMP 범위인 U+0000에서 U+FFFF 내의 코드포인트에 대해서만 제대로 동작합니다. 아스트랄 코드포인트에 이 메서드를 사용하면 예상 밖의 결과가 나와요.

>> String.fromCharCode(0x0041) // U+0041
'A' // U+0041
>> String.fromCharCode(0x1F4A9) // U+1F4A9
'' // U+1F4A9이 아니라 마지막 4자리인 U+F4A9이 반환됨

이 문제를 회피하려면 대체쌍 각각의 코드포인트를 직접 계산해서 별개의 인자로 전달하는 방법밖에 없습니다.

>> String.fromCharCode(0xD83D, 0xDCA9)
'💩' // U+1F4A9

대체쌍 반쪽짜리들 계산으로 골치 아프기 싫다면 Punycode.js에 다시 한번 기대봅니다.

>> punycode.ucs2.encode([ 0x1F4A9 ])
'💩' // U+1F4A9

다행히 ES6에는 String.fromCodePoint(codePoint)라는 게 도입되어서 아스트랄 기호들을 제대로 처리해줍니다. 이 메서드는 모든 유니코드 코드포인트, 즉 U+000000부터 U+10FFFF까지 모두 사용할 수 있습니다.

>> String.fromCodePoint(0x1F4A9)
'💩' // U+1F4A9

ES5와 구 환경에서의 하위 호환을 위해서는 String.fromCodePoint() 폴리필을 사용하시고요.

문자열에서 특정 글자 얻기

개똥 글자가 들어있는 문자열에서 String.prototype.charAt(position)을 사용해 첫번째 글자를 얻어내려고 하면, 글자 하나가 아니라 대체쌍 반쪽밖에 가져오지 못합니다.

>> '💩'charAt(0) // U+1F4A9
'\uD83D' // U+D83D, U+1F4A9의 대체쌍 중 첫번째 반쪽

ES6에는 String.prototype.at(position)의 도입이 제안된 상태입니다. charAt과 똑같으면서 필요한 경우에는 대체쌍 반쪽이 아닌 글자 전체를 처리해주는 것이죠.

>> '💩'at(0) // U+1F4A9
'💩' // U+1F4A9

ES5와 구 환경에서의 하위 호환을 위해서는 String.prototype.at() 폴리필을 쓸 수 있습니다

문자열에서 특정 코드포인트 얻기

String.prototype.charCodeAt(position)도 비슷합니다. 아까의 문자열에 이 메서드를 사용해 첫번째 글자의 코드포인트를 가져오려 하면 개똥 글자의 코드포인트가 아닌 그 대체쌍의 첫번째 반쪽에 대한 코드포인트가 반환됩니다.

>> '💩'charCodeAt(0)
0xD83D

다행히 ES6에는 String.prototype.codePointAt(position)가 도입되어서, charCodeAt와 똑같으면서 필요할 때는 대체쌍 반쪽이 아닌 글자 전체를 처리해줍니다.

>> '💩'codePointAt(0)
0x1F4A9

ES5와 구 환경에서의 하위 호환을 위해서는 String.prototype.codePointAt() 폴리필이 있습니다.

문자열 내 모든 글자 순회

문자열 내 모든 글자를 순회하면서 각각의 글자에 대해 어떤 작업을 하고 싶다고 해봅시다.

ES5에서는 대체쌍을 계산하기 위해 상당량의 상용코드(boilerplate code)를 작성해야만 합니다.

function getSymbols(string) {
    var length = string.length;
    var index = -1;
    var output = [];
    var character;
    var charCode;
    while (++index < length) {
          character = string.charAt(index);
          charCode = character.charCodeAt(0);
          if (charCode >= 0xD800 && charCode <= 0xDBFF) {
              // 주의: 여기서 대체쌍 반쪽자리 하나만 존재하는 경우는 계산하지 못함
              output.push(character + string.charAt(++index));
          } else {
              output.push(character);
          }
    }
    return output;
}

var symbols = getSymbols('💩');
symbols.forEach(function(symbol) {
    assert(symbol == '💩');
});

ES6에서는 간단히 for … of문만 쓰면 됩니다. 문자열 반복문이 대체쌍 대신 온전한 글자를 처리해줍니다.

for (let symbol of '💩') {
    assert(symbol == '💩');
}

하지만 for … of 문은 문법차원의 요소이기 때문에 여기에 대해서는 폴리필이 없어요.

그 밖의 문제들

이 동작 방식은 사실 모든 문자열 메서드에 영향을 미칩니다. String.prototype.substring, String.prototype.slice 등 여기에 명시적으로 언급하지 않은 것들이 다 포함되죠. 그러니까 주의해서 사용하시길 바랍니다.

정규식에서의 문제들

코드포인트와 유니코드 스칼라 값에 매치시키기

정규식의 . 연산자는 '글자' 한 개에만 매칭됩니다. 하지만 자바스크립트는 대체쌍 반쪽을 각각 하나의 '글자'로 계산하기 때문에, 이 연산자는 아스트랄 기호에는 절대 매치되지 않아요.

>> /foo.bar/.test('foo💩bar')
false

잠시 생각을 해보죠... 모든 유니코드 글자에 매치시키기 위해 사용할 수 있는 정규식이 뭘까요? 떠오르세요? 보셨다시피 . 연산자로는 부족합니다. 여기에는 개행문자나 아스트랄 기호 전체가 매치되지 않으니까요.

>> /^.$/.test('💩')
false

개행문자에도 매치시키기 위해 [\s\S]를 쓸 순 있죠. 하지만 여전히 아스트랄 기호 전체는 매치되지 않습니다.

>> /^[\s\S]$/.test('💩')
false

보면 아시겠지만 모든 유니코드 코드포인트에 매치되는 정규표현식은 전혀 간단하지가 않아요.

>> /^[\0-\uD7FF\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF]$/.test('💩') // 이게 뭡니까 세상에
true

당연히 이런 정규식을 직접 쓰고 싶진 않으시겠죠. 디버깅이라면 더 말할 것도 없고요. 위의 정규식을 만들어내기 위해 저는 Regenerate을 사용했습니다. 코드포인트나 글자 목록을 가지고 정규식을 쉽게 생성해주는 라이브러리에요.

>> regenerate.fromCodePointRange(0x0, 0x10FFFF)
'[\0-\uD7FF\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF]'

왼쪽에서 오른쪽 순으로, 이 정규식은 BMP 기호, 아스트랄 기호의 대체쌍들, 그리고 대체쌍 반쪽짜리들에 매치됩니다.

대체쌍 반쪽짜리는 자바스크립트 문자열에서 기술적으로는 가능하지만 그 자체로는 어떠한 글자에도 매핑되지 않기 때문에 쓰면 안됩니다. 유니코드 스칼라 값이라는 용어는 대체 코드포인트를 제외한 나머지 모든 코드포인트를 가리킵니다. 유니코드 스칼라 값에 매치되는 정규식은 다음과 같이 생성할 수 있습니다.

>> regenerate()
     .addRange(0x0, 0x10FFFF)     // 모든 유니코드 코드포인트
     .removeRange(0xD800, 0xDBFF) // 첫번째 대체쌍(high surrogate) 제외
     .removeRange(0xDC00, 0xDFFF) // 두번째 대체쌍(low surrogate) 제외
     .toRegExp()
/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/

Regenerate은 빌드 스크립트 안에서 사용하도록 개발되었습니다. 복잡한 정규식을 생성해내면서도 해당 스크립트 자체는 높은 가독성을 유지하고 유지보수하기도 쉽도록 말이죠.

ES6에서는 희망적이게도 u 플래그가 도입됩니다. 이 플래그를 사용하면 . 연산자에 대체쌍 반쪽짜리를 제외한 나머지 모든 코드포인트가 매치됩니다.

>> /foo.bar/.test('foo💩bar')
false

>> /foo.bar/u.test('foo💩bar')
true

u 플래그를 설정하면 . 연산자는 다음의 하위호환 정규식 패턴과 동일하게 동작하게 됩니다.

>> regenerate()
     .addRange(0x0, 0x10FFFF) // 모든 유니코드 코드포인트
     .remove(  // 개행문자 제외
       0x000A, // 라인 피드 <LF>
       0x000D, // 캐리지 리턴 <CR>
       0x2028, // 행 구분자 <LS>
       0x2029  // 단락 구분자 <PS>
     )
     .toString();
'[\0-\x09\x0B\x0C\x0E-\u2027\u202A-\uD7FF\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF]'

>> /foo(?:[\0-\x09\x0B\x0C\x0E-\u2027\u202A-\uD7FF\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF])bar/u.test('foo💩bar')
true

문자 클래스의 아스트랄 범위

/[a-c]/라는 정규식은 U+0061 라틴 소문자 A부터 U+0063 라틴 소문자 C까지의 글자에 매치됩니다. 그렇다면 /[💩-💫]/U+1F4A9 개똥부터 U+1F4AB 현기증 기호까지 모든 기호에 매치되어야 하는데, 실제로는 그렇지가 않아요.

>> /[💩-💫]/
SyntaxError: Invalid regular expression: Range out of order in character class

이런 결과가 나오는 이유는 이 정규식이 다음 정규식과 동일하기 때문입니다.

>> /[\uD83D\uDCA9-\uD83D\uDCAB]/
SyntaxError: Invalid regular expression: Range out of order in character class

우리가 기대한 것은 U+1F4A9, U+1F4AA, U+1F4AB에 매치되는 건데 이 정규식은 다음과 같이 동작합니다.

  • U+D83D(첫 글자의 high surrogate)를 찾는다. 없으면...
  • U+DCA9(첫 글자의 low surrogate)에서 U+D83D(두번째 글자의 high surrogate)를 찾는다. (범위의 시작 코드포인트 값이 종료 코드포인트 값보다 크기 때문에 유효하지 않음) 없으면...
  • U+DCAB(두번째 글자의 low surrogate)를 찾는다.

ES6에서는 다시 한번 마법의 /u로 좀더 이치에 맞는 동작을 선택할 수 있습니다.

>> /[💩-💫]/u.test('💩') // U+1F4A9에 매치됨
true
>> /[💩-💫]/u.test('💪') // U+1F4AA에 매치됨
true
>> /[💩-💫]/u.test('💫') // U+1F4AB에 매치됨
true

하지만 이 방법은 ES5와 구 환경에 하위호환은 되지 않습니다. 하위호환을 위해서는 Regenerate을 사용해 ES5-호환 정규식을 생성하여 아스트랄 범위 또는 아스트랄 글자를 처리해야 합니다.

>> regenerate.fromSymbolRange('💩', '💫')
'\uD83D[\uDCA9-\uDCAB]'
>> /^\uD83D[\uDCA9-\uDCAB]$/.test('💩') // U+1F4A9에 매치됨
true
>> /^\uD83D[\uDCA9-\uDCAB]$/.test('💪') // U+1F4AA에 매치됨
true
>> /^\uD83D[\uDCA9-\uDCAB]$/.test('💫') // U+1F4AB에 매치됨
true

실무에서 버그 회피

이 동작방식 때문에 문제될 수 있는 것들이 많은데요. 예를 들면 트위터에서는 하나의 트윗에 140자까지 허용되고 백엔드는 아스트랄 기호를 포함한 모든 글자를 받아들입니다. 하지만 트위터 사이트의 자바스크립트 카운터는 대체쌍을 고려하지 않고 문자열의 length 프로퍼티만 읽었기 때문에 아스트랄 기호를 70자 이상 입력할 수가 없었어요. (지금은 이 문제가 수정되었습니다.)

문자열을 처리하는 수많은 자바스크립트 라이브러리들이 아스트랄 기호를 제대로 계산하지 못하고 있습니다.

Countable.js가 출시되었을 때도 아스트랄 기호를 제대로 세지 못했고요.

Underscore.string 역시 reverse 구현에서 유니코드 결합자나 아스트랄 기호를 제대로 처리하지 못하고 있습니다. (그러니 미시 엘리엇의 알고리즘을 사용하세요.)

&#x1F4A9;와 같이 아스트랄 기호를 표기하는 HTML 숫자 엔티티도 적절하게 디코드하지 못하고요. 다른 HTML 엔티티 변환 라이브러리들도 비슷한 문제를 가지고 있습니다.

(이 문제들이 수정되기 전까지는 HTML 인코딩/디코딩이 필요할 때 he를 사용해보세요.)

이런 실수들은 모두 쉽게 발생할 수 있습니다. 말하자면 결국 자바스크립트가 유니코드를 다루는 방식 자체가 밉상맞아요. 이 글에서는 문제가 생겼을 때 해결책들을 쭉 설명했는데요, 그럼 문제를 예방하려면 어떻게 해야 할까요?

개똥 테스트™를 소개합니다

자바스크립트 코드로 문자열이나 정규식을 다룰 일이 있다면 개똥 글자(💩)가 포함된 문자열을 처리하는 유닛테스트를 추가하세요. 그리고 뭔가 잘못되는 게 없나 확인해보는 거죠. 아스트랄 기호가 지원되고 있는지 확인하는 빠르고 재미있고 쉬운 방법입니다. 그리고 코드에서 유니코드 관련된 버그가 발견되면, 이 글에서 설명한 테크닉을 적용해 수정하시면 됩니다.

일반적인 유니코드 지원을 확인하는 데 좋은 테스트 문자열은 다음과 같습니다.

Iñtërnâtiônàlizætiøn☃💩

첫 20자는 U+0000 ~ U+00FF 범위 내의 글자들이고, 그 다음에는 U+0100 ~ U+FFFF의 글자, 그리고 마지막으로 U+010000 ~ U+10FFFF 범위 내의 아스트랄 기호가 들어있어요.

한 줄 요약 : 나가서 개똥글자가 들어있는 pull request를 올리세요. 유니코드를 통해 웹을 전진®시키는 유일한 방법입니다.

일러두기 : 이 글은 ES6의 최신버전 초안 및 자바스크립트의 유니코드 지원을 개선하고자 하는 다양한 앞잡이들과 제안서에 기반해 쓰여졌습니다. 여기 언급된 새로운 기능들 중 일부는 실제로 ES6 최종 명세에 포함되지 않을 수 있습니다.



window.open 설명 : https://www.w3schools.com/jsref/met_win_open.asp

window 객체 설명 (코딩의 시작) : http://www.tcpschool.com/javascript/js_bom_window


* Center로 띄우기

var popWidth = 495;
        var popHeight = 372;
        var popLeft = (screen.width / 2) - (popWidth / 2);
        var popTop = (screen.height / 2) - (popHeight / 2);

/*
window.screenLeft / window.screenTop
window.screenX / window.scrrenY

var scnLeft = window.screenLeft ? window.screenLeft : window.screenX;
var scnTop = window.screenTop ? window.screenTop : window.screenY;

screenLeft / screenTop 은 브라우저 Left / Top이 왼쪽으로부터 얼마나 떨어져 있는지 값이다.
듀얼 스크린인 경우 첫번째 스크린은 왼쪽부터 떨어져 있는 px이고 두번째 스크린은 첫번째 스크린으로부터 떨여져 있는 px 값이다.
center로 맞출 때는 해당 값을 사용하면 현재 브라우저의 가운데로 위치하게 된다.
*/

        var options = 'width=' + popWidth + ',height=' + popHeight + ',left=' + popLeft + ',top=' + popTop + ',location=no,menubar=no,resizable=no,status=no,toolbar=no';
        window.open(mobileUrl, 'mobileAppDownload', options);






===================================================================================================================================



팝업 (자식창) 에서 호출한 페이지 (부모창) 닫기



출처 : http://blog.naver.com/PostView.nhn?blogId=innoc99&logNo=140055334056


우선 JavaScript의 Window객체에는 Open()과 Close()라는 함수가 있다..

각각의 함수는 (다들 아시겠지만) 이름대로 브라우저 창을 열고 닫아 주는 역할을 한다.

Open()함수는 여기서 주가 아니므로 간단히 예만 보고 넘어가도록 하자..

 

  window.open("b.html","_blank","direction=yes, location=yes, menubar=yes, scrollbars=yes, status=yes, 

                         toolbar=no, resizeble=no, width=300, height=300");

 

이런 형태로 사용한다. 각각의 파라메터는 생략가능하다.

 

문제는 close()함수인데.. 사실 클로즈 함수 자체는 어려울게 없다. 대상윈도우에다가 사용하면 된다..예를 들어..

 

 자기 자신을 닫을 때는

 window.close();

 self.close();

 close();

 중에 하나..

 

 팝업이나 Open된 창에서 부모창을 닫을 때는

 window.opener.close();

 opener.close();

 등등을 사용하면 된다.

 

여기서 발생하는 문제가 Close()함수를 호출하면 창을 닫을 것인지 다이얼로그창으로 물어본다는것이다.

보통 Close()를 사용할때 물어보길 원할때도 있지만 대부분은, 조용히 닫혀지기를 원한다.  이때 사용가능한 편법이 있다..

닫혀지는곳의 opener를 닫혀지는곳 자신이나, null, ""등의 값으로 설정하면된다.. 말이 좀 어렵다.. 직접 보도록하자..

 

 자기자신을 닫을 때는

self.opener = self; 혹은 self.opener = null; 한 후

self.close()를 하면 된다..

 

반대로 팝업이나 Open된 창에서 부모창을 닫을 때는 부모창을 a.html 자식창을 b.html이라고 할때

a.html에서 self.opener = self; 혹은 self.opener = null; 을 설정한 후

오픈된 b.html에서 opener.close()를 호출하면 된다.

 

사실 좀 복잡할지 모르지만 한두번 해보면 금방익숙해 진다.

그런데.. 문제가 하나있다.. MS가 바보가 아니라.. 이편법을 IE7에서는 막아버렸다. 자식이 부모를 맘대로 죽이는(?) 하극상을 못참았나보다.

이방법을 사용해도 메시지 박스가 뜬다.. 하지만, 막는자가 있으면 뚫는자도 있는법.. 매우 참신한 방법으로 이 방법을 해결할 수있다..

 

 자기자신을 닫을 때는

 window.open("about:blank","_self").close();

 자기 자신에 윈도우를 하나열어서 자기 자식윈도우로 만든다음에 바로 닫아버린다.

 

 그렇다면 반대로 팝업이나 Open된 창에서 부모창을 닫을 때는 어떻게 할까..

  간단하다..

  opener.open('about:blank','_self').close();

  부모창에다 열고 닫아버리면 된다...






+ Recent posts