书写原生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)
JS中事件流的三个阶段:捕获(低版本IE不支持)==>目标==>冒泡。
如果不同层的元素使用useCapture不同,会先从最外层元素往目标元素寻找设定为capture模式的事件,到达目标元素后执行目标元素的事件后,在循原路往外寻找设定为bubbling模式的事件。
语法如下:
element.addEventListener(type, listener, useCapture)
element.addEventListener(type, listener, options)
element.addEventListener(type, listener, useCapture)
element.addEventListener(type, listener, options)
addEventListener
addEventListener() 的工作原理是将实现 EventListener 的函数或对象添加到调用它的 EventTarget 上的指定事件类型的事件侦听器列表中。如果要绑定的函数或对象已经被添加到列表中,该函数或对象不会被再次添加。
addEventListener允许对同一个target同时绑定多个事件,且可以控制是在冒泡阶段还是捕获阶段触发。onclick、onmouseover这种方式只能绑定一个事件监听回调(最后绑定的生效),且只能在冒泡阶段触发。
removeEventListener
removeEventListener的入参和addEventListener一样。
警告:如果同一个事件监听器分别为“事件捕获(capture 为 true)”和“事件冒泡(capture 为 false)”注册了一次,这两个版本的监听器需要分别移除。移除捕获监听器不会影响非捕获版本的相同监听器,反之亦然。
passive
改善滚屏性能 将 passive
设为 true
可以启用性能优化,并可大幅改善应用性能,正如下面这个例子:
/* 检测浏览器是否支持该特性 */
let passiveIfSupported = false;
try {
window.addEventListener(
"test",
null,
Object.defineProperty({}, "passive", {
get() {
passiveIfSupported = { passive: true };
},
}),
);
} catch (err) {}
window.addEventListener(
"scroll",
(event) => {
/* do something */
// 不能使用 event.preventDefault();
},
passiveIfSupported,
);
/* 检测浏览器是否支持该特性 */
let passiveIfSupported = false;
try {
window.addEventListener(
"test",
null,
Object.defineProperty({}, "passive", {
get() {
passiveIfSupported = { passive: true };
},
}),
);
} catch (err) {}
window.addEventListener(
"scroll",
(event) => {
/* do something */
// 不能使用 event.preventDefault();
},
passiveIfSupported,
);
根据规范,addEventListener()
的 passive
默认值始终为 false
。然而,这会导致触摸事件和滚轮事件(如)的事件监听器在浏览器尝试滚动页面时可能会阻塞浏览器主线程——这可能会大大降低浏览器处理页面滚动时的性能。
事件代理/委托,是靠事件的冒泡机制实现的(所以,对于一些不具有冒泡特性的事件,比如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]
的方式会将之前绑定的同名事件的回调逻辑覆盖掉,是有点问题的。但是考虑到兼容性,一般来说代码是走不到这个地方的,如果要处理的话这里可以对 target
的 'on' + type
属性进行 set 和 get 拦截,再弄个队列。
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
}
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
}
stopImmediatePropagation方法可阻止相同事件上绑定的其他监听器函数被触发。
触发顺序
如果同类型事件的几个监听器函数被绑定到了同一个对象上,它们会按照添加的顺序被触发。
stopPropagation和stopImmediatePropagation的区别
触发事件的对象,也就是用户实际操作(比如点击)的对象。
获取事件对象和目标对象:
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
}
绑定事件的对象。对应的就是element.addEventListener(eventName, handler, options)里的element。
<html>
<body>
<div id="a">
<div id="b">
<div id="c">
<div id="d">最里层</div>
</div>
</div>
</div>
<script>
const a = document.querySelector('#a')
const b = document.querySelector('#b')
const c = document.querySelector('#c')
const d = document.querySelector('#d')
const getHandler = (elem, useCapture) => {
return (e) => {
const target = e.target
const currentTarget = e.currentTarget
const payload = {
elemId: elem.id,
useCapture,
targetId: target.id,
currentTargetId: currentTarget.id,
}
console.log(JSON.stringify(payload))
}
}
d.addEventListener('click', () => {
console.log('冒泡 d1')
}, { capture: false })
const elems = [a, b, c, d]
elems.forEach((elem) => {
elem.addEventListener('click', getHandler(elem, false), { capture: false })
elem.addEventListener('click', getHandler(elem, true), { capture: true })
})
d.addEventListener('click', () => {
console.log('捕获 d2')
}, { capture: true })
d.addEventListener('click', () => {
console.log('捕获 d3')
}, { capture: true })
d.addEventListener('click', () => {
console.log('冒泡 d4')
}, { capture: false })
</script>
</body>
</html>
<html>
<body>
<div id="a">
<div id="b">
<div id="c">
<div id="d">最里层</div>
</div>
</div>
</div>
<script>
const a = document.querySelector('#a')
const b = document.querySelector('#b')
const c = document.querySelector('#c')
const d = document.querySelector('#d')
const getHandler = (elem, useCapture) => {
return (e) => {
const target = e.target
const currentTarget = e.currentTarget
const payload = {
elemId: elem.id,
useCapture,
targetId: target.id,
currentTargetId: currentTarget.id,
}
console.log(JSON.stringify(payload))
}
}
d.addEventListener('click', () => {
console.log('冒泡 d1')
}, { capture: false })
const elems = [a, b, c, d]
elems.forEach((elem) => {
elem.addEventListener('click', getHandler(elem, false), { capture: false })
elem.addEventListener('click', getHandler(elem, true), { capture: true })
})
d.addEventListener('click', () => {
console.log('捕获 d2')
}, { capture: true })
d.addEventListener('click', () => {
console.log('捕获 d3')
}, { capture: true })
d.addEventListener('click', () => {
console.log('冒泡 d4')
}, { capture: false })
</script>
</body>
</html>
点击#d元素,控制台打印内容如下:
{"elemId":"a","useCapture":true,"targetId":"d","currentTargetId":"a"}
{"elemId":"b","useCapture":true,"targetId":"d","currentTargetId":"b"}
{"elemId":"c","useCapture":true,"targetId":"d","currentTargetId":"c"}
{"elemId":"d","useCapture":true,"targetId":"d","currentTargetId":"d"}
捕获 d2
捕获 d3
冒泡 d1
{"elemId":"d","useCapture":false,"targetId":"d","currentTargetId":"d"}
冒泡 d4
{"elemId":"c","useCapture":false,"targetId":"d","currentTargetId":"c"}
{"elemId":"b","useCapture":false,"targetId":"d","currentTargetId":"b"}
{"elemId":"a","useCapture":false,"targetId":"d","currentTargetId":"a"}
{"elemId":"a","useCapture":true,"targetId":"d","currentTargetId":"a"}
{"elemId":"b","useCapture":true,"targetId":"d","currentTargetId":"b"}
{"elemId":"c","useCapture":true,"targetId":"d","currentTargetId":"c"}
{"elemId":"d","useCapture":true,"targetId":"d","currentTargetId":"d"}
捕获 d2
捕获 d3
冒泡 d1
{"elemId":"d","useCapture":false,"targetId":"d","currentTargetId":"d"}
冒泡 d4
{"elemId":"c","useCapture":false,"targetId":"d","currentTargetId":"c"}
{"elemId":"b","useCapture":false,"targetId":"d","currentTargetId":"b"}
{"elemId":"a","useCapture":false,"targetId":"d","currentTargetId":"a"}
可以看到: