1. 程式人生 > >相容getElementsByClassName與classList(ie8及以上)

相容getElementsByClassName與classList(ie8及以上)

知乎文章地址:https://zhuanlan.zhihu.com/p/52423430

之前面試的時候考到對html元素的class進行增刪改,然後有前輩說如果使用classList並且通過原型實現相容,會非常滿意,由此想到對getElementsByClassName與classList這兩個介面進行瀏覽器相容。不過目前僅相容到ie8,下面是具體的一些方案:

相容getElementsByClassName:

// 相容getElementsByClassName
if (!('getElementsByClassName' in document)) {
  function getElementsByClassName (classList) {
    if (typeof classList !== 'string') throw TypeError('the type of classList is error')
    // 獲取父元素
    var parent = this
    // 獲取相應子元素
    var child = parent.getElementsByTagName('*')
    var nodeList = []
    // 獲得classList的每個類名 解決前後空格 以及兩個類名之間空格不止一個問題
    var classAttr = classList.replace(/^\s+|\s+$/g, '').split(/\s+/)
    for (var j = 0, len = child.length; j < len; j++) {
      var element = child[j]
      for (var i = 0, claLen = classAttr.length; i < claLen; i++) {
        var className = classAttr[i]
        if (element.className.search(new RegExp('(\\s+)?'+className+'(\\s+)?')) === -1) break
      }
      if (i === claLen) nodeList.push(element)
    }
    return nodeList
  }
  // 相容ie5及以上的document的getElementsByClassName介面
  document.getElementsByClassName = getElementsByClassName
  // 相容ie8及以上的getElementsByClassName介面
  window.Element.prototype.getElementsByClassName = getElementsByClassName
}

相容classList:

// 相容classList
// 定義一個classList物件
function ClassList (obj) {
  this.obj = obj
}
// 提取add、remove和contains中的公共方法
function op (self, valArr, tag) {
  var className = self.obj.className
  if (tag === 2) {
    if (valArr.length === 0) throw TypeError("Failed to execute 'contains' on 'DOMTokenList': 1 argument required, but only 0 present.")
    if (typeof valArr[0] !== 'string' || !!~valArr[0].search(/\s+/g)) return false
    return !!~className.search(new RegExp(valArr[0]))
  }
  for (var i in valArr) {
    if(typeof valArr[i] !== 'string' || !!~valArr[i].search(/\s+/g)) throw TypeError('the type of value is error')
    var temp = valArr[i]
    var flag = !!~className.search(new RegExp('(\\s+)?'+temp+'(\\s+)?'))
    if (tag === 1) {
      !flag ? className += ' ' + temp : ''
    } else if (tag === 3) {
      flag ? className = className.replace(new RegExp('(\\s+)?'+temp),'') : ''
    }
  }
  self.obj.className = className
}
ClassList.prototype.add = function () {
  var self = this
  op(self, arguments, 1)
}
ClassList.prototype.contains = function () {
  var self = this
  return op(self, arguments, 2)
}
ClassList.prototype.item = function (index) {
  typeof index === 'string' ? index = parseInt(index) : ''
  if (arguments.length === 0 || typeof index !== 'number') throw TypeError("Failed to execute 'toggle' on 'DOMTokenList': 1 argument required, but only 0 present.")
  var claArr = this.obj.className.replace(/^\s+|\s+$/, '').split(/\s+/)
  var len = claArr.length
  if (index < 0 || index >= len) return null
  return claArr[index]
}
ClassList.prototype.remove = function () {
  var self = this
  op(self, arguments, 3)
}
ClassList.prototype.toggle = function (value) {
  if(typeof value !== 'string' || arguments.length === 0) throw TypeError("Failed to execute 'toggle' on 'DOMTokenList': 1 argument(string) required, but only 0 present.")
  if (arguments.length === 1) {
    this.contains(value) ? this.remove(value) : this.add(value)
    return
  }
  !arguments[1] ? this.remove(value) : this.add(value)
}
var div = document.createElement("div")
if (undefined === div.classList) {
  console.log(111)
  // 相容ie8及以上的classList,不過對於ie8需要加上括號
  window.Element.prototype.classList = function () {
    return new ClassList(this)
  }
}
// 封裝一個方法獲取classList(因為上面封裝的是一個函式,為了統一呼叫,採用下面的方法)
function getClassList (el) {
  if (!el) throw TypeError("Failed to execute 'getClassList': 1 argumen required, but only 0 present.") 
  if (typeof el.classList === 'function') {
    return el.classList()
  } 
  return el.classList
}

上面這種方法稍微有點繁瑣,正常來說我們想要實現僅僅訪問classList屬性即可,故而,我們可以採用以下的方法,同樣相容到ie8:

if (!("classList" in document.documentElement)) {
  Object.defineProperty(window.Element.prototype, 'classList', {
    get: function () {
      var self = this

      function update(fn) {
        return function () {
          var className = self.className.replace(/^\s+|\s+$/g, ''),
            valArr = arguments

          return fn(className, valArr)
        }
      }

      function add_rmv (className, valArr, tag) {
        for (var i in valArr) {
          if(typeof valArr[i] !== 'string' || !!~valArr[i].search(/\s+/g)) throw TypeError('the type of value is error')
          var temp = valArr[i]
          var flag = !!~className.search(new RegExp('(\\s+)?'+temp+'(\\s+)?'))
          if (tag === 1) {
            !flag ? className += ' ' + temp : ''
          } else if (tag === 2) {
            flag ? className = className.replace(new RegExp('(\\s+)?'+temp),'') : ''
          }
        }
        self.className = className
        return tag
      }

      return {
        add: update(function (className, valArr) {
          add_rmv(className, valArr, 1)
        }),

        remove: update(function (className, valArr) {
          add_rmv(className, valArr, 2)
        }),

        toggle: function (value) {
          if(typeof value !== 'string' || arguments.length === 0) throw TypeError("Failed to execute 'toggle' on 'DOMTokenList': 1 argument(string) required, but only 0 present.")
          if (arguments.length === 1) {
            this.contains(value) ? this.remove(value) : this.add(value)
            return
          }
          !arguments[1] ? this.remove(value) : this.add(value)
        },

        contains: update(function (className, valArr) {
          if (valArr.length === 0) throw TypeError("Failed to execute 'contains' on 'DOMTokenList': 1 argument required, but only 0 present.")
          if (typeof valArr[0] !== 'string' || !!~valArr[0].search(/\s+/g)) return false
          return !!~className.search(new RegExp(valArr[0]))
        }),

        item: function (index) {
          typeof index === 'string' ? index = parseInt(index) : ''
          if (arguments.length === 0 || typeof index !== 'number') throw TypeError("Failed to execute 'toggle' on 'DOMTokenList': 1 argument required, but only 0 present.")
          var claArr = self.className.replace(/^\s+|\s+$/, '').split(/\s+/)
          var len = claArr.length
          if (index < 0 || index >= len) return null
          return claArr[index]
        }
      }
    }
  })
}

值得注意的是,ie10、11雖然支援classList,但是其中add和remove方法不支援刪除多個class,另外對於toggle方法,其傳入的第二個布林型別的引數無效。

另外提示,陣列的indexOf方法不支援ie8及以下的瀏覽器。

 

最後,分享一下鄙人的github地址~~~瀏覽器相容研究學習:

yaodebian/Browser-Compatible​github.com