본문 바로가기
Javascript

JavaScript - == vs === vs Typeof

by Su1993 2020. 9. 14.
반응형

== vs ===

===

자바스크립트에서 === 연산은 엄격한 동등성을 비교할 때 사용한다. 여기서 엄격한 동등성이란 타입과 값이 둘 다 같음을 의미한다.

true를 만족하는 몇 가지 예를 들면 다음과 같다.

3 === 3 
// 타입과 값이 같음 true

'abcd' === 'abcd'
// 타입과 값이 같음 true

true === true
// 타입과 값이 같음 true

 

false를 만족하는 몇 가지 예를 들어보면 다음과 같다.

33 === "33"
// 숫자타입과 문자열타입 false

"aaa" === "bbb"
// 타입은 같지만 값이 다름 false

false === 0
// 타입과 값 둘 다 다름 false

 

===은 타입과 값 모두 같아야 true를 반환한다.

==

자바스크립트에서 == 연산자를 쓰는 목적은 가벼운 동등 비교를 하기 위한 것이다. == 연산자도 강제 형 변환을 수행한다.

강제 형 변환(type coercion)이란 동등 연산자로 비교하기 전에 피연산자들을 공통 타입(common type)으로 만드는 걸 말한다.

2020/09/08 - [Javascript] - JavaScript - Type Coercion(형 변환)

 

===와 비교하는 예를 들어보면

 

11 === "11"
// 타입이 다름 false

11 == "11"
// 형 변환 true

 

두 번째 == 비교에서 true가 된 이유는 자바스크립트가 값을 동등한 타입으로 형 변환 후에 값을 비교하기 때문이다.

 

false === 0
// 다른 타입과 다른 값 false

false == 0
// true

0 == ""
// true

"" == false
// true

2020/09/08 - [Javascript] - JavaScript - Type Coercion(형 변환)

 

false == 0 이 true인 이유는 자바스크립트에서 0이 falsy 값이기 때문이다. 그래서 형 변환을 통해 비교하기 때문에 true가 나오게 된다.

 

* 기억할 내용

 

undefined == undefined
// true

null == null
// true

null == undefined
// true

NaN == undefined
// false

NaN == null
// false

NaN == NaN
// false

 

 


 

Typeof

JavaScript의 특징 중 하나는 변수를 선언할 때 자료형을 다르게 사용하지 않는다는 것이다. 이런 점이 장점이 되기도 하지만 데이터를 받아서 활용할 때는 안에 있는 데이터의 자료형이 어떤 것인지 구분되지 않아 불편하기도 하다. 이때 자료형을 검사할 수 있는 typeof가 존재한다.

 

typeof variable;

 

해당하는 변수의 primitive 타입을 return한다. primitive 타입의 종류는

  • boolean
  • string
  • number
  • undefined
  • function
  • object

등이 있다. 위 타입 중 하나를 string 형태로 return하기 때문에 어떤 타입인지 string으로 구분하면 된다.

 

if(typeof variable === 'object') {/* 오브젝트 처리 */}

 

만약 variable이 null이라면 true를 나타내는데 false를 나타내고 싶다면

 

if(variable != null && typeof variable === 'object') {/*오브젝트 처리*/}
if(!!variable && typeof variable === 'object') {/*오브젝트 처리*/}

 

위 방법들이 있다. 만약 변수가 string인 경우를 판별하고 싶다면 'object'를 'string'으로 바꿔주면 된다.

 

typeof 'abc'; // 'string'
typeof 1; // 'number'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
typeof function () {}; // 'function'

 

Instanceof

instanceof 연산자를 사용하면 객체가 특정 클래스에 속하는지 아닌지 확인할 수 있다. 그리고 상속 관계도 확인해준다. 

 

obj instanceof Class

 

obj가 Class에 속하거나 Class를 상속받는 클래스에 속하면 true가 반환된다.

 

class Test{}
let test = new Test();

test instanceof Test;
// test가 클래스 Test의 객체이므로 true

 

instanceof는 생성자 함수와 Array 같은 내장 클래스에도 사용할 수 있다.

 

function test() {}

new test() instanceof test;
// true

let arr = [1, 2, 3];
arr instanceof Array;
arr instanceof Object;

 

Array는 프로토타입 기반으로 Object를 상속받는다.

instanceof 연산자는 프로토타입 체인을 거슬러 올라가며 인스턴스 여부나 상속 여부를 확인한다. 그런데 정적 메서드 Symbol.hasInstance를 사용하면 직접 확인 로직을 설정할 수도 있다.

obj instanceof Class의 동작은 아래와 비슷하다.

1. 클래스에 정적 메서드 Symbol.hasInstance가 구현되어 있으면, obj instanceof Class문이 실행될 때, Class[Symbol.hasInstance](obj)가 호출된다. 호출 결과는 true나 false이어야 한다. 이런 규칙을 기반으로 instanceof의 동작을 커스터마이징 할 수 있다.

 

// blue 프로퍼티가 있으면 color라고 판단할 수 있도록
// instanceOf의 로직을 직접 설정

class color {
  static [Symbol.hasInstance](obj) {
    if(obj.blue) return true;
  }
}

let obj = { blue: true };
alert(obj instanceof color);
//true, color[Symbol.hasInstance](obj)가 호출됨

 

2. 대부분의 클래스엔 Symbol.hasInstance가 구현되어 있지 않다면 일반적인 로직이 사용된다.

obj instanceOf Class는 Class.prototype이 obj 프로토타입 체인 상의 프로토타입 중 하나와 일치하는지 확인한다. 비교는 차례대로 진행된다.

 

obj.__proto__ === Class.prototype?
obj.__proto__.__proto__ === Class.prototype?
obj.__proto__.__proto__.__proto__ === Class.prototype?
...
// 이 중 하나라도 true라면 true를 반환합니다.
// 그렇지 않고 체인의 끝에 도달하면 false를 반환합니다.

 

위 예시에서 test.__proto__ === Test.prototype가 true이기 때문에 instanceof는 true를 반환한다. 상속받은 클래스를 사용하는 경우엔 두 번째 단계에서 일치 여부가 확인된다.

 

class Color {}
class Blue extends Color {}

let blue = new Blue();
blue instanceof Blue;
// true
// blue.__proto__ === Blue.prototype
// blue.__proto__.__proto__ === Color.prototype

 

blue instanceof Color을 실행했을 때 Color.prototype과 비교되는 대상들(아래참고)

 

 

objA가 objB의 프로토타입 체인 상 어딘가에 있으면 true를 반환해주는 메서드, objA.isPrototypeOf(objB)도 있다. obj instanceof Class는 Class.prototype.isPrototypeOf(obj)와 동일하다. isPrototypeOf는 Class 생성자를 제외하고 포함 여부를 검사하는 점이 특이하다. 검사 시, 프로토타입 체인과 Class.prototype만 고려한다. isPrototypeOf의 이런 특징은 객체 생성 후 prototype 프로퍼티가 변경되는 경우 특이한 결과를 초래하기도 한다.(아래 참고)

 

function Test() {}
let test = new Test();

//프로토타입이 변경됨
Test.prototype = {};

//더 이상 Test가 아님
test instanceof Test;
// false

 

타입 확인을 위한 Object.prototype.toString

일반 객체를 문자열로 변화하면 [object Object]가 된다.

let obj = {};

alert(obj);
// [object Object]
obj.toString();
// [object Object]

 

이렇게 [object Object]가 되는 이유는 toString의 구현 방식 때문이다. toString은 toString을 더 강력하게 만들어주는 기능이 있다. toString의 기능을 사용하면 확장 typeof, instanceof의 대안을 만들 수 있다.

https://tc39.es/ecma262/#sec-object.prototype.tostring 이 사이트 내용에 따르면, 객체에서 내장 toString을 추출하는 게 가능하다.

이렇게 추출한 메서드는 모든 값을 대상으로 실행할 수 있다. 호출 결과는 값에 따라 달라진다.

 

  • Boolean - [object Boolean]
  • Number - [object Number]
  • Undefined - [object Undefined]
  • Null - [object Null]
  • Array - [object Array]
  • 그 외 - 커스터마이징 가능
// 편의를 위해 toString 메서드를 변수에 복사
let objectToString = Object.prototype.toString;
let arr = [];

objectToString.call(arr);
// [object Array]

 

call(call / apply와 데코레이터, 포워딩 참고)을 사용해 컨텍스트를 this = arr로 설정하고 함수 objectToString을 실행.

toString 알고리즘은 내부적으로 this를 검사하고 상응하는 결과를 반환한다.

 

let a = Object.prototype.toString;

a.call(1234);
// [object Number]
a.call(alert);
// [object Function]
a.call(null);
// [object Null]

 

Symbol.toStringTag

특수 객체 프로퍼티 Symbol.toStringTag를 사용하면 toString의 동작을 커스터마이징 할 수 있다.

 

let test = {
  [Symbol.toStringTag]: "Test"
};

alert({}.toString.call(test));
// [object Test]

 

대부분의 호스트 환경은 자체 객체에 이와 유사한 프로퍼티를 구현해놓고 있다.

(브라우저 관련 예시)

 

// 특정 호스트 환경의 객체와 클래스에 구현된 toStringTag
alert( window[Symbol.toStringTag]); 
// Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); 
// XMLHttpRequest
alert( {}.toString.call(window) ); 
// [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); 
// [object XMLHttpRequest]

 

실행 결과처럼 호스트 환경 고유 객체의 Symbol.toStringTag 값은 [object...]로 쌓인 값과 동일하다. 이처럼 'typeof' 연산자의 강력한 변형들은 원시 자료형 뿐만 아니라 내장 객체에도 사용할 수 있고 커스터마이징도 가능하다. 내장 객체의 타입 확인을 넘어서 타입을 문자열 형태로 받고 싶다면 instanceof 대신, {}.toString.call을 사용할 수 있다.

 

참고

https://ko.javascript.info/instanceof

반응형

댓글