본문 바로가기

JavaScript

20210703 JavaSciprt DeepDive 01 : 변수, 표현식과 문, 데이터 타입, 연산자, 제어문

JavaScript Deep Dive 01


용어 및 중요사항 정리


변수

  • 변수 : 확보한 메모리 공간 자체 or 메모리 공간 식별자

  • 식별자 : 어떤 값을 구별해서 식별할 수 있는 고유한 이름(식별자는 값이 아니라, 메모리 주소를 기억함으로서 메모리 주소에 붙인 이름 역할을 함)

  • 변수선언: 변수를 사용하기 위한 메모리 공간 확보와 변수 이름과 확보된 메모리 공간의 주소 연결 (값 저장 준비, var, let, const)
    • 값 할당 없이 변수선언만 되어도, undefined가 기본으로 할당 되어 초기화 됨

  • 변수 호이스팅 : 변수 선언문이 코드 작성 맨위로 올라 간듯이 동작하는 특징
    • 변수 선언이 런타임 전에 실행되기 때문 (var, let, const, function, function*, class 키워드로 선언하는 모든 식별자)

  • 값의 할당 : 우변의 값을 좌변의 변수에 할당하는 것 (변수 선언과 값의 할당은 2개의 문으로 나뉨)

    • 변수 선언은 런타임 전에 실행되고 , 값 할당은 런타임때 실행됨
    • 값이 할당 되는 경우 기존에 메모리에 저장된 값에 덮어 쓰는 것이 아닌 새로운 공간에 값을 저장하고 해당 공간을 가르키도록 변경됨
    console.log(a); // undefined <- 변수 선언 되면서 아직 명시적으로 값이 할당 되지 않았기 때문에 (기본적으로 undefined 가 할당 됨으로)
    var a = "Hi!"; // var a -> 런타임 -> = 'Hi!'
    console.log(a); // Hi!

  • 값의 재할당: 이미 값이 할당 되어 있는 변수에 새로운 값을 또다시 할당하는 것
    • 재할당이 가능해서 변수는 변수다.
    • 변수에 저장된 값을 변경할 수 없으면 상수(constant) (const 키워드가 상수임)

  • 가비지 콜렉터 : 앱이 할당한 메모리 공간을 주기적으로 검사하여 더이상 사용하지 않는 메모리를 해제하는 것
    • 변수의 값이 재할당 되면서 가르키는 메모리가 변경되고, 어떠한 변수도 해당 값이 저장된 공간을 가르키지 않아 사용하지 않는 값은 가비지 콜렉터에 의해 메모리에서 자동 해제됨

  • 언매니저드& 매니지드 언어: 언어가 메모리 제어를 자동으로 할수 있는지 없는지에 따라 달라짐
    • Javascript는 매니지드 언어 임(개발자가 제어 불가, 언어가 자동으로 제어)

  • 예약어 : 프로그래밍 언어에서 사용되고 있거나, 사용될 예정인 단어들을 말함

  • 식별자 명명 규칙:
    • 사용할 수 있는 문자: 알파벳, 숫자, 언더스코어(_), 달러기호($)
    • 첫글자: 영어, _, $
    • 예약어는 안됨



표현식과 문


  • 네이밍컨벤션 : 식별자 구분 명명 규칙 (camelCase, snake_case, PascalCase, typeHungarianCase)

  • 값(value) : 표현식(식)이 평가되어 생성된 결과, 변수에 할당되는 것

  • 평가(evaluate) : 식 해석(계산)을 통한 값 생성 또는 참조

  • 리터럴(literal) : 값을 생성하는 방법으로 사람이 이해할 수 있는 문자 or 약속된 기호를 사용해 값을 생성하는 표기법(notation)
    • 런타임에 리터럴을 평가해 값을 생성
    • 정수, 부동소수점, 2진수, 8진수, 16진수, 문자열, 불리언, null, undefined, 객체, 배열, 함수, 정규 표현식

  • 표현식(expression) : 값으로 평가될 수 있는 문(statement)

    • 표현식이 평가되어 새로운 값을 생성 또는 기존 값을 참조함 (리터럴 포함)
    • 기본적인 리터럴, 연산자, 변수 식별자 참조(이미 선언이 된 변수 식별자), 함수/메서드 호출
    • 표현식은 값처럼 활용할 수 있어서 다르 표현식의 일부가 되어 새로운 값을 만들어 낼 수 있음
    • 표현식은 문의 일부일 수도 있고 그자체로 문이 될 수도 있음
    const x = 1 + 2; // x는 값으로 활용 될수 있음
    x + 3; // 6 <- 표현식이 다른표현식의 일부로서 작용하여 새로운 값을 생성

  • 동치(equivalent) : 표현식과 표현식이 평가된 값이 동등한 관계를 일컫는 말

  • 문(statement) : 프로그램을 구성하는 기본단위, 최소 실행 단위 (문의 집합 -(프로그래밍)-> 프로그램)

    • 문은 토큰으로 구성됨
      • 변수 선언문
      • 할당문
      • 함수 선언문
      • 조건문
      • 반복문
    • 문이 끝나면 세미콜론(;)을 작성해서 종료를 나타냄
      • 단, 블록 형태({})의 문은 붙지 않음/ 함수 선언문, 조건문, 반복문/ 블록이 종결성을 갖기 때문
    • 표현식인 문(실행O, 평가O) vs 표현식이 아닌 문(실행O, 평가X) -> 평가, 실행 차이
    var x; // 실행은 되지만 평가는 안됨 -> 표현식이 아닌 문
    x = 100; // 실행도 되고, 평가도 됨 -> 표현식인 문

  • 토큰(token): 문법적으로 더이상 나눌 수 없는 코드 (키워드, 식별자, 연산자, 리터럴, 세미콜론, 마침표 등)



데이터 타입


  • 데이터 타입(data type, type):
    • 원시: number, string, boolean, undefined, null, symbol
    • 객체: 객체, 함수, 배열 등

  • 숫자 타입(Number Type) : 배정밀도 64비트 부동소수점 형식 -> 실수 (정수만 표현X)
    • 모두 2진수로 저장 -> 값 참조시 모두 10진수로 해석됨
    • 특별한 값: Infinity, -Infinity, NaN(Not a Number)

  • 문자열 타입(String) : JS 일반적인 표기법은 작은 따옴표('')/ 그외 ("", ``)
    • 키워드 또는 식별자와 구분하기 위해서 따옴표 사용
    • 원시 타입(Primitive), 변경 불가능한 값(Immutable value)

  • 템플릿 리터럴(template literal) : 문자열 처리 기능 제공 -> 멀티라인 문자열, 표현식 삽입, 태그드 템플릿
    • 멀티라인 문자열 : 문자열 내에서의 줄바꿈 개행을 허용하여 인식함 (일반 문자열인 경우 개행 인식을 위해 이스케이프 시퀸스를 사용함)
    • 표현식 삽입 : 기존의 문자열 조합을 위해 문자열 연산자를 사용했었는데 표현식 삽입을 통해 간단히 문자열을 삽입 가능 (`삽입 당할 내용 ${문자열에 추가시킬 표현식} 삽입 당할 내용`)
      • 문자열 연산자 (+) : 피연산자 중에 하나 이상이 문자열인 경우 문자열 연산자로 동작 (그외엔 덧셈 연산자로 작동)

  • 라인피드(LF) vs 캐리지 리턴(CR)
    • Carriage Return (\r) : 커서를 처음으로 이동
    • Line Feed(\n) : 커서를 다음 행으로 아래로 내림
    • 운영체제 마다 채택한 개행 정책이 다름(macOS: LF, windows: CR+LF) -> 텍스트 에디터는 운영체제에 맞게 개행문자를 자동으로 변환해 줌
    • JavaScript에서는 보통 피드(\n)를 개행으로 사용

  • 불리언 타입 : 논리적인 참, 거짓 (true, false)로서 조건문에 자주 사용

  • undefined 타입 : 자바스크립트 엔진이 변수를 초기화 할때 사용하는 값 (개발자가 어떤 값을 초기화 시킨적이 없다는 뜻), 개발자가 의도적으로 값이 없다고 명시하고 싶을 때는 null을 사용

  • 선언(declaration) vs 정의(definition) : C에서는 식별자에 메모리 주소가 연결(할당)되었는가로 선언과 정의를 나눔 (연결되면 정의, 아니면 선언)
    • 정의 : 어떤 대상을 명확하게 규정, 식별자에 메모리 주소가 연결(할당)
    • 선언 : 단순 컴파일러에 식별자의 존재를 알림,식별자에 메모리 주소가 연결(할당) 되지 않음
    • JavaScript의 경우 변수선언시 암묵적으로 정의가 이루어 지기 때문에 선언과 정의 구분이 모호함
      • undefined 라는 값이 정의 되었지만, undefined는 정의 되지 않은 임을 뜻함
      • 변수는 선언, 함수는 정의

  • null 타입: null은 변수에 값이 없다는 것을 의도적으로 명시 할때 사용
    • null 을 변수에 할당하는 것은 이전 할당되어 있던 값에 대한 참조를 명시적으로 제거를 의미
    • JavaScript는 이렇게 null을 명시하면 카비지 콜렉션을 수행하여 아무도 참조하지 않는 이전 값을 차지하는 메모리 공간을 삭제 할 것임
    • 그래서, 의도적으로 어떤 값이 없게 되거나 반환할게 없는 경우 undefined이 아니라 null을 사용함

  • 심벌(symbol) 타입 : 변경 불가능한 원시 타입의 값, 다른 값과 중복 되지 않는 유일한 값
    • symbol은 함수를 호출해 생성함 (나머지 원시 타입은 리터럴로 생성)

  • 객체 타입 : 원시 타입과 반대되는 값들(자바스크립트를 이루고 있는 거의 모든 것이 객체임)

  • 저장 단위 :
    • 1비트
    • 1바이트 = 8비트
    • 1킬로바이트 = 1000바이트 = 8000비트
    • 1메가바이트 = 10^6바이트 = 8 * 10^6비트
    • 1기가바이트 = 10^9바이트
    • 1테라바이트 = 10^12바이트
    • 예시) javascript number 타입의 값 경우 64비트 부동소수점 -> 메모리 저장시 8바이트 공간 필요

  • 메모리 저장과 참조, 해석
    • 심벌 테이블 : 식별자를 key로하여 값의 메모리 주소, 데이터 타입, 스코프 등을 관리
      • 식별자 - 심벌테이블 - 메모리
    • 저장 : 코드 실행 -> 해당값의 타입에 맞게 메모리 공간 확보 -> 2진수로 해당 메모리 공간에 저장
    • 참조 : 코드 실행 -> 식별자로 메모리 공간 주소로 찾아감 -> 타입으로 한번에 읽어 들여야할 메모리 공간을 알아보고 읽음
    • 해석 : 타입을 보고 해당 메모리에 있는 2진수를 어떻게 해석해야 할지 결정해서 해석함

  • 데이터 타입이 필요한 이유
    • 값 저장시 메모리 공간 크기 결정
    • 값 참조시 한번에 읽어 들여야 할 메모리 공간 크기 결정
    • 메모리에서 읽어 들인 2진수를 어떻게 해석할지 결정하기 위해

  • 정적타입 언어:
    • 변수 선언시 변수에 할당할 수 있는 값의 타입을 사전에 선언하는 언어들(명시적 타입 선언)
      • C, C++, 자바, 코틀린, 고, 하스켈, 러스트, 스칼라
    • 컴파일 시점에 타입 체크를 함 -> 프로그램 실행 할지 말지 결정

  • 동적타입 언어: javascript와 같이 동적타이핑이 가능한 언어
    • python, ruby, lisp, perl 등
    • 유연성은 높지만 신뢰성이 떨어짐
    • 타입추론(type inference) : javascript 처럼 동적타입 언어의 경우 변수 선언이 아닌 값 할당에 의해 타입이 결정되는 것 (즉, 값에 의해 변수의 타입이 결정)
    • 동적타이핑(dynamic typing) : 값이 재할당 될때 마다 타입이 언제든지 동적으로 변하는 특징

  • 트레이드 오프(trade-off) : 하나를 개선하면 다른 하나가 희생되는 모순적 관계
  • 은 탄환(silver bullet) : 고질적인 문제를 단번에 해결할 수 있는 명쾌한 해결책 (사실, 은탄환은 없음)

  • 변수 사용 주의 사항

    • 필요한 만큼 최소한으로 유지
    • 변수의 유효 범위(스코프)는 최대한 좁게 만들자
    • 전역변수 최소화
    • 변수보다는 상수를 사용하자
    • 변수 이름(식별자)를 목적, 의미 파악할 수 있게 네이밍 하자



연산자


  • 연산자 표현식 : 연산자와 피연산자의 조합으로 이루어진 표현식

    • 피연산자(operand) : 연산의 대상으로 표현식 이여야 함
    • 연산자(Operator) : 하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입, 지수 연산 등을 수행하여 하나의 값을 만드는 토큰

  • 산술연산자 : 피연산자를 가지고 수학적 계산을 수행하여 새로운 숫자 값을 만듦 (연산 불가시 NaN 반환)

    • 부수효과(side Effect): 피연산자의 값을 변경하는 것(변수가 가진 값 원본을 변경하는 것)

    • 이항(binary) 산술 연산자 : 2개의 피연산자를 산술 연산하여 숫자값을 만듦

      • 부수효과X -> 언제나 새로운 값을 만듦
      • +, -, *, /, %, //
    • 단항(unary) 산술 연산자 : 1개의 피연산자를 산술 연산하여 숫자 값을 만듦

      • + : 어떠한 효과도 없음 / 부수 효과X

        • 단지, 숫자타입이 아닌 피연산자에 붙여 사용시 값을 숫자타입으로 변경하여 새로운 숫자 값을 만듦
        • '1' -> 1, true -> 1, false -> 0, 'string' -> NaN
      • - : 양수를 음수로, 음수롤 양수로 반전한 값을 반환 / 부수 효과X

        • 역시, 값을 숫자타입으로 변경시키고 부호도 반전시킴
      • ++, -- : 증가, 감소/ 부수 효과 O(암묵적 할당이 있어 원본에 변경을 줌)

        • 전위 증감연산자: 먼저 피연산자 값을 증가/감소 후 다른 연산을 수행
        • 후위 증감연산자: 먼저 다른 연산을 수행 후, 피연산자의 값을 증감
        // '=' 을 기준으로 생각 변수와 바로 만나면 할당 먼저, 증감연산자를 바로 만나면 증감 먼저
        let x = 5,
          result;
        result = x++; // result에 선할당 후 x증가
        console.log(result, x); // 5, 6
        result = ++x; // x 먼저 증가 후 result에 할당
        console.log(result, x); // (7, 7)

  • 문자열 연결 연산자('+') : 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작

  • 암묵적 타입 변환(implicit coercion), 타입 강제 변환(type coercion)
    • 자바 스크립트엔진이 암묵적으로 타입을 강제로 변환하는 것
      • 단항 산술연산자 +, - 를 통해서 숫자 타입으로 변환

  • 할당 연산자 : 우항에 있는 피연산자의 평가 결과를 좌항에 있는 변수에 할당 (변수 값이 변하는 부수 효과가 있음)
    • =, +=, -=, *=, /=, %=
    • 모두 부수 효과가 있음
    • 숫자 뿐만아니라 문자연결시에도 사용 가능
    • 할당문: 표현식인 문(할당된 값으로 평가됨)
      • 연쇄할당:
        • 여러 변수에 동일한 값을 연쇄 할당 가능 (a = b = c = 0;)
        • 표현식이므로 연쇄적으로 사용해서 더큰 표현식으로 만들 수 있음

  • 비교 연산자
    • 좌항, 우항의 피연산자 비교한 결과를 불리언 값 반환
    • 동등 vs 일치 비교 연산자 : 동등비교(==), 일치비교(===), 부동등비교(!=), 불일치비교(!==)
      • 동등비교의 경우 비교시 피연산자를 먼저 암묵적 타입 변환하여 타입을 일치시킨 후 비교함 -> 예측 어려움
      • 일치비교의 경우 좌우 타입도 같고, 값도 같아야 true를 반환
        • 단, NaN === NaN 은 유일하게 false임 (NaN 확인은 isNaN함수 사용)
        • -0, 0은 같음
      • Object.is(A,B): A, B를 예측 가능한 정확한 비교 결과를 반환 (-0,0 / NaN 문제 해결)
    • 대소관계 비교 연산자: >, <, <=, >= -> true or false

  • 삼항조건 연산자: 조건에 따른 반환할 값 결정(부수 효과 X)
    • 조건식 ? true시 반환 값 : false시 반환값
    • 조건식의 결과가 불리언이 아니면 불리언으로 암묵적 타입 변환 일어남
    • 삼항조건 연산자 vs 조건문
      • 삼항조건 연산자는 표현식으로 값처럼 사용가능
      • 조건문(if...else)은 값처럼 사용 불가
      • 어떤 값을 결정하는 경우 -> 삼항 조건 연산자
      • 수행해야할 문이 여러개라면 -> if ...else 문

  • 논리 연산자
    • 좌우항 논리연산
    • 논리합(||), 논리곱(&&), 논리부정(!) / 부수효과X
    • ||, &&은 언제나 불리언 값을 반환 하지 X : 2개의 피연산자 중 어느 한쪽으로 평가
    • 논리 부정(!) 연산자 언제나 불리언 값 반환
      • 피연산자가 불리언 값이 아니면 불리언 타입으로 암묵적 변환

  • 드 모르간의 법칙

    !(x || y) === (!x && !y);
    !(x && y) === (!x || !y);

  • 쉼표연산자
    • 왼쪽의 피연산자 부터 차례대로 피연산자 평가 후 마지막 피연산자의 평가 결과를 return

  • 그룹연산자
    • 소괄호로 피연산자를 감싸 그룹 연산자를 활용하면, 그룹 연산자의 우선순위를 높임

  • typeof 연산자 : 피연산자의 데이터 타입을 문자열로 반환
    • string, number, boolean, undefined, symbol, object, function
    • '' -> string , 1 -> number, NaN -> number, true -> boolean, Symbol() -> symbol
    • undefined -> undefined, function() {} -> function
    • null, [], {}, new Date(), /test/gi -> object
    • null 타입 확인은 일치 연산자로 확인할 것! (typeof foo === null)
    • 선언하지 않은 식별자의 type은 undefined 반환

  • 지수 연산자
    • 좌항을 밑(base), 우항을 지수(exponent)로 거듭 제곱하여 숫자값 반환
    • 2 * 2; -> 4, 2 ** 2 *\ 2; -> 16
    • 음수를 밑으로 사용시 괄호로 묶어야 함
    • 할당 연산자와 같이 사용 가능 (**=)
    • 지수 연산자는 이항 연산자 중에 우선순위가 가장 높음

  • 기타 연산자
    • 옵셔널 체이닝 연산자(?.)
    • null 병합 연산자(??)
    • 프로퍼티 삭제(delete)
    • new 연산자(new):생성자 함수 호출시 사용하여 인스턴스 생성
    • instanceof 연산자(instanceof): 좌변의 객체가 우변의 생성자 함수와 연결된 인스턴스인지 판별
    • in연산자 (in) : 프로퍼티 존재 확인

  • 부수효과 있는 연산자
    • 할당 연산자(=), 증감연산자(++/--), delete연산자 (원본 지장O, 다른 코드에 영향을 줌)
    • 위에 외에는 부수 효과 없음 (원본 지장X, 다른 코드에 영향 X)

  • 연산자 우선순위

    • 연산자 우선순위가 높을 수록 먼저 실행됨
      • ( ) 그룹 연산자
      • new(매개변수 존재), .,[](프로퍼티 접근), ()(함수호출), ?.(옵셔널 체이닝)
      • new(매개변수 미존재)
      • 후위 증감연산자 (x++, x--)
      • !x(부정), +x, -x(단항 산술), ++x, --x(전위 증감연산자), typeof, delete
      • **(지수 연산자)
      • *, /, % (이항 연산자)
      • +, - (이항 연산자)
      • <, >, <=, >=(대소 비교 연산자), in, instanceof
      • ==, !=, ===,!== (동등, 일치 비교 연산자)
      • ?? (null 병합 연산자)
      • && (논리 곱)
      • || (논리 합)
      • ? A : B (삼항조건 연산자)
      • =, +=, -=, ... (할당 연산자)
      • , (쉼표 연산자)

  • 연산자 결합 순서 : 연산자의 좌,우 어느쪽부터 평가를 수행할 것인지

    • 좌->우 : +,-,/,%, <, >, <=, >=, &&, ||, ., [], (), ??, ?., in, instanceof
    • 우->좌 : ++, --, 할당 연산자(=, +=, -=, ...), !x, +x, -x, ++x, --x, typeof, delete, ?a:b



제어문


  • 제어문 : 코드의 실행 흐름을 인위적으로 제어 할수 있음 (조건문, 반복문)

  • 블록문 : 0개 이상의 문을 중괄호({})로 묶은 것(코드블록, 블록)

    • JS는 하나의 실행 단위로 취급함
    • 보통은 제어문, 함수 정의시 사용함

  • 조건문 : 주어진 조건의 평가 결과에 따라 블록문의 실행을 결정
    • 조건식은 불리언 값으로 평가될 수있는 표현식

  • if..else문
    • 조건식의 평가 결과가 true -> if문의 코드 블럭 실행
    • 조건식의 평가 결과가 false -> else 문의 코드 블럭 실행
    • 조건식은 불리언 값으로 평가 되어야 함
    • 추가 조건은 else if문 활용
    • 코드 블럭내의 문이 1개면 중괄호 생략 가능
    • 삼항 조건 연산자로 변경 가능 (삼항 조건 연산자의 경우 자신을 구성하게 중복하여 사용이 가능함)
      • 변수에 할당 하는 경우 삼항 조건 연산자를 활용하는게 좋고, 실행 내용이 여러 줄이라면 if...else문을 사용하는 것이 좋음

  • switch문 : 주어진 표현식을 가진 case문으로 실행 흐름을 옮김
    • switch문의 조건식은 보통 문자열이나 숫자 값으로 사용함 (다양한 상황시 실행될 코드 블록 결정)
    • case문 : 특정 상황의 표현식을 지정하고 콜론을 쓰고, 해당 실행 문들을 위치시킴
    • default문 : case 문에 걸리는게 없는 경우 실행될 문을 작성
    • break문: fall Through를 방지하기 위해서 해당 사용함
    • 폴스루(fall Through) : case문은 break가 없는한 계속해서 다음에 코드가 있으면 해당 코드를 실행함, 코드가 더이상 없을 때 끝나게 되는 현상 (switch의 조건식은 단지 해당 case로 보내고, 그 이후에 break가 없으면 다음 case, default 존재시 해당 코드를 실행함, default의 경우에는 break가 없어도 됨)
      • 여러개의 case문을 하나의 조건(즉, 같은 코드를 실행할 하나의 블럭)으로 사용가능
      • 단지 break만 잘 위치시키면 됨

  • 반복문: 조건식의 평가 결과가 참인 경우 코드 블록을 실행후 다시 조건식을 다시 평가해서 참인 경우 다시 실행하고, 조건식이 거짓 일때 까지 반복
    • for문, while문, do...while 문
    • 반복문 대체 기능: forEach, for...in, for...of

  • for문 : 조건식이 거짓일 때 까지 코드블록 반복실행
    • for (변수 선언문 or 할당문; 조건식; 증감식) { 조건식이 참인경우 실행될 문}
    • 반복횟수가 명확 할 때 사용

  • while문 : 조건식이 거짓일 때 까지 코드블록 반복 실행
    • 반복횟수가 명확하지 않을 때 사용
    • 조건식에 있는 평가 결과가 불리언이 아니면 불리언으로 강제 변환함
    • if문으로 조건만들고 break 문으로 코드 블럭 탈출 가능

  • do...while문 : 코드 블럭을 먼저 실행하고 조건식을 평가하여 거짓일 때 까지 반복실행 함(무조건 한번 이상 블록 실행시 사용)

  • break 문 : 레이블 문, 반복문 또는 switch문에서만 코드 블록을 탈출함
    • 레이블문(label statement) : 식별자가 붙은 문으로 프로그램의 실행 순서 제어하는데 사용됨(case, default문도 레이블 문임) , 그런데 비추함 별로 가독성 안좋음
      • 레이블문 탈출은 break문에 레이블 식별자를 지정함
      • 중첩된 for문인 경우 내부 for에 break를 외부 for의 식별자를 사용하면 외부 for를 탈출함

  • continue문: 반복문의 코드 블록 실행을 현 지점에서 중단하고 반복문의 증감식으로 실행 흐름을 이동시킴(반복문 안에서도 흐름을 제어하여 다음 반복으로 넘길 수 있음)
    • if로 구현하는게 더 가독성이 좋긴함, 실행 코드가 많다면, continue문을 사용하는 게 좋음