Javascript

[Javascript] 함수에도 길이가 있다 - 123['toString'].length + 123의 결과값은?

apost 2023. 1. 31. 07:53

인터넷에 많이 알려진 코딩 문제 중의 하나입니다.

객체의 속성과 메서드에 접근하는 방법에 대한 이해를 어느 정도까지 하고 있는 것을 파악하기 위한 개미지옥 문제입니다.

 

실무에서 이런 코딩을 하지는 않습니다. 그리고 할일도 없습니다.

테스트를 위한 문제이고 보통 다음과 같은 패턴으로 질문을 합니다.

어차피 왜 이 값이 출력되는지 이해하지 못하면 의미가 없으므로 출력 결까지 적어놓습니다.

대괄호 위치에 들어갈 수 있는 문자열에 제약이 있습니다. 마찬가지로 뒤에서 설명합니다.

 

123['toString'].length + 123 // 124
100['valueOf'].length + 100 // 100

 

타입이 없는 변수가 주는 언어적인 장점은 무수히 많지만, 반대로 단점도 무수히 많습니다.

그중 대표적인 것인 변수를 연산에 사용할 때 연산을 위해, 변수 값을 하나의 타입에서 다른 타입으로 변환할 때 발생하는 문제입니다. 타입 캐스팅(Type casting) 문제라고도 하는 이 문제는 자바스크립트로 코드를 작성할 때 개발자들을 괴롭히는 가장 큰 문제입니다.

 

물론, 대부분의 경우, 정말 대부분의 경우, 변수의 값을 다른 타입으로 자동으로 변환해 주는 자바스크립트의 내부 로직은 정말 잘 작동합니다.

정해진 규칙에 따라 사용만 하면 개발자의 의도에 따라 원하는 타입으로 정확하게 캐스팅을 할 수도 있습니다.

 

예를 들어 다음 코드에서 변수 i는 정수지만, str_i는 명시적으로 문자열 변수가 됩니다. 정수 앞에 빈 문자열을 붙여줌으로써 정수 값이 문자열로 명시적으로 변환되도록 자바스크립트 인터프리터에게 유도를 해주는 것입니다.

 

var i = 10; // 정수 10
var str_i = '' + i; // 문자열 '10'

 

그렇다면 글 제목에 있는

 

123['toString'].length + 123

 

이 코드가 반환하는 값은 얼마일까요? 숫자 124입니다.

123['toString'].length 가 정수 1이 되기 때문입니다.

 

최종 결과값이 문자열이 될지 숫자일지 힌트가 없기 때문에 앞쪽의 계산 결과가 명확하게 0보다 같거나 큰 정수값이고 최종 결괏값도 123보다 크거나 같은 정수값이 된다고 1차적으로 추론을 할 수 있어야 합니다.

유사한 계산식이 몇 가지 있지만, 대동소이합니다.

 

".length" 속성은 0보다 같거나 큰 정수를 반환합니다.

따라서 123['특정문자열'].length의 최종 반환값이 정수이고 정수 + 123가 되어 최종적으로 반환하는 계산 결과 값은 정수 값이 됩니다.

결과가 뭐가 되었던 문자열로 답을 적으면 마이너스 백만점을 받게 됩니다.

틀리더라도 최소한 숫자를 적어야 빵점이 됩니다.

 

 

문제의 핵심은 123['특정문자열'] 입니다. 

이 부분을 이해하려면 설명이 좀 필요합니다.

자바스크립트 코딩을 좀 해봤다면 '12345'[3] 이 '4'라는 문자열의 4번째 문자 1개를 반환한다는 것 정도는 알고 있습니다.

그래서 혹시나 123이 문자열로 캐스팅되어 "123"이 되고, 배열 인덱스로 문자열이 들어갔으므로 false 에 해당하는 0으로 캐스팅되어 "123"[0] 이 되고, 첫 번째 문자 "1"이 반환되어 정수로 다시 캐스팅되면서 1 + 123 = 124가 된다.

 

그럴듯합니다.

망상입니다.

 

숫자에는 인덱스 위치로 값을 얻는 [] 연산자가 구현되어 있지 않습니다.

그러니까 100[] 연산자는 애초에 없습니다. 에러가 발생합니다.

 

따라서 100['특정문자열'] 이 무엇을 반환하는지를 이해해야 이 문제를 풀 수 있습니다.

 

 

 

 

함수에도 길이가 있다.

앞의 문제를 풀기 위해 자바스크립트의 한 가지 정의를 알아야 합니다.

함수(function(){})에는 길이 속성인 length 속성이 있고, 실제로 사용할 수 있습니다.

정확하게는 자바스크립트를 설계하고 만들던 초창기의 오류?, 또는 애매한 설계 결함이었고, 현재까지 그 상태로 유지가 되고 있는 것입니다.

 

다음 예를 확인해보겠습니다.

 

const params = ['라이언', 5, 'male']
const func1 = () => {}
const func2 = (name) => {}
const func3 = (name = '라이언') => {}
const func4 = (name, age = 5) => {}
const func5 = (name, age = 5, sex) => {}
const func6 = (name='라이언', age, sex) => {}
const func7 = (name = '라이언', age, sex) => {}
const func8 = (company, ...params) => {}
const func9 = (...params) => {}
console.log(func1.length) // 0
console.log(func2.length) // 1
console.log(func3.length) // 0
console.log(func4.length) // 1
console.log(func5.length) // 1
console.log(func6.length) // 0
console.log(func7.length) // 0
console.log(func8.length) // 1
console.log(func9.length) // 0

 

길이 값의 기준은 간단합니다.

기본값이 있는 매개변수 앞까지의 매개변수 개수입니다.

그러니까 첫 번째 매개변수부터 시작해서 기본 값이 없는 연속 매개변수 개수를 길이로 출력합니다. 펼침 연산자도 기본 값이 있는 매개변수로 간주되어 제외됩니다.

기본 값 적용된 첫 매개변수부터는 전부 길이 값 계산에서 제외됩니다.

 

함수에 길이 속성이 있다고 설명을 굳이 하는 이유는 "123['toString']" 가 함수가 되기 때문입니다.

정확하게는 Number.prototype.toString() 함수가 됩니다.

이 함수는 radix(진수) 파라미터 1개만을 받습니다. 기본 값 없는 파라메터 한 개 이므로 123['toString'].length 는 함수의 길이를 반환하는 규칙에 따라 1이 반환됩니다.

그래서 합이 124가 됩니다.

 

그리고 맨 앞의 123은 숫자 객체인 Number가 됩니다.

즉 Number 객체의 정적 메서드인 toString() 함수를 접근 연산자 []로 접근해서 익명 함수를 반환하게 됩니다.

객체의 속성에 접근할 때 객체명["속성명"]으로 접근을 하는 것과 같은 방식입니다.

 

당연하게도 대괄호([]) 안에 사용할 수 있는 문자열도 Number 객체의 메서드 이름으로 제한됩니다.

그 이외의 다른 문자열을 넣으면 undefined 에러가 발생합니다.

 

Number 객체의 valueOf() 메서드는 파라미터가 없습니다. 따라서 0이 반환되고 100['valueOf'].length 는 0이 됩니다.

 

방향을 조금 바꿔서 'str'['toUpperCase'].length+123 은 값이 몇이 될까요?

123입니다. 'str'['toUpperCase'].length 가 0이 되기 때문입니다. 문자열의 길이가 아니라 toUpperCase() 메서드의 기본값없는 파라미터 개수가 몇 개인지가 반환됩니다.

 

 

 

 

길이 값을 반환하는 2차 지옥

함수의 길이를 반환하는 것까지는 알았는데, 여기까지만 알고 그다음을 배우지 못한 개발자에게 2차 지옥이 기다리고 있습니다.

 

123['toString']().length + 123 // 126

 

결과 값은 이미 적혀 있습니다.

앞서의 것과는 괄호(())만 더 붙어 있습니다.

괄호가 붙는 것이 어떤 의미인지 개념을 모르고 있으면 앞서의 함수 길이 문제로 착각을 하고 그대로 개미지옥으로 들어가게 됩니다.

 

이 문제는 맨 앞의 값(123)에 대괄호 안의 함수를 적용할 수 있는 경우에만 가능합니다.

즉, 제한적으로만 사용할 수 있습니다.

숫자 123을 toString() 메서드로 바꾸면 문자열 "123"이 됩니다.

그리고 문자열 "123"의 길이는 3이 되므로 126이 됩니다.

대괄호 뒤 ()의 역할은 즉시 실행 함수로 123['toString'] 가 반환하는 익명 함수를 실행하는 것입니다.

(123['toString'])() 과 같습니다.

 

 

 

 

이 문제를 풀 수 있는 사람은 자바스크립트 개발을 해봤던 개발자 중에서도 한 자릿수 퍼센트입니다.(단언컨대 2-3% 안쪽입니다.)

그것도 이런 문제를 접해봤던, 그러니까 이런 문제가 있다더라 하고 봤었던 경우에만 풀 수 있습니다.

이런 문제를 처음 보면 10년을 자바스크립트 코딩을 했어도 못 풉니다.

현실 세계에서는 이런 코딩을 하는 멍청이가 없으니까...