관련 지식
#javascript #jquery #extend #default-option

자바스크립트 뿐 아니라 모든 프로그래밍 언어는 함수에 정의되는 argument가 primitive type 이 많을수록 유연한 함수에서 멀어집니다. argument의 일부를 옵션값으로 정의하고 싶은 경우에도 파라미터 순서를 지키거나/고려해야 하기 때문입니다.

primitive type 은 객체나 함수가 아닌 원시 데이터 타입을 의미.
참고 : Primitive

  1. function fnCall(n1, o1, o2, o3) {
  2. }
  3. fnCall('필수', null, '옵션2');
  4. fnCall('필수', '옵션1', null, '옵션3'); //옵션값을 넣치 않을때도 순서는 지켜야함

그래서 함수의 인자를 늘리기보다 값을 재 정의한 객체를 넘기는 형태로 함수의 인자 갯수를 조절하는게 확장성이 좋습니다.

  1. function fnCall(n1, o) {
  2. }
  3. fnCall('필수', { o2 :'옵션2' });
  4. fnCall('필수', { o3 :'옵션3', o1:'옵션1' }); //옵션 순서는 의미없음

디폴트 값이 필요한 이유

아래 소스는 ajax() 의 간단한 예제입니다. 실제로 이정도로 간단하게 작성하는 경우는 없겠지만 호출이 가능한 샘플임은 틀림없습니다.

  1. $.ajax({
  2. url: "test.jsp",
  3. success: function() {
  4. }
  5. });

그런데 저 샘플에는 method도 파라미터도 정의되지 않았습니다. 실제로 ajax()함수에 정의할수 있는 값은 20가지가 넘습니다. 즉 ajax() 함수를 호출할때 정의한 두 가지 항목을 제외한 나머지 값은 디폴트 값으로 동작한다는 것입니다. 디폴트 값이 존재할 경우 전체 항목을 반드시 정의할 필요없이 필요한 항목만 정의하면 되므로 함수 호출이 매우 편리해집니다.

그럼 디폴트 옵션값을 내부적으로 정의해두고 함수 호출쪽에서 새롭게 정의한 옵션값이 있으면 그것을 디폴트 값보다 우선 적용하면 될것입니다. 디폴트 값과 사용자 정의 값을 어떻게 합치면 될까요?

$.extend 의 사용

JQuery 에는 extend() 함수가 있고 그 함수를 호출하면 간단하게 옵션을 합칠수 있습니다.

  1. //기본 옵션
  2. var defaultOption = {
  3. key1 : 'aaa',
  4. key2 : 'bbb',
  5. key3 : 82,
  6. key4 : function defaultFn() {
  7. },
  8. key5 : 'ccc'
  9. };
  10. var userOption = {
  11. key1 : 'AAA', //문자열 재정의
  12. key4 : function newFn() { //함수를 재정의
  13. },
  14. key7 : 'dump' //없는 항목 정의
  15. };
  16. $.extend(defaultOption, userOption);
  17. console.log(defaultOption);

output)

  1. {
  2. key1: "AAA",
  3. key2: "bbb",
  4. key3: 82,
  5. key4: [Function: newFn],
  6. key5: "ccc",
  7. key7: "dump"
  8. }

기대하는 대로 값이 나왔나요? $.extend() 함수는 두 객체의 값을 결합합니다. 정확히 말하면 첫번째 인자의 값에 두번째 인자의 값을 엎어친다고 보는게 맞습니다. defaultOption 변수의 값 자체가 바뀌기 때문입니다.. 만약 이러한 결과를 기대한게 아니라면 기능을 만들어야 합니다.

combine 예제

이것은 $.extend() 함수와는 다른 기능입니다. 직접 구현할 기능은 아래 세 가지 조건을 만족하는 기능입니다.

  1. 원본 디폴트 옵션 변수는 변조시키지 않음
  2. 디폴트 옵션에 정의되지 않은 항목은 결합되지 않음
  3. 사용자 정의값에서 빈값은 정의 불가

$.extend() 와는 다르게 기준이 되는 디폴트 옵션에 정의된 항목만 결합할것. 그리고 디폴트 옵션을 변경 시키지 않고 결합된 객체를 리턴하는 것이 구현 목표 입니다.

  1. function combine(def, usr) {
  2. var n = {};
  3. for(var prop in def) {
  4. n[prop] = usr[prop] || def[prop] ;
  5. }
  6. return n;
  7. }
  8. var op = combine(defaultOption, userOption);
  9. console.log(op);

output)

  1. {
  2. key1: 'AAA',
  3. key2: 'bbb',
  4. key3: 82,
  5. key4: [Function: newFn],
  6. key5: 'ccc'
  7. }

이전에 설명드린 논리 연산을 이용하여 간단하게 구현하였습니다. key7은 기본 옵션에는 없는 잘못된 정의이기 때문에 리턴값에 포함되지 않았습니다.

extend 예제

$.extend() 함수는 강력한 기능이지만 JQuery 를 사용하지 않는 환경이라면 사용하지 못하게 됩니다. 이번엔 extend() 함수를 구현하겠습니다. JQuery의 그것과 다른점은 역시 디폴트 옵션값은 변경시키지 않는다는 것입니다.

  1. function extend(def, usr) {
  2. var n = {};
  3. for(var prop in def) {
  4. n[prop] = def[prop];
  5. }
  6. for(var prop in usr) {
  7. n[prop] = usr[prop] || n[prop];
  8. }
  9. return n;
  10. }
  11. var op2 = extend(defaultOption, userOption);
  12. console.log(op2);

output)

  1. {
  2. key1: 'AAA',
  3. key2: 'bbb',
  4. key3: 82,
  5. key4: [Function: newFn],
  6. key5: 'ccc',
  7. key7: 'dump'
  8. }

combine (deep copy)

그런데 위 두개의 예제는 depth가 1인 옵션에 대해서만 적용되고 아래 데이터와 같은 2 이상에서는 정상적으로 동작하지 않습니다.

  1. var defaultOption = {
  2. group1 : {
  3. key1 : 'aaa',
  4. key2 : 'bbb'
  5. },
  6. group2 : {
  7. key3 : 82,
  8. key4 : function defaultFn() {
  9. },
  10. key5 : 'ccc'
  11. },
  12. group3 : {
  13. group4 : {
  14. key6 : 100,
  15. key7 : 'ddd'
  16. }
  17. }
  18. };

이렇게 depth가 다른 경우엔 deep copy 가 되어야 합니다. 반복 로직을 줄이기 위해 몇 개의 함수가 더 필요하게 되었는데 combine() 함수의 내부에 선언하여 외부 노출을 막았습니다.

  1. function combine(def, usr) {
  2. function isEnd(v, k) {
  3. return ((v instanceof Function) || !(v instanceof Object));
  4. }
  5. function _(dst, def, usr) {
  6. for(var prop in def) {
  7. if(prop == 0) {
  8. return;
  9. }
  10. if(isEnd(def[prop], prop)) {
  11. dst[prop] = usr[prop] || def[prop];
  12. }
  13. else {
  14. dst[prop] = {};
  15. _(dst[prop], def[prop], usr[prop]);
  16. }
  17. }
  18. }
  19. var dst = {};
  20. _(dst, def, usr);
  21. return dst;
  22. }
  23. var op = combine(defaultOption, userOption);
  24. console.log(op);

output)

  1. {
  2. group1: {
  3. key1: 'AAA',
  4. key2: 'bbb'
  5. },
  6. group2: {
  7. key3: 180,
  8. key4: [Function: newFn],
  9. key5: 'ccc'
  10. },
  11. group3: {
  12. group4: {
  13. key6: 100,
  14. key7: 'ddd'
  15. }
  16. }
  17. }

이 소스의 완성된 샘플은 맨 밑에 추가하겠습니다.

정리

여기서 선보인 샘플이 정답은 아닙니다. 상황에 따라 다른 기능을 써야 할 수도 있죠. 이러한 코딩 방법이 있다는 것만 참고하시면 될 것 같습니다.

최종 샘플

  1. //기본 옵션
  2. var defaultOption = {
  3. group1 : {
  4. key1 : 'aaa',
  5. key2 : 'bbb'
  6. },
  7. group2 : {
  8. key3 : 82,
  9. key4 : function defaultFn() {
  10. },
  11. key5 : 'ccc'
  12. },
  13. group3 : {
  14. group4 : {
  15. key6 : 100,
  16. key7 : 'ddd'
  17. }
  18. }
  19. };
  20. var userOption = {
  21. group1 : {
  22. key1 : 'AAA', //재정의 했음
  23. },
  24. group2 : {
  25. key3 : 180, //재정의함
  26. key4 : function newFn() { //함수를 재정의 함
  27. },
  28. key5 : 'ccc'
  29. },
  30. group3 : {
  31. key6 : 200, //잘못정의했음
  32. group4 : {
  33. key8 : 'dump' //잘못정의했음
  34. }
  35. }
  36. };
  37. function combine(def, usr) {
  38. function isEnd(v, k) {
  39. return ((v instanceof Function) || !(v instanceof Object));
  40. }
  41. function _(dst, def, usr) {
  42. for(var prop in def) {
  43. if(prop == 0) {
  44. return;
  45. }
  46. if(isEnd(def[prop], prop)) {
  47. dst[prop] = usr[prop] || def[prop];
  48. }
  49. else {
  50. dst[prop] = {};
  51. _(dst[prop], def[prop], usr[prop]);
  52. }
  53. }
  54. }
  55. var dst = {};
  56. _(dst, def, usr);
  57. return dst;
  58. }
  59. var op = combine(defaultOption, userOption);
  60. console.log(op);