개발/자바스크립트

자바스크립트 개발자라면 알아야 할 33가지 개념 - 4. 암묵적, 명시적, Nominal, 구조적 및 덕 타이핑

DOTBAAAM 2025. 6. 10. 12:45
반응형

암묵적, 명시적, Nominal, 구조적 및 덕 타이핑(Implicit, Explicit, Nominal, Structuring and Duck Typing)

Implicit Coercion(암묵적 강제 변환)

자바스크립트의 암묵적 강제 변환은 개발자가 명시적으로 타입을 변환하지 않아도 자바스크립트 엔진이 필요에 따라 타입을 다른 타입으로 강제 변환하는 것을 의미합니다.

3 * "3"; // 9
1 + "2" + 1; // '121'

true + true; // 2
10 - true; // 9

const foo = {
  valueOf: () => 2,
};

3 + foo; // 5
4 * foo; // 8

const bar = {
  toString: () => " promise is a boy :)",
};

1 + bar; // '1 promise is a boy :)'

숫자 표현식에서 숫자가 아닌 값

문자열

-, *, /, % 연산자 중 하나을 포함하는 숫자 표현식에서 피연산자로 문자열을 전달할 때마다, 숫자의 변환 과정은 내장 Number 함수를 호출하는 것과 유사합니다.

  • 숫자만 포함된 문자열: 숫자로 변환
3 * "3"; // 9
3 * Number("3"); // 9
Number("5"); // 5
  • 숫자가 아닌 값을 포함한 문자열: NaN 반환
Number("1,"); // NaN
Number("foo"); // NaN
Number("!@#$%"); // NaN
+ 연산자

+ 연산자는 덧셈과 문자열 연결이라는 두 가지 기능을 수행합니다.

+ 연산자의 피연산자가 문자열일 때, 자바스크립트는 문자열을 숫자로 변환하는 대신 숫자를 문자열로 변환합니다.

// 문자열 연결
1 + "2"; // '12'

// 덧셈
1 + 2; // 3

// 덧셈 + 문자열 연결
1 + 2 + "1"; // '31'

// 문자열 연결 + 문자열 연결
1 + "2" + 1; // '121'
객체

대부분의 자바스크립트 객체 변환은 [object object]를 반환합니다.

"name" + {}; // 'name[object Object]'

모든 자바스크립트 객체는 객체가 문자열로 변환될 때마다 호출되는 toString 메서드를 상속 받습니다.

toString 메서드의 반환 값은 문자열 연결 및 수학적 표현식과 같은 연산에 사용됩니다.

const foo = {};

foo.toString(); // '[object Object]'

const bar = {
  toString: () => "Hello, ",
};

bar + "world"; // 'Hello, world'

수학적 표현식일 때, 자바스크립트는 반환 값이 숫자가 아닌 경우 숫자로 변환하려고 시도합니다.

const foo = {
  toString: () => 4,
};

2 * foo; // 8
2 / foo; // 0.5
2 + foo; // 6

const bar = {
  toString: () => "four",
};

2 * bar; // NaN
2 + bar; // '2four'

const baz = {
  toString: () => "2",
};

2 + baz; // '22'
2 * baz; // 4
배열 객체

배열의 상속된 toString 메서드는 다르게 동작합니다. 인자 없이 join 메서드를 호출하는 것과 유사하게 동작합니다.

[1, 2, 3].toString(); // '1,2,3'
[].toString(); // ''

4 + [1, 2, 3]; // '41,2,3'
4 * [1, 2, 3]; // NaN
valueOf 메서드

문자열 또는 숫자 값이 예상되는 곳에 객체를 전달할 때마다, 자바스크립트에 의해 사용될 valueOf 메서드를 정의할 수 있습니다.

const foo = {
  valueOf: () => 3,
};

3 + foo; // 6
3 * foo; // 9

객체에 toStringvalueOf 메서드가 둘 다 정의되어 있는 경우, 자바스크립트는 valueOf 메서드를 사용합니다.

const bar = {
  toString: () => 2,
  valueOf: () => 5,
};

"sa" + bar; // "sa5"
3 * bar; // 15
2 + bar; // 7
True, False, ''
Number(true); // 1
Number(false); // 0
Number(""); // 0

4 + true; // 5
4 * false; // 0
3 + ""; // '3'
3 * ""; // 0

Falsy and Truthy

모든 자바스크립트 값은 true또는 false로 강제 변환 될 수 있습니다.

true로 강제 변환 되는 것은 값이 truthy 함을 의미하고, false로 강제 변환 되는 것은 값이 falsy 함을 의미합니다.

falsy를 반환하는 값
  1. false
  2. 0
  3. null
  4. undefined
  5. ""
  6. NaN
  7. -0
truthy를 반환하는 값

falsy를 반환하는 값을 제외한 나머지는 truthy를 반환합니다.

if (-1) // truthy
if ("0") // truthy
if ({}) // truthy

Explicit Coercion(명시적 강제 변환)

개발자에 의해 의도적으로 값의 타입을 변환하는 것을 명시적 강제 변환이라고 합니다.

문자열 타입으로 변환

String 생성자 함수를 new 연산자 없이 호출하는 방법
console.log(String(1)); // '1'
console.log(String(NaN)); // 'NaN'
console.log(String(Infinity)); // 'Infinity'
console.log(String(true)); // 'true'
console.log(String(false)); // 'false'
Object.prototype.toString 메서드를 사용하는 방법
console.log((1).toString()); // '1'
console.log(NaN.toString()); // 'NaN'
console.log(Infinity.toString()); // 'Infinity'
console.log(true.toString()); // 'true'
console.log(false.toString()); // 'false'
문자열 연결 연산자를 이용하는 방법
console.log(1 + ""); // '1'
console.log(NaN + ""); // 'NaN'
console.log(Infinity + ""); // 'Infinity'
console.log(true + ""); // 'true'
console.log(false + ""); // 'false'

숫자 타입으로 변환

Number 생성자 함수를 new 연산자 없이 호출하는 방법
console.log(Number("0")); // 0
console.log(Number("-1")); // -1
console.log(Number("10.53")); // 10.53
console.log(Number(true)); // 1
console.log(Number(false)); // 0
parseInt, parseFloat 함수를 사용하는 방법(문자열만 변환 가능)
console.log(parseInt("0")); // 0
console.log(parseInt("-1")); // -1
console.log(parseFloat("10.53")); // 10.53
단항 연결 연산자를 이용하는 방법
console.log(+"0"); // 0
console.log(+"-1"); // -1
console.log(+"10.53"); // 10.53
console.log(+true); // 1
console.log(+false); // 0
산술 연산자를 이용하는 방법
console.log("0" * 1); // 0
console.log("-1" * 1); // -1
console.log("10.53" * 1); // 10.53
console.log(true * 1); // 1
console.log(false * 1); // 0

불리언 타입으로 변환

Boolean 생성자 함수를 new 연산자 없이 호출하는 방법
console.log(Boolean("x")); // true
console.log(Boolean("")); // false
console.log(Boolean("false")); // true
console.log(Boolean(0)); // false
console.log(Boolean(1)); // true
console.log(Boolean(NaN)); // false
console.log(Boolean(Infinity)); // true
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean({})); // true
console.log(Boolean([])); // true
!부정 논리 연산자를 두번 사용하는 방법
console.log(!!"x"); // true
console.log(!!""); // false
console.log(!!"false"); // true
console.log(!!0); // false
console.log(!!1); // true
console.log(!!NaN); // false
console.log(!!Infinity); // true
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!{}); // true
console.log(!![]); // true

Nominal Typing(명목적 타이핑)

C++, Java, Swift와 같은 언어들은 주로 명목적 타입 시스템(Nominal Type System)을 사용합니다.

이 시스템에서는 타입의 이름(name)을 기준으로 타입 호환성을 검사하며, 타입의 구조가 같더라도 이름이 다르면 서로 호환되지 않습니다.

class UserId {
    int id;
}

class ProductId {
    int id;
}

UserId userId = new ProductId(); // 컴파일 오류

Structural Typing(구조적 타이핑)

OCaml, Haskell, Elm, TypeScript, Go와 같은 언어들은 주로 구조적 타입 시스템(Structural Type System)을 채택하고 있습니다.

구조적 타이핑(Structural Typing)은 타입 이름이 아닌, 멤버(구성 요소) 구조를 기준으로 타입을 검사하는 방식으로, 명목적 타이핑(Nominal Typing)과는 반대되는 개념입니다.

이 방식에서는 한 타입이 다른 타입이 요구하는 모든 멤버를 포함하고 있다면, 두 타입은 호환 가능한 타입으로 간주됩니다.

type Foo = { name: string };
type Bar = { name: string };

let foo: Foo = { name: "Alice" };
let bar: Bar = foo; // 구조가 동일하므로 호환 가능

Duck Typing(덕 타이핑)

Duck Typing(덕 타이핑)객체의 변수와 메서드 집합(행동)이 그 객체의 타입을 결정하는 방식입니다.

이 개념은 덕 테스트(Duck Test)에서 유래되었습니다.

“어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥 소리를 낸다면, 그 새는 오리라고 부를 수 있다.”

덕 타이핑은 Python, Ruby 같은 동적 타입 언어에서 사용됩니다.

class Duck:
    def __init__(self):
        self.name = '오리'

    def sound(self):
        print('꽥꽥!')

    def walk(self):
        print(f'{self.name}가 걷는다.')

class Dog:
    def __init__(self):
        self.name = '개'

    def sound(self):
        print('왈왈!')

    def walk(self):
        print(f'{self.name}가 걷는다.')

Duck 클래스와 Dog 클래스는 서로 다른 클래스이지만, 둘 다 sound()walk() 메서드를 가지고 있습니다.

def make_sound(duck):
  duck.sound()

def take_walk(dog):
  dog.walk()

이 두 함수는 객체가 어떤 타입인지 명시적으로 검사하지 않고, 그저 sound() 또는 walk() 메서드가 존재한다고 가정하고 호출합니다.

duck = Duck()
dog = Dog()

make_sound(duck)  # 꽥꽥!
make_sound(dog)   # 왈왈!

take_walk(duck)   # 오리가 걷는다.
take_walk(dog)    # 개가 걷는다.

두 객체는 서로 다른 클래스이지만 동일한 인터페이스(메서드)를 구현하고 있기 때문에, 덕 타이핑 방식에서는 문제 없이 작동합니다.

반응형