扩展 HTML 原生标签(下)
上一节请到如何通过Object.defineProperty
为标签定义新的属性和重写现有的属性,这一节我们聊一下如何为标签添加方法和事件。
标签的方法
文章开头已经提到过使用原型属性来为标签增加一个方法。下面事件为 A 标签增加了一个jump
方法。
HTMLAnchorElement.prototype.jump = function() {
window.location.href = this.href;
}
定义完成后可以 在 Javascript 可以直接调用jump
方法。
<a id="QrossLink" href="http://www.qross.cn">星源数软</a>
<script type="text/javascript">
document.querySelector('#QrossLink').jump();
</script>
如果想为每一个标签增加同一个方法,可以在HTMLElement
上定义。
HTMLElement.prototype.getNodeName = function() {
return this.nodeName;
}
虽然也可以在 Object 对象上定义,但是非常非常不建议这么做。
Object.prototype.getNodeName = function() {
return this.nodeName;
}
这会让所有的数据类型都有了getNodeName
这个方法。
标签的事件
我们已经知道了如何定义扩展属性和扩展方法,最后我们来看一下事件如何定义。事件则是属性和事件的结合体,从本质上讲,事件是一种属性,但其数据类型是函数,跟方法相同。但其执行由特定的交互行为触发,而不是主动执行。我们以onclick
事件为例,解释一下事件的定义和执行过程。
首先我们为一个 DIV 标签定义一个onclick
事件。
<!--方法一-->
<div id="Test" onclick="alert('Hello World!')"></div>
<script type="text/javascript">
//方法二
document.querySelector('#Test').onclick = functino(ev) {
alert('Hello World!');
}
//方法三
document.querySelector('#Test').addEventListener('click', function(ev) {
alert('Hello World!');
});
</script>
定义事件可以有以上三种方法:方法一在元素属性上定义,我们甚至可以通过setAttribute('onclick', "alert('Hello World!')")
定义。方法二支持通过给onclick
赋值来定义,不值类型必须是函数,而不能是字符串。第一种方法和第二种方法都会指定onclick
“属性”,所有赋值后会互相覆盖。这么看事件好像真是一个属性。方法三是为元素添加事件监听,可以添加多次,添加的事件与onclick
“属性”互相独立,不会冲突。
定义完成后,我们用鼠标点击这个 DIV 所在的区域,则会执行这个事件。也可以调用document.querySelector('#Test').click()
方法来执行。这样看,onclick
事件即是当执行click
方法时要执行的逻辑。
因为事件本身就是自定义的逻辑,而且同一个事件还可以通过事件监听添加多次。所以下面就介绍一下如何定义自己的事件,以复选框为例,我们为事件添加两个方法onchange-checked
和onchange-unchecked
,即定义当复选框选中时和当复选框取消选中时触发的事件。
第一步,向定义属性或方法一样:
HTMLInputElement.prototype['onchange-checked'] = null;
HTMLInputElement.prototype['onchange-unchecked'] = null;
因为事件名称中间有中横线-
也就是减号,所有不能直接用点规则写在原型上.prototype.onchange-checked = null
。
第二步,编写元素属性上的处理逻辑,也就是上面第一种方法的解析:
this['onchange-checked'] = this.getAttribute('onchange-checked');
this['onchange-unchecked'] = this.getAttribute('onchange-unchecked');
上面介绍的第二种方法不用管,本身完成第一步之后,元素就可以接受属性的直接赋值。比如:
document.querySelector('#CheckBox1')['onchange-checked'] = function(ev) { ... }
document.querySelector('#CheckBox1')['onchange-unchecked'] = function(ev) { ... }
第三步,事件的执行逻辑,就是如何执行已经定义的事件。根据上面两步,事件的值可以是字符串或函数,那么我们就得有执行字符串或函数的处理方法。以下是实现代码:
HTMLInputElement.prototype.execute = function(eventName, ...args) {
let logic = this[eventName];
if (logic != null) {
if (typeof (logic) == 'function') {
logic.call(this, ...args);
}
else if (typeof (logic) == 'string') {
eval('_ = function() {' + logic + '}').call(this, ...args);
}
}
}
第四步,事件的触发逻辑,就是什么时候执行这个自定义事件。这个需要在绑定在原生事件上添加逻辑进行处理。以下是实现代码:
document.querySelector('#CheckBox1').addEventListener('change', function(ev) {
if (this.checked) {
this.execute('onchange-checked', ev);
}
else {
this.execute('onchange-unchecked', ev);
}
});
完整的代码如下:
<!--示便-->
<input id="CheckBox1" type="checkbox" onchange-checked="alert('Hello')" onchange-unchecked="alert('World')" />
<script type="text/javascript">
HTMLInputElement.prototype['onchange-checked'] = null;
HTMLInputElement.prototype['onchange-unchecked'] = null;
HTMLInputElement.prototype.execute = function(eventName, ...args) {
let logic = this[eventName];
if (logic != null) {
if (typeof (logic) == 'function') {
logic.call(this, ...args);
}
else if (typeof (logic) == 'string') {
eval('_ = function() {' + logic + '}').call(this, ...args);
}
}
}
document.querySelectorAll('input[type=checkbox]').forEach(checkbox => {
checkbox['onchange-checked'] = checkbox.getAttribute('onchange-checked');
checkbox['onchange-unchecked'] = checkbox.getAttribute('onchange-unchecked');
checkbox.addEventListener('change', function(ev) {
if (this.checked) {
this.execute('onchange-checked', ev);
}
else {
this.execute('onchange-unchecked', ev);
}
});
});
</script>
至此,关于如何扩展原生标签的属性、方法和事件已经全部介绍完,下一期会介绍一下如何创建自己的 HTML 标签。
上述的自定义事件其实原生 Javascript 也有相应的接口,请参考 HTML 标签自定义事件