JavaScript: 연산자 operator

    JavaScript: 연산자 operator

    2018, Mar 20    

    참고한 글

    산술 연산

    +   -   *   /   %   ++   --
    

    덧셈 연산자는 피연산자 중 하나가 문자열일 때 나머지 피연산자도 문자열로 변환하려고 시도한다. 그래서 'nan' + 1의 결과는 ‘nan1’이 된다. 증가/증감++ -- 연산자는 피연산자를 가능할 경우 숫자 타입으로 변경한다. 가령 x++에서 x가 문자열 "1"이면 x는 숫자 1로 바뀌며 이후 1씩 증가된다. x가 숫자로 바꿀 수 없는 문자열일 경우 NaN을 반환한다.

    할당 연산

    =    +=    -=    *=    /=    %=
    &=    ^=    |=    <<=    >>=    >>>=
    

    비교 연산

    ==    ===    !=    !==    >    <    >=    <=
    

    ===는 일치연산자 혹은 엄격한 동등 연산자라고 한다. ==는 동등(equal)을 판단하며 ===는 일치(identical)하는지 판단한다. 둘은 같음을 정의하는 기준이 다르다. 가령 ===는 값이 같아도(형변환을 통해 같다고 판단되는 값. 가령 1과 ‘1’은 동등하다.) 타입이 다를 경우 둘이 서로 다른것으로 판단한다.

    동등 연산==의 판단 기준:

    • 두 값의 타입이 같은 경우, 두 값이 일치하면 둘은 동등하다.
    • 두 값의 타입이 다른 경우에도 여전히 동등할 여지가 있으므로 타입 변환이 사용된다.
    • 두 값 중 하나가 null이고 다른 하나가 undefined라면 두 값은 동등하다.
    • 두 값 중 하나가 문자열이고 다른 하나가 숫자면 문자열을 숫자로 변환한다.
    • 두 값 중 하나가 true이면 이를 1로 변환, false일 땐 0으로 변환 후 비교한다.
    • 한 값이 객체고 다른 하나가 숫자 또는 문자열이면 객체를 원시 타입으로 변환 후 비교한다. 객체의 원시 타입 변환에는 해당 객체의 toString() 메서드나 valueOf() 메서드가 사용된다.

    일치 연산===의 판단 기준:

    • 두 값의 타입이 서로 다르면 두 값은 일치하지 않는다.
    • 두 값이 모두 null이거나 undefined면, 두 값은 일치한다.
    • 두 값이 모두 불리언 값 true이거나 false일 경우에 두 값은 일치한다.
    • 적어도 하나의 값이 NaN이면 두 값은 일치하지 않는다. NaN 값은 자기 자신을 포함해 다른 어떠한 값과도 일치하지 않는다. 임의의 값 x가 NaN인지 검사하기 위해서는 x !== x와 같이 사용한다. 앞의 표현식이 참을 만족 할 때만 x의 값이 NaN이 된다.
    • 두 값이 모두 숫자고 같은 값을 갖는다면, 두 값은 일치한다. 만약 하나의 값이 0이고 다른 하나의 값이 -0일지라도, 두 값은 일치한다.
    • 두 값이 모두 문자열이고, 같은 위치에 정확히 같은 16비트 문자열 값을 갖고 있다면, 두 값은 일치한다. 만약 문자열의 길이나 내용이 다를 경우, 두 값은 일치하지 않는다. 두 문자열이 같은 의미를 갖고, 육안상 같은 문자열을 갖더라도 16비트 값의 순서가 다르게 인코딩되어 있을 수도 있다.
    • 자바스크립트에서는 유니코드 문자열에 대해서 정규화 과정을 수행하지 않는다. 또한 ===== 연산자를 사용해 유니코드 문자열의 동등 비교를 할 수 없다. 이와 같은 문자열을 비교하려면 String.localeCompare()를 사용한다.
    • 두 값이 모두 같은 객체나 배열 또는 함수를 참조하고 있으면, 두 값은 일치한다. 두 값이 서로 다른 객체를 참조할 경우에 설사 두 객체의 프로퍼티가 일치하더라도 두 값은 일치하지 않는다.

    논리 연산

    &&    ||    !
    
    var a = 0, b = 1;
    a == 0 && b == 1; // true
    true && b == 0; // false
    a != b || b != b; // true
    false || true; // true
    false || (true && false); // false
    

    논리 연산자 &&, || 는 피연산자가 관계 표현식 또는 boolean 타입이거나 괄호로 감싼 논리 표현식일 땐 일반적인 AND와 OR연산을 수행한다. 만약 피연산자가 boolean이 아닐 때 해당 피연산자는 boolean 타입으로 변환된다. 이후 규칙에 따라 값을 반환하는데, 반환되는 값은 boolean이 아니라 boolean으로 변환되기 전인 원래의 값이 반환된다. 가령 다음을 보면:

    undefined && 1; // undefined
    

    undefined가 false로 변환되고 연산식을 평가한 후 다시 원래의 값인 undefined가 반환된다.

    1 && 2; // 2
    3 && 1; // 1
    0 && undefined; // 0
    undefined && 0; // undefined
    123 && null; // null
    null && 123; // null
    

    &&는 다음 규칙을 따른다:

    • 피연산자가 모두 true일 땐 우변 피연산자의 값을 반환
    • 피연산자가 모두 false일 땐 좌변 피연산자의 값을 반환
    • 둘 중 하나가 false일 땐 false로 평가되는 값을 반환
    [] || {}; // Array [  ]
    "123" || window; // "123"
    null || undefined; // undefined
    undefined || null; // null
    0 || 1; // 1
    1 || 0; // 1
    

    ||는 다음 규칙을 따른다:

    • 피연산자가 모두 true일 땐 좌변 피연산자의 값을 반환
    • 피연산자가 모두 false일 땐 우변 피연산자의 값을 반환
    • 둘 중 하나가 false일 땐 true로 평가되는 값을 반환

    다른 연산자도 마찬가지지만 논리 연산자 또한 피연산자를 비교하기 전에 각 피연산자를 평가(evaluate)하는 과정을 거친다. 표현식이나 호출식을 먼저 실행한 후 비교한다고 생각하면 된다. 그리고 상황에 따라 우변의 피연산자는 평가될 수도 있고 평가되지 않을 수도 있다는 것을 주의해야 한다. 다음 예를 보면:

    alert(1) || alert(2);
    

    OR 연산은 좌변이 false일 때 우변도 평가한다. (window.alert의 리턴값은 undefined) 따라서 경고창은 두 번 나타난다.

    alert(1) && alert(2);
    

    AND 연산은 좌변이 false일 때 우변을 평가하지 않는다. 따라서 경고창은 한 번 나타난다.

    true || alert(2);
    

    OR 연산은 좌변이 true일 때 우변을 평가하지 않는다. 이 경우 경고창이 나타나지 않는다.

    true && alert(2);
    

    AND 연산은 좌변이 true일 때 우변을 평가한다. 경고창이 나타난다.

    다음은 논리 연산자로 삼항 연산을 흉내낸 것이다:

    var a = 1;
    var result = (a == 1) && 'equal' || 'not equal';
    console.log(result); // "equal"
    

    OR 연산자의 좌변과 우변이 모두 true로 평가되는 값일 때만 제대로 작동하는 한계가 있지만…

    비트 연산

    • ~: 비트단위 NOT
    • &: 비트단위 AND
    • |: 비트단위 OR
    • ^: 비트단위 XOR
    • <<: 왼쪽으로 이동
    • >>: 부호 비트를 확장하면서 오른쪽으로 이동
    • >>>: 부호 비트 확장 없이 오른쪽으로 이동

    삼항 연산자

    조건 연산자 또는 선택 연산자. TRUE 혹은 FALSE에 해당하는 값을 반환한다.

    조건 ? TRUE : FALSE
    
    var a = 1;
    var result = (a == 1) ? "" : "일 아님";
    console.log(result); // "일"
    

    instanceof

    객체의 프로토타입 체인에 생성자의 프로토타입이 존재하는지 테스트한다. 좌변의 피연산자로 객체를 받고 우변의 피연산자로 생성자를 받는다. 이 연산자로 객체가 특정 타입의 인스턴스인지 판단할 수 있다. 우변은 반드시 함수 이름이어야 하는데, 만약 함수가 아니면 TypeError가 발생할 것이다.

    object instanceof constructor
    
    function fn() {
        // blah blah blah
    }
    
    fn instanceof Function;  // true
    alert instanceof Function;  // true
    "" instanceof String; // false, 문자열 자체는 원시 타입이기 때문
    new String() instanceof String;  // true, String 래퍼 객체는 String의 인스턴스
    [] instanceof Array; // true, [].__proto__ === Array.prototype
    [] instanceof Object; // true, 모든 객체는 Object의 인스턴스
    

    typeof

    연산자 다음에오는 변수, 함수, 객체 또는 표현식의 타입을 반환한다.

    typeof expression
    
    typeof "John"                // "string"
    typeof 3.14                   // "number"
    typeof NaN                    // "number"
    typeof false                  // "boolean"
    typeof [ 1, 2, 3, 4 ]           // "object"
    typeof { name: 'John', age: 34 }  // "object"
    typeof new Date()             // "object"
    typeof function() {}         // "function"
    typeof myCar                  // "undefined" (if myCar is not declared)
    typeof null                   // "object"
    

    in

    객체나 배열에 특정 프로퍼티가 존재하는지 확인한다.

    expression in Object
    
    // Arrays
    var cars = [ "Saab", "Volvo", "BMW" ];
    "Saab" in cars          // false, 'Saab'은 프로퍼티의 이름이 아니라 값이므로 false
    0 in cars               // true
    1 in cars               // true
    4 in cars               // false, cars의 길이는 3이므로 배열의 4번째 인덱스는 없다.
    "length" in cars        // true, Array 래퍼 객체에 length란 프로퍼티가 존재함.
    
    // Objects
    var person = { firstName: "John", lastName: "Doe", age: 50 };
    "firstName" in person   // true
    "age" in person         // true
    
    // Predefined objects
    "PI" in Math            // true
    "NaN" in Number         // true
    "length" in String      // true
    

    또한 in은 for-in 반복문에서 사용된다.

    for (variable in object) { ... }
    
    • object: 반복할 객체. 객체의 프로퍼티 만큼 반복된다.
    • variable: 매번 반복될 때마다 프로퍼티의 key를 할당한다. 만약 반복되는 객체가 배열일 경우 인덱스를 할당할 것이다.
    var foo = { a: 1, b: 2};
    var propNames = [];
    for (var ele in foo) {
       propNames.push(ele);
    }
    console.log(propNames); // [ "a", "b" ]
    
    var foo2 = [ 'aaa', 'bbb', 'ccc' ];
    var idxs = [];
    for (var ele in foo2) {
      idxs.push(ele);
    }
    console.log(idxs); // [ "0", "1", "2" ]
    

    delete

    프로퍼티를 삭제한다. 삭제에 성공했을 때 true를, 실패했을 때 false를 반환한다.

    delete Object.property
    
    var fn = { word: "hi" };
    
    console.log(fn.word);   // "hi"
    
    delete fn.word; // true
    console.log(fn.word); // undefined
    

    var문으로 정의된 변수(전역 객체의 프로퍼티라 하더라도), 함수 선언문(또는 함수 구문)으로 정의된 함수, 함수 매개변수로 선언한 함수는 삭제할 수 없다.

    var a = 1;
    delete a; // false
    
    function fn() { }
    delete fn; // false
    
    function callee(callback) {
    	console.debug(delete callback); // false
    }
    callee(function() {});
    

    void

    void expression
    

    void 연산자는 피연산자를 실행하고 undefined를 반환한다. 피연산자를 실행해야 하지만 결과를 노출하고 싶지 않을때 사용한다.

    <a href="javascript:void window.open()">open</a>
    

    void는 같은 이름의 함수가 존재하는데:

    void(0); // undefined
    void(); // SyntaxError: expected expression, got ')'
    

    연산자와 마찬가지로 항상 undefined를 반환하는 함수다. 전달인자는 어느 값이어도 상관 없지만 할당하지 않으면 에러가 발생한다. 이동할 주소가 없는 앵커 태그가 필요할 때 종종 사용된다.

    <a href="javascript:void(0)">test</a>