관련 지식
#javascript

자바스크립트를 공부하면 프로토타입 에 대해서 보게 되는데 프로토타입을 사용하는 방법이 너무 OOP 관점에만 치우쳐진듯 하여 조금 다르게 정리해 봅니다. OOP 코드와 상관없이 이를 어떤식으로 활용할 수 있는지 소개해 보겠습니다.

프로토타입 이란 객체가 가지고 있는 고유의 속성으로 객체가 만들어질때 새로 생성되지 않고 참조되는 값입니다.
우리가 자주쓰는 자바스크립트 객체 중에 Array가 있습니다. 그리고 Array 객체의 함수 중에 toString이라는 함수가 있습니다. toString 함수를 이용하여 자세한 특징을 확인 해보겠습니다.

toString() 기능 확인

source)

  1. console.log(Array);
  2. var arr1 = new Array();
  3. arr1.push("첫번째");
  4. arr1.push("두번째");
  5. console.log(arr1);
  6. console.log(arr1.toString());

output)

  1. D:>node prototype
  2. [Function: Array]
  3. [ '첫번째', '두번째' ]
  4. 첫번째,두번째

4번째 줄은 객체로써 출력되었고 toString() 함수가 사용된 5번째 줄은 배열의 값이 문자열로 출력 되었습니다. arr1 변수의 toString 속성을 재정의 하면 어떻게 될까요?

수정된 source)

  1. var arr1 = new Array();
  2. arr1.push("첫번째");
  3. arr1.push("두번째");
  4. console.log(arr1);
  5. arr1.toString = function() { //추가
  6. return "deprecated"
  7. };
  8. console.log(arr1.toString());

output)

  1. D:>node prototype
  2. [Function: Array]
  3. [ '첫번째', '두번째' ]
  4. deprecated

toString() 함수에서 배열의 값이 아닌 문자열 ‘deprecated’ 가 출력되는 것을 볼수 있습니다. toString 속성이 재정의 되면서 기존 함수를 엎어쓴걸까요? 다음 예제를 위 소스에 이어서 추가하여 실행해 보겠습니다.

추가할 source)

  1. var arr2 = new Array();
  2. arr2.push("세번째");
  3. arr2.push("네번째");
  4. console.log(arr2.toString());

output)

  1. D:>node prototype
  2. [Function: Array]
  3. [ '첫번째', '두번째' ]
  4. deprecated
  5. 세번째,네번째

arr2.toString() 은 원래 기능으로 동작하는것을 볼수 있습니다.

동작 설명

위 코드의 동작 구조는 다음과 같습니다.

  1. Array는 prototype으로 toString() 함수를 가지고 있다.
  2. arr1 객체를 만들면 arr1의 toString 속성은 Array 객체의 prototype인 toString() 함수가 된다.
  3. arr1 객체의 toString 속성으로 함수를 정의하면 toString() 함수가 만들어지고 참조 우선순위에 의해 새로 만들어진 것을 사용하게 된다.
  4. 새로 만들어진 toString() 함수는 arr1에만 존재한다. 따라서 arr2 객체의 toString 은 Array의 toString() 함수가 된다.

설명이 조금 복잡한가요? 그림으로 보겠습니다.

Array 객체는 prototype 속석으로 여러 함수를 가지고 있습니다. 눈으로 직접 확인하려면 크롬 개발자 도구를 이용하는 것이 좋습니다. 그러면 아래와 같은 화면을 볼 수 있습니다.

Array.prototype

arr1객체를 만들면 arr1.toString() 형태로 호출할수 있는데 이때 toString() 함수는 arr1 객체가 가지고 있는것이 아니라 Array 객체의 prototype 에 있는 toString() 함수입니다. 왜냐하면 객체를 새로 만들때 prototype 함수는 새로 생성하지 않고 각 객체가 prototype 함수를 사용하기 때문입니다. 글이 길어지니 복잡해지네요. 여러개의 객체를 만들 경우 아래와 같은 그림이 됩니다.

toString 함수위치

그림으로 보면 간단한 내용입니다. 우리가 사용하고 있던 toString() 함수가 알고봤더니 변수에 있던게 아니라 Array 객체의 prototype 에 있던거구나! 하는 내용입니다.

  1. arr1.toString = function() { //추가
  2. return "deprecated"
  3. };

그런데 위와 같이 코드를 추가하면 어떻게 되었을까요?

toString 추가

변수에 toString 키가 추가 되었습니다. 따라서 arr3 변수를 통해 호출 가능한 toString이 두개가 된 셈인데 우선 순위에 의해 변수값에 추가된 toString()함수가 호출되는 것입니다. 실제로 그렇게 되었는지 브라우저 개발자도구에서 아래 소스를 실행하고 결과를 보겠습니다.

실행코드)

  1. var arr1 = new Array();
  2. console.log(arr1);
  3. //여기 위 소스와 아래 부분을 따로 따로 나누어 실행해야 함.
  4. arr1.toString = function() {
  5. return "deprecated";
  6. };
  7. console.log(arr1);

output)

개발자도구결과

어렵지 않죠? 물론 변수에 추가한 toString은 해당 변수에만 존재할 뿐 다른 변수에는 존재하지 않습니다.

prototype 함수 재정의하기

그렇다면 Array 로 생성하는 모든 변수의 toString() 함수를 다른것으로 바꾸려면 어떻게 해야할까요? 간단합니다. toString() 함수가 prototype에 있는것이라 했으니 그것을 재정의 하면 됩니다. 아래 실행해 보겠습니다.

  1. Array.prototype.toString = function() { //prototype의 toString 재정의
  2. return "deprecated"
  3. };
  4. Array.toString2 = function() { //Array 객체에 새로운 키 추가
  5. return "2222";
  6. }
  7. var arr1 = new Array();
  8. arr1.push("첫번째");
  9. arr1.push("두번째");
  10. console.log(arr1.toString()); //deprecated 출력
  11. var arr2 = new Array();
  12. arr2.push("세번째");
  13. arr2.push("네번째");
  14. console.log(arr2.toString()); //deprecated 출력
  15. console.log(arr2.toString2()); //Uncaught TypeError: arr2.toString2 is not a function

두개의 변수는 기본적으로 Array.prototype의 toString을 참조하기 때문에 Array의 toString을 수정하면 동일하게 적용됩니다. 여기까진 위에서 확인한 내용이죠.
그러나 toString2()로 만든 함수는 prototype에 추가된 것이 아니기 때문에 새로운 객체에 만들어지지도, 참조되지도 않습니다. 즉 prototype에 적용하는 함수만 새로운 객체에서 참조할 수 있게 됩니다.

만약 Array로 만든 변수 arr1에 prototype을 지정하면 어떻게 될까요?

  1. arr1.prototype.toString = function() {
  2. return "이것은?"
  3. };

크롬에선 “Uncaught TypeError: Cannot set property ‘toString’ of undefined” 오류가 나네요. 다른 브라우저도 비슷하게 오류가 날것 같습니다.

prototype 에 대해선 복잡하게 생각하지 말고 간단하게 두 가지만 기억하면 됩니다.

  1. 객체의 prototype을 추가/수정하면 그 객체로 만들어진 모든 객체는 동일하게 적용된다.
  2. prototype으로 추가된 함수는 객체 생성시 매번 만들어지지 않으므로 효율이 좋다.

prototype의 활용

prototype 을 설명한 블로그 등을 찾아보면 일반적으로 객체를 만들고, 상속받는 형태인 OOP 코드 예제를 많이 사용합니다. 그래서 그에 대한 설명은 생략하고 다른 형태의 활용법을 보여 드리겠습니다.

  1. var lyric = " \
  2. I waited 'til I saw the sun \
  3. I don't know why I didn't come \
  4. I left you by the house of fun \
  5. I don't know why I didn't come \
  6. I don't know why I didn't come \
  7. ";
  8. console.log(lyric.replace("I", "We"))

위의 가사는 노라존스의 don’t know why의 가사 일부입니다. replace 함수는 문자를 치환할 수 있는 함수로 위의 소스는 ‘I’를 ‘We’로 변경하려는 것입니다.
하지만 안타깝게도 자바스크립의 replace() 함수는 문자로 지정시 매칭되는 첫번째 글자만 치환하고 그 이후의 문자에 대해선 처리하지 않습니다. 모든 문자를 치환하고 싶다면 정규표현식을 써야하죠. 자바의 replaceAll() 함수가 있으면 좋을듯 합니다.

만들어 보겠습니다.

  1. String.prototype.replaceAll = function(regex, str) {
  2. return this.split(regex).join(str);
  3. };
  4. console.log(lyric.replaceAll("I", "We"))

완성입니다. 위 코드를 모든곳에서 공통으로 호출하는 함수, 스코프 등에서 호출하면 어디서건 필요한 순간에 replaceAll() 함수를 사용할 수 있게 되니다. 어렵습니까?

자바스크립트를 쓰다보면 자주써야 하는 함수가 기본제공이 안되거나 오동작이 발생하는 불편한 경우가 있습니다. 예를들어 문자로된 숫자 “08”을 숫자로 변환하고 싶을때 parseInt() 함수를 아래와 같이 사용할 수가 있습니다.

  1. parseInt("08"); //두번째 인수 생략
  2. parseInt("08", 10); //두번째 인수 10진수로 고정

대부분의 브라우저에서는 두번째 인수를 생략하여도 크게 문제가 없지만 ie8 에서는 인수를 생략할 경우 0을 반환하는 이상한 기능을 보입니다.
MDN Web Docs 하단 내용 참고

또한 무리하게 한줄로 작성하기 위해 함수 중첩을 많이 사용하면 가독성이 매우 떨어집니다. 아래 소스를 보시죠. 두번째 소스는 String 객체에 toInt() 함수를 추가 했을때 사용할 수 있는 방법입니다.

trim()과 parseInt() 중첩된 예)

  1. //(1000 + 33) * 11을 구현
  2. var amount = (parseInt($.trim("1000"), 10) + parseInt($.trim("33"), 10)) * parseInt($.trim("11"), 10)
  3. var amount = ("1000".trim().toInt() + "33".trim().toInt()) * "11".trim().toInt();

필요하다면 toInt()안에 trim 기능도 포함시킬수 있습니다.

toInt() 안에 trim을 넣었을 경우)

  1. var amount = (parseInt($.trim("1000"), 10) + parseInt($.trim("33"), 10)) * parseInt($.trim("11"), 10)
  2. var amount = ("1000".toInt() + "33".toInt()) * "11".toInt();

소스가 훨씬 간결해집니다. 자주써야 하는 함수는 이렇게 prototype 으로 지정해두면 굉장히 편리하게 이용할수 있습니다.

자주 쓰는 예

  1. //문자열의 앞뒤 공백을 없앰
  2. String.prototype.trim = function () {
  3. var s = (this!=null) ? this : "";
  4. s = s.replace(/^\s+/g,"");
  5. s = s.replace(/\s+$/g,"");
  6. return s;
  7. };
  8. //문자열 전체에 대해 매칭되는 문자 치환하기
  9. String.prototype.replaceAll = function(regex, str) {
  10. return this.split(regex).join(str);
  11. };
  12. //문자열로 된 숫자를 정수로 변환(형변환)
  13. String.prototype.toInt = function() {
  14. return parseInt(this.trim().replaceAll(",", ""), 10);
  15. };
  16. //한자리 숫자 앞에 0을 붙임
  17. String.prototype.twoDigit = function() {
  18. if(this.toInt() < 10)
  19. return "0" + this;
  20. return this;
  21. };
  22. //한자리 숫자(문자아님)를 두자리 형태(문자)로 변환
  23. Number.prototype.twoDigit = function() {
  24. if(this < 10)
  25. return "0" + this;
  26. return this;
  27. };
  28. var str = "7";
  29. var num = 3;
  30. var money = " 123,456";
  31. console.log(str === 7);
  32. console.log(str.toInt() === 7);
  33. console.log(money.toInt());
  34. console.log(str.twoDigit());
  35. console.log(num.twoDigit());

이외에도 prototype에 필수 함수를 추가하여 자바스크립트 개발을 편하게 만들수 있는 기능들은 얼마든지 있을 것 같습니다. 공통 유틸 함수대신 prototype을 적극 사용해보세요.