IE8及以下:attachEvent
element.attachEvent(type, listener)
标准的绑定事件监听函数的方法:addEventListener
element.addEventListener(type, listener, useCapture)
addEventListener方法的listener监听函数在元素的作用域内进行,this指向当前元素;attachEvent方法的listener监听函数在全局作用域下运行,this指向window。
注意:target.addEventListener、target.attachEvent与target.onclick这类方法的区别:
function addEvent(target, type, listener) {
try {
// Chrome, FireFox, Opera, Safari, IE9&+
target.addEventListener(type, listener, false)
} catch (e) {
try {
// IE6 - IE10, not available in IE11
target.attachEvent('on' + type, listener)
} catch (err) {
// all browsers
target['on' + type] = listener
}
}
}
// or shorter one like this:
function addEvent(target, type, listener) {
if (target.addEventListener) {
// non-IE, IE9&+
target.addEventListener(type, listener, false)
} else if (target.attachEvent) {
// IE6 - IE10, not available in IE11
target.attachEvent('on' + type, listener)
} else {
// all browsers
target['on' + type] = listener
}
}
function addEvent(target, type, listener) {
try {
// Chrome, FireFox, Opera, Safari, IE9&+
target.addEventListener(type, listener, false)
} catch (e) {
try {
// IE6 - IE10, not available in IE11
target.attachEvent('on' + type, listener)
} catch (err) {
// all browsers
target['on' + type] = listener
}
}
}
// or shorter one like this:
function addEvent(target, type, listener) {
if (target.addEventListener) {
// non-IE, IE9&+
target.addEventListener(type, listener, false)
} else if (target.attachEvent) {
// IE6 - IE10, not available in IE11
target.attachEvent('on' + type, listener)
} else {
// all browsers
target['on' + type] = listener
}
}
function removeEvent(target, type, listener) {
if (target.removeEventListener) {
target.removeEventListener(type, listener, false)
} else if (target.detachEvent) {
target.detachEvent('on' + type, listener)
} else {
target.detachEvent['on' + type] = null
}
}
function removeEvent(target, type, listener) {
if (target.removeEventListener) {
target.removeEventListener(type, listener, false)
} else if (target.detachEvent) {
target.detachEvent('on' + type, listener)
} else {
target.detachEvent['on' + type] = null
}
}
JS中事件流的三个阶段:捕获(低版本IE不支持)==>目标==>冒泡。
如果不同层的元素使用useCapture不同, 会先从最外层元素往目标元素寻找设定为capture模式的事件, 到达目标元素后执行目标元素的事件后,在循原路往外寻找设定为bubbling模式的事件。
书写原生js脚本将body下的第二个div隐藏:
var oBody = document.getElementsByTagName('body')[0]
var oChildren = oBody.childNodes
var nDivCounter = 0
for (var i = 0, len = oChildren.length; i < len; i++) {
if (oChildren[i].nodeName === 'DIV') {
nDivCounter++
if (nDivCounter === 2) {
oChildren[i].style.display = 'none'
}
}
}
var oBody = document.getElementsByTagName('body')[0]
var oChildren = oBody.childNodes
var nDivCounter = 0
for (var i = 0, len = oChildren.length; i < len; i++) {
if (oChildren[i].nodeName === 'DIV') {
nDivCounter++
if (nDivCounter === 2) {
oChildren[i].style.display = 'none'
}
}
}
<ul id="list" class="foo">
<li>#0</li>
<li><span>#1</span></li>
<li>#2</li>
<li>#3</li>
<li>
<ul>
<li>#4</li>
</ul>
</li>
<!-- ... -->
<li><a href="//v2ex.com">#99998</a></li>
<li>#99999</li>
<li>#100000</li>
</ul>
<ul id="list" class="foo">
<li>#0</li>
<li><span>#1</span></li>
<li>#2</li>
<li>#3</li>
<li>
<ul>
<li>#4</li>
</ul>
</li>
<!-- ... -->
<li><a href="//v2ex.com">#99998</a></li>
<li>#99999</li>
<li>#100000</li>
</ul>
// 还原题目真实DOM结构
var list = document.getElementById('list')
void function() {
var html = ''
for (var i = 0; i <= 10000; i++) {
if (i === 1) {
html += '<li><span>#1</span></li>'
} else if (i === 4) {
html += '<li><ul><li>#4</li></ul></li>'
} else if (i === 9998) {
html += '<li><a href="//v2ex.com">#9998</a></li>'
} else {
html += '<li>#' + i + '</li>'
}
}
list.innerHTML = html
}()
// or, list.className += ' bar'
list.classList.add('bar')
var li10 = document.querySelector('#list > li:nth-of-type(10)')
li10.parentNode.removeChild(li10)
var newItem = document.createElement('LI')
var textNode = document.createTextNode('<v2ex.com />')
newItem.appendChild(textNode)
// index for css nth-of-type is 1-based
var li501 = document.querySelector('#list > li:nth-of-type(501)')
list.insertBefore(newItem, li501)
list.addEventListener('click', function(e) {
var target = e.target || e.srcElement
if (target.id === 'list') {
alert('你点到最外层的ul上了,叫我怎么判断?')
return
}
while (target.nodeName !== 'LI') {
target = target.parentNode
}
var parentUl = target.parentNode
var children = parentUl.childNodes
var count = 0
for (var i = 0, len = children.length; i < len; i++) {
var node = children[i]
if (node.nodeName === 'LI') {
count++
}
if (node === target) {
alert('是当前第' + count + '项')
break
}
}
}, false)
// PS: if querySelector method is not available, the following can be changed.
var li10 = document.querySelector('#list > li:nth-of-type(10)')
var li501 = document.querySelector('#list > li:nth-of-type(501)')
// As below:
function getLiByIndex(index /* 0-based index */ ) {
var count = -1
for (var i = 0, len = list.childNodes.length; i < len; i++) {
if (list.childNodes[i].nodeName === 'LI') {
count++
if (count === index) {
return list.childNodes[i]
}
}
}
}
var li10 = getLiByIndex(9)
var li501 = getLiByIndex(500)
// 还原题目真实DOM结构
var list = document.getElementById('list')
void function() {
var html = ''
for (var i = 0; i <= 10000; i++) {
if (i === 1) {
html += '<li><span>#1</span></li>'
} else if (i === 4) {
html += '<li><ul><li>#4</li></ul></li>'
} else if (i === 9998) {
html += '<li><a href="//v2ex.com">#9998</a></li>'
} else {
html += '<li>#' + i + '</li>'
}
}
list.innerHTML = html
}()
// or, list.className += ' bar'
list.classList.add('bar')
var li10 = document.querySelector('#list > li:nth-of-type(10)')
li10.parentNode.removeChild(li10)
var newItem = document.createElement('LI')
var textNode = document.createTextNode('<v2ex.com />')
newItem.appendChild(textNode)
// index for css nth-of-type is 1-based
var li501 = document.querySelector('#list > li:nth-of-type(501)')
list.insertBefore(newItem, li501)
list.addEventListener('click', function(e) {
var target = e.target || e.srcElement
if (target.id === 'list') {
alert('你点到最外层的ul上了,叫我怎么判断?')
return
}
while (target.nodeName !== 'LI') {
target = target.parentNode
}
var parentUl = target.parentNode
var children = parentUl.childNodes
var count = 0
for (var i = 0, len = children.length; i < len; i++) {
var node = children[i]
if (node.nodeName === 'LI') {
count++
}
if (node === target) {
alert('是当前第' + count + '项')
break
}
}
}, false)
// PS: if querySelector method is not available, the following can be changed.
var li10 = document.querySelector('#list > li:nth-of-type(10)')
var li501 = document.querySelector('#list > li:nth-of-type(501)')
// As below:
function getLiByIndex(index /* 0-based index */ ) {
var count = -1
for (var i = 0, len = list.childNodes.length; i < len; i++) {
if (list.childNodes[i].nodeName === 'LI') {
count++
if (count === index) {
return list.childNodes[i]
}
}
}
}
var li10 = getLiByIndex(9)
var li501 = getLiByIndex(500)
事件代理/委托,是靠事件的冒泡机制实现的(所以,对于一些不具有冒泡特性的事件,比如focus、blur,就没有事件代理/委托这种说法了)。
优点有:
缺点有:
// 只考虑IE 9&+
function delegate(element, targetSelector, type, handler) {
element.addEventListener(type, function(e) {
var targets = Array.prototype.slice.call(element.querySelectorAll(targetSelector))
var target = e.target
if (targets.indexOf(target) !== -1) {
return handler.apply(target, arguments)
}
})
}
// 兼容写法
function delegate(element, targetClass, type, handler) {
addEvent(element, type, function(e) {
e = e || window.event
var target = e.target || e.srcElement
if (target.className.indexOf(targetClass) !== -1) {
handler.apply(target, arguments)
}
})
}
function addEvent(target, type, listener) {
if (target.addEventListener) {
// non-IE, IE9&+
target.addEventListener(type, listener, false)
} else if (target.attachEvent) {
// IE6 - IE10, not available in IE11
target.attachEvent('on' + type, listener)
} else {
// all browsers
target['on' + type] = listener
}
}
// 只考虑IE 9&+
function delegate(element, targetSelector, type, handler) {
element.addEventListener(type, function(e) {
var targets = Array.prototype.slice.call(element.querySelectorAll(targetSelector))
var target = e.target
if (targets.indexOf(target) !== -1) {
return handler.apply(target, arguments)
}
})
}
// 兼容写法
function delegate(element, targetClass, type, handler) {
addEvent(element, type, function(e) {
e = e || window.event
var target = e.target || e.srcElement
if (target.className.indexOf(targetClass) !== -1) {
handler.apply(target, arguments)
}
})
}
function addEvent(target, type, listener) {
if (target.addEventListener) {
// non-IE, IE9&+
target.addEventListener(type, listener, false)
} else if (target.attachEvent) {
// IE6 - IE10, not available in IE11
target.attachEvent('on' + type, listener)
} else {
// all browsers
target['on' + type] = listener
}
}
说明:上面的实现方案中addEvent方法的最后一种实现方式, 即target['on' + type]
的方式会将之前绑定的事件覆盖掉,是有点问题的。 但是考虑到兼容性,一般来说代码是走不到这个地方的,所以也没有问题。
The stopPropagation() method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases. It does not, however, prevent any default behaviors from occurring; for instance, clicks on links are still processed. If you want to stop those behaviors, see the preventDefault() method.
e = e || window.event
if (e.stopPropagation) {
e.stopPropagation()
} else {
// IE 8&-
e.cancelBubble = true
}
e = e || window.event
if (e.stopPropagation) {
e.stopPropagation()
} else {
// IE 8&-
e.cancelBubble = true
}
e = e || window.event
if (e.preventDefault) {
// none-IE, IE 9&+
e.preventDefault()
} else {
// IE 5-8
e.returnValue = false
}
e = e || window.event
if (e.preventDefault) {
// none-IE, IE 9&+
e.preventDefault()
} else {
// IE 5-8
e.returnValue = false
}
The stopImmediatePropagation() method of the Event interface prevents other listeners of the same event from being called.
If several listeners are attached to the same element for the same event type, they are called in the order in which they were added. If stopImmediatePropagation() is invoked during one such call, no remaining listeners will be called.
原生JS中return false只会阻止默认行为, 而用jQuery的话会同时阻止事件传播和阻止事件的默认行为:
$('a').click(function() {
// 同时阻止默认行为和事件传播
return false
})
document.getElementById('link').onclick = function(e) {
// 阻止默认行为
return false
}
$('a').click(function() {
// 同时阻止默认行为和事件传播
return false
})
document.getElementById('link').onclick = function(e) {
// 阻止默认行为
return false
}
下面这段是在jQuery 3.6.0中找到的代码,供参考:
ret = ((jQuery.event.special[handleObj.origType] || {}).handle ||
handleObj.handler).apply(matched.elem, args);
if (ret !== undefined) {
if ((event.result = ret) === false) {
event.preventDefault();
event.stopPropagation();
}
}
ret = ((jQuery.event.special[handleObj.origType] || {}).handle ||
handleObj.handler).apply(matched.elem, args);
if (ret !== undefined) {
if ((event.result = ret) === false) {
event.preventDefault();
event.stopPropagation();
}
}
The target property of the Event interface is a reference to the object onto which the event was dispatched. It is different from Event.currentTarget when the event handler is called during the bubbling or capturing phase of the event.
The currentTarget read-only property of the Event interface identifies the current target for the event, as the event traverses the DOM. It always refers to the element to which the event handler has been attached, as opposed to Event.target, which identifies the element on which the event occurred and which may be its descendant.
获取事件对象和目标对象:
function (e) {
e = e ? e : window.event
var target = e.target || e.srcElement
// do some things here
}
function (e) {
e = e ? e : window.event
var target = e.target || e.srcElement
// do some things here
}
Initially implemented in Internet Explorer, Event.srcElement is a now-standard alias (defined in the DOM Standard but flagged as "historical") for the Event.target property. It's supported in all major browser engines, but only for compatibility reasons. Use Event.target instead.
This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes.
Avoid using it, and update existing code if possible. Be aware that this feature may cease to work at any time.