IE와 Edge를 지원하기위해 버그를 잡아보자

IE와 Edge를 지원하기위해 버그를 잡아보자

document.getElementByName

document.getElementByName 는 Dom에서 name속성으로 원하는 element를 가져올 수 있다. 스팩상 __proto__NodeList인 형태로 값을 가져오는데 IEEdge에서는 Object로 가져오는것을 확인할 수 있다.

IE에서의 document.getElementByName __proto__ 이미지

  • IE에서의 document.getElementByName proto 이미지

  • Edge에서의 document.getElementByName proto 이미지

IE에서의 document.getElementByName __proto__ 이미지

  • Chrome에서의 document.getElementByName proto 이미지

따라서 이렇게 getElementByName으로 가져온 값을 forEach하려 할때 브라우져별로 동일하게 동작하지 않을 수 있다.

가능하면 기본적인 for문을 사용하거나, foreachfor in 을 사용하려면 강제로 형변환이 필요하다.

하지만 하위 브라우저나 여러 다양한 브라우저를 지원하기 위해서 기본적인 for문을 사용하는것이 가장 좋아보인다.

new URL()

URL 은 보통 URL을 javascript에서 params를 얻어오기 위해서 사용하고 있었는데 아래와 같이 사용하고 있었다.

// window.location.href = 'lanace.github.io?type=3';

var url = new URL(window.location.href);
url.searchParams.get('type');	// 3

그치만 IE에서는 URL을 지원하지 않을 뿐더러 문서에는 지원한다고 했던 Edge 에서도 정상적으로 동작하지 않았다. 그래서 stackOverFlow의 답변을 참고하여 수정하였다.

function decodeUriComponentWithSpace (component) {
    return decodeURIComponent(component.replace(/\+/g, '%20'))
  }

  // type : 'hash', 'search' or 'both'
  function getLocationParameters (location, type) {
    if (type !== 'hash' && type !== 'search' && type !== 'both') {
      throw 'getLocationParameters expect argument 2 "type" to be "hash", "search" or "both"'
    }

    let searchString = typeof location.search === 'undefined' ? '' : location.search.substr(1)
    let hashString = typeof location.hash === 'undefined' ? '' : location.hash.substr(1)
    let queries = []
    if (type === 'search' || type === 'both') {
      queries = queries.concat(searchString.split('&'))
    }
    if (type === 'hash' || type === 'both') {
      queries = queries.concat(hashString.split('&'))
    }

    let params = {}
    let pair

    for (let i = 0; i < queries.length; i++) {
      if (queries[i] !== '') {
        pair = queries[i].split('=')
        params[this.decodeUriComponentWithSpace(pair[0])] = this.decodeUriComponentWithSpace(pair[1])
      }
    }
    return params
}

   // TEST: 
window.location.hash = 'test=a&test2=b'
console.log(getLocationParameters(window.location, 'both'))

위 코드는 stackoverflow에서 가져온 코드이다. 당연한거지만 코드를 가져오더라도 어떻게 동작하는지, 내부적으로는 어떻게 돌아가는지 정도는 확인해보고 사용해야한다.

위 코드 에서 조금 이상해보이는 부분이 있는데 바로 decodeUriComponentWithSpace 함수이다. 이 함수는 component.replace(/\+/g, '%20') 하고 있는것을 볼 수 있는데 문자열에서 + 기호를 모두 %20으로 바꾼다는것이다. 이것이 의미하는게 무엇일까.

URL.search

new URL() 문제를 해결하고나니 연관된 이슈로 search params를 일부 수정해야되는 경우가 생겼다. 기존에 URL을 사용해서 url.search 를 하면 자동으로 search params가 나왔는데 이를 사용할 수 없게된 것이다.

즉 아래와 같이 사용할 수 없었다.

var url = new URL(window.location.href);
url.searchParams.set('type', 1);
url.searchParams.set('page', 1);

return url.pathname + url.search;

따라서 직접 구현해서 사용하기로 하였다. 우선 new URL() 문제는 위에서 getLocationParameters() 함수를 사용해 params object를 가져오고, 가져온 object에서 typepage를 수정하였다.

그리고 object를 search params로 만드는 작업은 아래의 코드를 이용하였다.

var objectToSearchParams = function(obj, prefix) {
  var str = [],
    p;
  for (p in obj) {
    if (obj.hasOwnProperty(p)) {
      var k = prefix ? prefix + "[" + p + "]" : p,
        v = obj[p];
      str.push((v !== null && typeof v === "object") ?
        serialize(v, k) :
        encodeURIComponent(k) + "=" + encodeURIComponent(v));
    }
  }
  return str.join("&");
}

이후 return 부분에서 window.location.pathname + objectToSearchParams(url); 하여 해결하였다.

Array.prototype.includes

Array.prototype.includes 에서 볼 수 있드시 IE에서는 지원하지 않는다.

문서에서 polyfill을 설명한대로 적용하여 해결하였다.

// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
  Object.defineProperty(Array.prototype, 'includes', {
    value: function(searchElement, fromIndex) {

      if (this == null) {
        throw new TypeError('"this" is null or not defined');
      }

      // 1. Let O be ? ToObject(this value).
      var o = Object(this);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If len is 0, return false.
      if (len === 0) {
        return false;
      }

      // 4. Let n be ? ToInteger(fromIndex).
      //    (If fromIndex is undefined, this step produces the value 0.)
      var n = fromIndex | 0;

      // 5. If n ≥ 0, then
      //  a. Let k be n.
      // 6. Else n < 0,
      //  a. Let k be len + n.
      //  b. If k < 0, let k be 0.
      var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

      function sameValueZero(x, y) {
        return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
      }

      // 7. Repeat, while k < len
      while (k < len) {
        // a. Let elementK be the result of ? Get(O, ! ToString(k)).
        // b. If SameValueZero(searchElement, elementK) is true, return true.
        if (sameValueZero(o[k], searchElement)) {
          return true;
        }
        // c. Increase k by 1. 
        k++;
      }

      // 8. Return false
      return false;
    }
  });
}

정리

아직 완벽하게 iechrome을 지원하는건 아니지만 최소한 위에서 찾았던 문제점들에 대해서는 작업할때 참고해서 작업해야겠다. chrome 점유율이 점점 더 높아지고 있긴 한데, 아직 edgeie를 사용하는 사용자도 많다ㅠ ~언제쯤 갈아타실꺼죠!~ 여튼 신기술을 좋아하긴 하지만 이런 부분에서는 약간 단점도 있는것같다.

적절하게 사용하는게 중요한듯…

계속해서 버그 잡으면서 업데이트 해야겠다.