实现classList前,先补充点知识
数组是特殊的对象,如何特殊?
- 1.对象属性是数字(索引)
- 2.有length属性
什么是类数组对象?
- 1.具备数组的特征
- 2.写法上跟数组一样,但不是数组,原型是Object。
类数组调用数组方法的方式
var o = {
0: 'a', 1: 'b', 2: 'c', length: 3}
[].push.call(o, 'd')
对classList的理解
classList是所有可视化DOM实例的属性,也是用来设置DOM的类
classList属性的值是DOMTokenList类型,本质是一个集合对象,提供了add,remove,toggle等函数操作类
提示:classList属性对象可以轻松地完成jQuery对class相关的操作功能,但是IE10以下不兼容
接下来我们打造一个可以兼容所有浏览器的classList
第一步:如何给所有DOM元素都加上我们自己定义的neClassList属性
首先思考所有DOM元素继承于哪里
<div id="mydiv" class="classA classB classAB"></div>
我们可以通过constructor得到对象是由哪个构造器创建出来的
// HTML元素属于HTMLElement对象
console.log(mydiv.constructor) //ƒ HTMLDivElement() { [native code] }
// native code 本地代码,表示调用当前系统的底层API
console.log(mydiv instanceof HTMLElement) // true
可以得出HTMLElement是所有元素的祖先类,故在HTMLElement上定义neClassList属性
Object.defineProperty(HTMLElement.prototype, 'neClassList', {
get: function () {
// 当前元素没有NEDOMTokenList时创建NEDOMTokenList,有就直接访问
if(!this.__dtl__) {
//__dtl__自己创建的私有变量
this.__dtl__ = new NEDOMTokenList(this.className.split(" "), this)// 将所有类名和dom传过去
}
return this.__dtl__
}
})
第二步:定义构造器NEDOMTokenList
function NEDOMTokenList (classes, dom) {
// 改造成类数组
[].push.apply(this, classes)
}
可以通过console.log(mydiv.neClassList)
验证
第三步:在原型链上添加我们想要的功能
NEDOMTokenList.prototype = {
// 原型链的标准,构造器
constructor: NEDOMTokenList,
// 添加元素
add: function add(clazz) {
if(this.contains(clazz)) return
[].push.call(this, clazz)
},
// 移除元素
remove: function remove(clazz) {
for(var i = 0; i<this.length; i++) {
if(clazz === this[i]) {
[].splice.call(this, i, 1)
return clazz
}
}
},
// 是否包含元素
contains: function contains(clazz) {
return [].includes.call(this, clazz)
},
// 切换元素
toggle: function toggle(clazz) {
this.contains(clazz) ? this.remove(clazz) : this.add(clazz)
}
}
但我们发现一个问题,添加了classC后并没有同步HTML中
mydiv.neClassList.add('classC')
第四步:利用监听器进行同步
// 每次操作完后重新设置value
Object.defineProperty(this, 'value', {
enomerable: false, // 设置私有变量不可枚举
set: function (nv) {
dom.className = nv
}
})
再在每个方法后面添加this.value = [].join.call(this, ' ')
,如
add: function add(clazz) {
if(this.contains(clazz)) return
[].push.call(this, clazz)
this.value = [].join.call(this, ' ')
},
这样就能实现同步了
至此,实现了可以兼容所有浏览器的classList,最后,奉上标准的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="mydiv" class="classA classB classAB"></div>
<script>
function NEDOMTokenList (classes, dom) {
// 改造成类数组
[].push.apply(this, classes)
// 监听器
Object.defineProperty(this, 'value', {
enomerable: false, // 设置私有变量不可枚举
set: function (nv) {
dom.className = nv
}
})
}
// 类数组的属性方法
NEDOMTokenList.prototype = Object.create(Array.prototype, {
// 基于数组的功能上扩展
// 原型链的标准,构造器
constructor: {
value: NEDOMTokenList // value{} 编译器的规则
},
// 添加元素
add: {
value: function add(clazz) {
if(this.contains(clazz)) return // 若包含元素则不添加
[].push.call(this, clazz)
this.value = [].join.call(this, ' ')
}
},
// 移除元素
remove: {
value: function remove(clazz) {
for(var i = 0; i<this.length; i++) {
if(clazz === this[i]) {
[].splice.call(this, i, 1)
return clazz
}
}
this.value = [].join.call(this, ' ')
}
},
// 是否包含元素
contains: {
value: function contains(clazz) {
return [].includes.call(this, clazz)
}
},
// 切换元素
toggle: {
value: function toggle(clazz) {
this.contains(clazz) ? this.remove(clazz) : this.add(clazz)
this.value = [].join.call(this, ' ')
}
}
})
Object.defineProperty(HTMLElement.prototype, 'neClassList', {
get: function () {
if(!this.__dtl__) {
this.__dtl__ = new NEDOMTokenList(this.className.split(" "), this)
}
return this.__dtl__
}
})
mydiv.neClassList.add('classC')
mydiv.neClassList.add('classA')
mydiv.neClassList.remove('classA')
mydiv.neClassList.add('classA')
console.log(mydiv.neClassList)
console.log(mydiv.neClassList.contains('classAB'))
</script>
</body>
</html>