相容getElementsByClassName與classList(ie8及以上)
阿新 • • 發佈:2018-12-17
知乎文章地址: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地址~~~瀏覽器相容研究學習: