前言
classList越来越多浏览器支持,兼容性不是主要问题,但是也有一些局限性,比如add方法一次只能添加一个class,解决办法也很简单,在原型上添加新方法就行
js
DOMTokenList.prototype.adds = function(tokens) {
tokens.split(" ").forEach(function(token) {
this.add(token);
}.bind(this));
return this;
};
let bodyClasses= document.body.classList;
bodyClasses.adds("vinsea1 vinsea2").toString(); // vinsea1 vinsea21
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
但是出于学习的目的,来试着自己实现一个。
classList
常用方法
- add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了
- contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回 false
- remove(value):从列表中删除给定的字符串
- toggle(value):如果列表中已经存在给定的值,就删除;如果列表中没有给定的值,就添加
html
<div id="vinsea" class="vinseaA vinseaB"></div>
<script type="text/javascript">
vinsea.classList.add('vinseaC');
console.log(vinsea.classList.value); // vinseaA vinseaB vinseaC
vinsea.classList.remove('vinseaC');
console.log(vinsea.classList.value); // vinseaA vinseaB
vinsea.classList.toggle('vinseaA');
console.log(vinsea.classList.value); // vinseaB
</script>1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
全部方法
JavaScript
console.log(vinsea.classList.__proto__)
// add: ƒ add()
// contains: ƒ contains()
// entries: ƒ entries()
// forEach: ƒ forEach()
// item: ƒ item()
// keys: ƒ keys()
// length: (...)
// remove: ƒ remove()
// replace: ƒ replace()
// supports: ƒ supports()
// toString: ƒ toString()
// toggle: ƒ toggle()
// value: (...)
// values: ƒ values()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
类数组
类数组不是数组,但是效果上和某些操作上相似,可以当对象使用,也能当数组使用。 组成条件:
- 对象的属性必须是数字(索引)
- 必须有
length属性
JavaScript
// 数组
const vArray = ['哈哈', '嘻嘻', '嘿嘿']
console.log(vArray[0], vArray[1], vArray[2]) // 哈哈 嘻嘻 嘿嘿
// 对象(类数组)
const vObj = {
0: '哈哈',
1: '嘻嘻',
2: '嘿嘿',
length: 3
}
console.log(vObj[0], vObj[1], vObj[2]) // 哈哈 嘻嘻 嘿嘿1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
简单实现classList
构造函数
html5的classList的构造函数是:DOMTokenList
JavaScript
console.log(vinsea.classList.constructor.name) // DOMTokenList1
给DOM元素添加属性
所有HTML元素都有classList属性,所以他们必然是继承自一个公共的基类,根据原型链写一个简单的递归查找:
JavaScript
function findOwnProperty(proto, propertyName) {
if (proto.hasOwnProperty(propertyName)) {
console.log(`查找结束:${proto.constructor.name}`)
return proto.constructor.name
} else {
console.log(`当前构造函数为:${proto.constructor.name}`)
return findOwnProperty(proto.__proto__, propertyName)
}
}
const result = findOwnProperty(vinsea, 'classList')
console.log(result)
// 输出:
// 当前构造函数为:HTMLDivElement
// 当前构造函数为:HTMLDivElement
// 当前构造函数为:HTMLElement
// 查找结束:Element
// Element1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
结论是classList这个属性是来自Element类,所以自定义一个classList属性步骤为
- 给Element的原型添加自定义的属性,假设起名为:
vClassList - 自定义一个
vClassList的构造函数,假设起名为:VvDOMTokenList - 给
VvDOMTokenList添加常用方法 - 改变dom
最终代码
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>简单实现classList</title>
</head>
<body>
<div id="vinsea" class="vinseaA">
<h1>简单实现classList: </h1>
<h3>vClassList</h3>
</div>
</body>
<script type="text/javascript">
window.onload = function() {
/**
* vClassList的构造函数
* @param {Array} classes class数组
* @param {Object} dom 当前dom
*/
function VvDOMTokenList(classes, dom) {
// 通过 push,将this转为类数组
[].push.apply(this, classes);
// change属性用于改变数据时自动改变dom的class
Object.defineProperty(this, "change", {
enumerable: false, // 不可枚举
set: function(newValue) {
dom.className = newValue;
}
})
}
/**
* VvDOMTokenList构造函数的方法
* 通过Object.create,继承Array的特性
*/
VvDOMTokenList.prototype = Object.create(Array.prototype, {
// Object.create的写法,每个属性值都要写在value里
// 构造器指向
constructor: {
value: VvDOMTokenList
},
// 添加
add: {
value: function add(cls) {
if (this.contains(cls)) return;
[].push.call(this, cls);
this.change = [].join.call(this, ' ')
}
},
// 移除
remove: {
value: function remove(cls) {
for (var i = 0; i < this.length; i++) {
if (cls === this[i]) {
[].splice.call(this, i, 1);
this.change = [].join(this, ' ')
return cls;
}
}
}
},
// 是否包含
contains: {
value: function contains(cls) {
return [].includes.call(this, cls)
}
},
// 切换
toggle: {
value: function toggle(cls) {
this.contains(cls) ? this.remove(cls) : this.add(cls);
}
}
});
/**
* 给Element添加自定义的属性
*/
Object.defineProperty(Element.prototype, "vClassList", {
get: function() {
// 随便定义一个标识"__vinsea__"用于判断当前是否已经有实例化对象
if (!this.__vinsea__) {
const classes = this.className;
this.__vinsea__ = new VvDOMTokenList(classes.split(' '), this);
// 模仿classList,添加一个value属性
this.__vinsea__.value = classes;
}
return this.__vinsea__;
}
})
// 测试
console.log('默认class:', vinsea.classList);
vinsea.vClassList.add('vinseaB');
console.log('新增class vinseaB:', vinsea.vClassList);
vinsea.vClassList.remove('vinseaA');
console.log('删除class vinseaA:', vinsea.vClassList)
vinsea.vClassList.toggle('vinsea-active');
console.log('切换class vinsea-active:', vinsea.vClassList)
}
</script>
</html>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106