JS冒泡和闭包案例分析

背景:

今天逛网页发现了百度知道上一个有意思的JS问题,提问者的问题事实上蛮简单的,懂点前端开发技术的应该都能实现。提问者的要求:实现子菜单的弹出,菜单共同拥有三级。每级菜单显示时有500毫秒的延迟。然后提问者贴出了他的问题代码。

对别人贴出来的代码。仅仅要不是特别复杂,我都会看一眼。

毕竟程序猿交流,源码是最好的语言,刚開始看他的代码就有点感觉哪里不正确。

后来细致分析了下。发现确实是蛮有意思的。

假设感觉分析过程比較无聊。能够直接看结论。

以下是他的代码:

<html>
	<head>
		<style type="text/css">
			* {
				margin: 0;
				padding: 0;
			}
			#div1 {
				position: absolute;
			}
			li {
				list-style-type: none;
				text-align: center;
				width: 70px;
				height: 30px;
				line-height: 30px;
			}
			#zg li {
				background: blue;
				float: left;
			}
			#zg li #bj li {
				background: green;
			}
			#zg li #bj li #xc li {
				background: red;
			}
			#xc,
			#sjz {
				position: relative;
				left: 70px;
				top: -30px;
			}
			#dc {
				position: relative;
				left: 70px;
				top: -60px;
			}
			#bj {
				display: none;
			}
			#hb {
				display: none
			}
			#xc {
				display: none;
			}
			#dc {
				display: none;
			}
		</style>

		<script type="text/javascript">
			onload = function() {
				var lis = document.getElementsByTagName("li");
				var t = 0;
				for (var i = 0; i < lis.length; i++) {
					lis[i].timer = null;
					lis[i].onmouseover = function() {
						me = this;
						me.timer = setInterval(function() {
							if (me.children[0]) {
								me.children[0].style.display = "block"
							}
						}, 500)
					}
					lis[i].onmouseout = function() {
						clearInterval(me.timer)
						this.children[0] ? this.children[0].style.display = "none" : 0;
					}
				}
			}
		</script>

	</head>

	<body>
		<div id="div1">
			<ul id="zg">
				<li>北京
					<ul id="bj">
						<li>西城区
							<ul id="xc">
								<li>西单</li>
								<li>西单</li>
								<li>西单</li>
							</ul>
						</li>
						<li>东城区
							<ul id="dc">
								<li>东单</li>
								<li>东单</li>
								<li>东单</li>
							</ul>
						</li>
						<li>崇文区</li>
					</ul>
				</li>
				<li>河北
					<ul id="hb">
						<li>石家庄
							<ul id="sjz">
								<li>桥东</li>
								<li>桥东</li>
								<li>桥东</li>
							</ul>
						</li>
						<li>保定</li>
						<li>邢台</li>
					</ul>
				</li>
			</ul>
		</div>
	</body>

</html>

上面的代码,我们拿出JS部分来研究下:

onload = function() {
				var lis = document.getElementsByTagName("li");
				var t = 0;
				for (var i = 0; i < lis.length; i++) {
					lis[i].timer = null;
					lis[i].onmouseover = function() {
						me = this;
						me.timer = setInterval(function() {
							if (me.children[0]) {
								me.children[0].style.display = "block"
							}
						}, 500)
					}
					lis[i].onmouseout = function() {
						clearInterval(me.timer)
						this.children[0] ?

this.children[0].style.display = "none" : 0;
					}
				}
			}

首先抛开最主要的错误,比方应该用setTimeout设置延迟。

问题:

我想非常多人应该都和我有相同的疑问:鼠标移到子菜单后一级菜单应该运行onmouseout方法啊。然后子菜单应该被隐藏才对。

但实际上,假设鼠标移的快的话子菜单有“闪烁”现象可是终于是能够显示的,仅仅是三级菜单显示不出来。

疑问:

难道是onmouseout方法没有运行吗?带着这个疑问。我在onmouseout和onmouseover方法上加上了console.log。并断点调试了下。

结果发现一个有意义的现象。

onmouseout方法确实运行了,也就是子菜单是的style属性变为了none。可是控制台显示。整改方法的运行顺序:父菜单onmouseout  -> 子菜单 onmouseover–>
父菜单onmouseover –> 子节点 onmouseout –> 父节点 onmouseout

原因:

出现上面的问题主要原因有三点:

(1)JS的运行是须要耗时的,尤其是设置style的display是要引发render Tree的重构的,所以界面不会里面发现变化。

(2)提问者的onmouseover函数是有益设置了一个500毫秒的延迟来运行显示函数的。

(3)浏览器的事件模型是冒泡运行的。

分析:

当然上面的原因,翻译成人话就是:

当鼠标移到子菜单的时候,浏览器去运行父菜单的onmouseout,这个时候理论上子菜单是须要隐藏的,可是由于第一点原因,子菜单没有隐藏,我们的鼠标移动到了二级菜单上。这个时候浏览器须要触发二级菜单上的onmouseover方法,然后运行子菜单的onmouseover方法。并设置了延迟函数,由于我们并没有阻止事件冒泡,二级菜单的onmouseover冒泡到一级菜单上,运行父菜单的onmouseover。

分析到上面,我们应该注意按理说二级菜单的onmouseover中应该把它子菜单(三级菜单)显示出来了。可是我们要注意onmouseover的逻辑并非正常的逻辑,而是一种延迟。所以二级菜单的show函数(也就是那个匿名函数)运行是发生在一级菜单的onmouseover函数后的。所以me这个闭包变量被改变了。

通过调试,能够发现me这个变量是挂载在windows下的。那么它就是一个全局变量了,子菜单的onmouseover把它设置为子节点本身,可是接着冒泡到父节点。onmouseover的this变为了父节点本身,所以终于show函数被运行两次。两次的me变量都是父菜单相应的节点。

上面这个问题有点类似高级程序设计语言(如Java)中进行多线程编程的资源竞争了,在高级程序语言中,为了同步一个资源(变量、对象)。语言本身会提供类似synchronized的keyword。当然我们这里并非多线程,没法办法同步这个变量(后面再说解决的方法)。

接着分析,后面的两个onmouseout。事实上更好理解了,当程序运行到这里的时候。浏览器最终完毕了render Tree的重构。子菜单就隐藏了,然后浏览器就觉得我们的鼠标移出了子菜单,所以開始出发子菜单的onmouseout事件,而且是冒泡运行的,详细过程不做分析了。

程序运行完两次onmouseout后。肯定不会花费500ms,除非你的机器特别老,所以这个时候,我们设置的两个延迟函数会被触发。然后运行me的子节点的显示,我们上面提到了两次show函数的me实际上是同一个变量。都指向了父菜单相应的节点。所以子节点就被显示了。

到了这里。我们的问题又来了。onmouseout里面调用了clearTimeout,那么两个延迟函数应该被取消啊。答案就是,clearTimeout使用方法不正确。

相信非常多人和楼主我一样。并没有把JS当一门完整的技术学过。所以我们知道setTimeout和setInterval。知道用相应的clear方法取消。可是我们从没研究过它的执行机制(当然非常多时候不是必需,就好比Java刚出来的时候,非常少有人关心JVM层次的东西)。这里我们不深究。仅仅给出结论,从W3C上就能够知道。

setTimeout和setInterval都会返回一个字符串(类似Thread
ID),调用clear方法时。须要传入相应的ID,这样才干停止运行延迟函数。

最后一个问题,既然子菜单经过了隐藏显示过程。那么为什么浏览器触发了onmouseout没有再次触发onmouseover。从而形成死循环,让页面死掉呢?至于这个问题,我也没有找到标准答案,或许浏览器对于mouse事件的触发是依照像素触发的吧,我们没有再移动鼠标,自然不会触发onmouseover了。

结论:

废话了一堆。非常多人应该都不耐烦了,说下结论吧。

(1)JS编程,我们一定要注意事件冒泡,冒泡是个好东西,可是要慎重使用。事件冒泡能让我们提高程序效率,比方table操作,非常多时候我们会让事件冒泡到table上再进行操作。这样能有非常高的运行效率。jQuery的事件处理就非常好的利用了冒泡。

(2)慎重使用闭包,JS编程中,闭包非常easy造成内存泄露。慎重使用,并且闭包变量非常easy称为资源竞争对象。尤其是在延迟函数中。非常easy发生莫名其妙的问题。

(3)clearInterval和clearTimeout一定要传入setInterval和setTimeout返回的ID。否则是无法正常取消延迟的。

(4)onmouseenter和onmouseleave能够作为解决上面问题的一种方案。可是兼容性问题要注意!

onmouseenter和onmouseleave不会进行事件冒泡,onmouseover和onmouseout会冒泡到父节点,this指向也会发生变化。

PS:

今天废话了一堆。分析这个案例没什么意思,仅仅是无聊。

“不求甚解”固然是求学哲学,可是“追本溯源”才是求解问题的方法。让人给出正确答案远比找出问题原因要easy。

时间: 2024-10-07 06:48:04

JS冒泡和闭包案例分析的相关文章

js闭包简要分析

相信大多数接触过js编程的程序员或多或少都对js中的闭包了解一些吧,所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.闭包是 ECMAScript (JavaScript)最强大的特性之一,但用好闭包的前提是必须理解闭包.闭包的创建相对容易,人们甚至会在不经意间创建闭包,但这些无意创建的闭包却存在潜在的危害,尤其是在比较常见的浏览器环境下.如果想要扬长避短地使用闭包这一特性,则必须了解它们的工作机制.而闭包工作机制的实现很大程

JS高级---闭包案例,点赞

闭包案例,点赞 //获取所有的按钮 //根据标签名字获取元素 function my$(tagName) { return document.getElementsByTagName(tagName); } //闭包缓存数据 function getValue() { var value = 2; return function () { //每一次点击的时候,都应该改变当前点击按钮的value值 this.value = "赞(" + (value++) + ")"

详解js中的闭包

前言 在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以<javascript高级程序设计>里面的理论为基础.用拆分的方式,深入讲解一下对于闭包的理解,如果有不对请指正. 写在闭包之前 闭包的内部细节,依赖于函数被调用过程所发生的一系列事件为基础,所以有必要先弄清楚以下几个概念: 1. 执行环境和活动对象 ** - 执行环境(execution context)定义了变量或者函数有权访问的其他数据,每个执行环境都有一个与之关联的

《大型网站技术架构:核心原理与案例分析》笔记

目录 · 大型网站软件系统的特点 · 大型网站架构演化发展历程 · 初始阶段的网站架构 · 需求/解决问题 · 架构 · 应用服务和数据服务分离 · 需求/解决问题 · 架构 · 使用缓存改善网站性能 · 需求/解决问题 · 架构 · 使用应用服务器集群改善网站的并发处理能力 · 需求/解决问题 · 架构 · 数据库读写分离 · 需求/解决问题 · 架构 · 使用反向代理和CDN加速网站响应 · 需求/解决问题 · 架构 · 使用分布式文件系统和分布式数据库系统 · 需求/解决问题 · 架构 ·

《大型网站技术架构:核心原理与案例分析》笔记03

<大型网站技术架构:核心原理与案例分析>笔记01 1.大型网站核心架构要素. 架构:"最高层次的规划,难以改变的决定."从这个角度而言,人生规划也是一种架构.选什么学校.学什么专业.进什么公司.找什么对象,过什么样的生活,都是自己人生的架构. 软件架构:"有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计." 五大要素:性能.可用性.伸缩性.扩展性和安全性. 2.性能测试指标: 响应时间:指应用执行一个操作需要的时间,包括从发出请求开始

CSS3-3D制作案例分析实战

一.前言 上一节,介绍了基础的CSS3 3D动画原理实现,也举了一个小小的例子来演示,但是有朋友跟我私信说想看看一些关于CSS3 3D的实例,所以在这里为了满足一下大家的需求,同时也为了以后能够更好的巩固CSS3 3D的知识,所以在这里写下这篇博文,希望能够帮助你更好的理解3D的制作和实现原理,同时也欢迎各位小伙伴对文中的错误给予指正 二.入门案例分析 这里先说一说我的规划,我打算先从入门级的案例入手,然后依次递推,最后要达到的效果是,理解完所有的例子的设计思路,基本上CSS3-3D制作就能够随

从闭包案例中学习闭包的作用,会不会由你。

在文章初识js中的闭包中讲解了闭包的一些概念,但是对于初学者来说可能并不是特别的容易理解,我今天用两个案例来解释闭包可能会好理解一些,在讲案例之前,我们需要了解一些闭包的概念.在看这篇文章之前,请先看上面的那篇文章,不然效果不会太好. 闭包的理解: 所谓的闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰. 闭包的创建: 一个函数中嵌套另外一个函数,并且将这个函数return出去,然后将这个return出来的函数保存到了一个变量中,那么就创建了一个闭包. 为啥要学闭包之没有使

java代码实现highchart与数据库数据结合完整案例分析(二)---折线图

作者原创:未经博主允许不许转载 在上一篇的博客中,展示和分析了如何做一个饼状图,有疑问可以参考上一篇博客. 现在分析和展示折线图的绘制和案例分析, 先展示效果图: 与饼状图不同的是,折线图展现更多的数据,也会体现出更多的自动性. 先展示一下js代码: <!-- 播放类型统计折线图 --> <script type="text/javascript"> $(function () { var livFlowList=document.getElementById(

通俗易懂地解释JS中的闭包

1. "闭包就是跨作用域访问变量." [示例一] ? 1 2 3 4 5 6 7 8 9 var name = 'wangxi' function user () {  // var name = 'wangxi'  function getName () {  console.log(name)  }  getName() } user() // wangxi 在 getName 函数中获取 name,首先在 getName 函数的作用域中查找 name,未找到,进而在 user 函