代码改变世界

一道原生js题目引发的思考(鼠标停留区块计时)

2014-11-04 18:32  大额_skylar  阅读(1525)  评论(2编辑  收藏  举报

我瞎逛个啥论坛,发现了一个题目,于是本着练手的心态就开始写起来了,于是各种问题接踵而至,收获不小。

题目是这样的:

Demo: mouseenter与mouseover区别demo

           跨浏览器的区块计数demo

DownLoad:https://github.com/zhangmengxue/Practice/timeCounter.html

         

刚看上去,没什么特别,心里想了,我就用mouseover和mouseout事件,然后绑个定时器不就行了嘛~.......于是还没开始写呢,就被问到了,那mouseover和mouseenter这两个事件有什么区别的?为什么不用mouseenter呢?

然后我就仔细想了下mouseover和mouseenter之间的区别,下面是书上列出的定义:

 

->mouseenter:在鼠标光标从元素外部首次移动到元素范围之内时触发,这个事件不冒泡。而且在光标移到它后代元素上时不会触发;

->mouseleave:在位于元素上方的鼠标光标移动到这个元素范围之外时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发;

->mouseover:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发。

->mouseout:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的一个元素可能是位于前一个元素的外部,也可能是这个元素的子元素。

 

尝试给下面这段代码分别绑定mouseenter和mouseover事件:

12     <ul id="test">
13       <li>item 1</li>
14       <li>item 2</li>
15       <li>item 3</li>
16     </ul> 

猛戳这里看demo:mouseenter与mouseover区别demo

根据例子可以看出来,虽然事件都是绑定到父元素ul上了,但是mouseover的事件对象其实却是子元素li,而mouseenter的事件对象才是真正的ul元素。

也就是说我的demo因为简单,所以这两个事件的区别在我的demo差别不明显,但是如果我的区块中有子元素的话,那么就会有问题了。

恩,于是遂改mouseenter和mouseleave,多么痛的领悟。

第一个版本的代码:

 

56     <script type="text/javascript">
57          var counter = 0,
58              counter1 = 0,
59              counter2 = 0,
60              counter3 = 0;
61          var timer = null;
62          function timerCounter(div,counter,num){
63              div.addEventListener('mouseenter',function(){
64                 timer = setInterval(count,1000);             
65              },false); 
66             div.addEventListener('mouseleave',function(){
67              clearInterval(timer);
68              //这里设置null是很有必要的,为了重复计数
69              timer = null;
70              },false);
71             var span = document.createElement('span');
72             div.appendChild(span);
73             function count(){
74                 counter++;
75                 span.innerHTML = 'STAY:'+counter;
76                 console.log('区块'+num+'上停留的时间为:'+counter);
77             } 
78             return counter;
79          }
80           var div = document.getElementsByTagName('div');
81           timerCounter(div[0],counter1,div[0].dataset['spmid']);
82           timerCounter(div[1],counter2,div[1].dataset['spmid']);
83           timerCounter(div[2],counter3,div[2].dataset['spmid']);        
84     </script>

 

看它的运行结果,猛戳这里:区块停留计数器第一个版本

一直在chrome下测试,也没想别的,也没发现什么问题,然后想到好像用Date对象也可以实现,于是没费什么事儿,在我的chrome里,他也可以计数了,唯一不足的就是他要鼠标离开后才显示秒数,不过我没计较,因为潜意识里面意识到了我的代码很乱,但还是先把它实现放上来看看:

 

56     <script type="text/javascript">
57          var startTime = 0,
58              totalTime = 0,
59              stayTime = 0,
60              counter1 = 0,
61              counter2 = 0,
62              counter3 = 0;
63          var timer = null;
64          function timerCounter(div,startTime,totalTime,num){
65              var span = document.createElement('span');
66              div.addEventListener('mouseenter',function(){
//换成用Date对象
67 startTime = new Date().getTime(); 68 },false); 69 div.addEventListener('mouseleave',function(){ 70 totalTime += new Date().getTime()-startTime; 71 //console.log(totalTime); 72 span.innerHTML = '鼠标离开后才会显示停留时长,stay:'+Math.floor(totalTime/1000); 73 },false); 74 div.appendChild(span); 75 } 76 var div = document.getElementsByClassName('mod-spm'); 77 timerCounter(div[0],startTime,totalTime,div[0].dataset['spmid']); 78 timerCounter(div[1],startTime,totalTime,div[1].dataset['spmid']); 79 timerCounter(div[2],startTime,totalTime,div[2].dataset['spmid']); 80 </script>

 

看它的运行结果,猛戳:区块停留计数器第二个版本

哎,貌似也可以接受,但是当我看到别人的代码的时候,心里可就不这样想了。我自己写完之后去百度了下看看还有没有别的方法,看别人有没有更好的实现,于是我看到了下面这段代码:

 

1     function ShowStayTime(obj) {
2         this.obj = obj;
3         this.totalTime = 0;
4         this.enterTime = null;
5         this.divTime= document.createElement('div');
6     }
7     ShowStayTime.prototype = {
8         constructor:ShowStayTime,
9         init: function() {
10             this.showStayTime();
11             this.obj.appendChild(this.divTime);
12             this.beginTime();
13             this.leaveTime();
14         },
15         showStayTime: function() {
16             var message = "";
17             message = "停留时间" + this.totalTime + "ms";
18             this.divTime.innerText = message;
19         },
20         beginTime: function() {
21             this.obj.addEventListener("mouseenter",function() {
22                 this.enterTime = new Date();
23             })
24         },
25         leaveTime: function() {
26             var temp = this;
27             this.obj.addEventListener("mouseleave",function() {
28                 temp.totalTime += new Date().getTime() - this.enterTime.getTime();
29                 temp.showStayTime();
30             })
31         }
32     }
33     var divs = document.getElementsByClassName('mod-spm');
34         var show1 = new ShowStayTime(divs[0]);
35         var show2 = new ShowStayTime(divs[1]);
36         var show3 = new ShowStayTime(divs[2]);
37         show1.init();
38         show2.init();
39         show3.init();

 

用的也是Date方法,可是人家写的代码比我的 有思想多了。值得我学习呀~~于是我也更意识到了,真的还有很长的路要走,基础不能忘,设计模式这种高端的东西也要搞起来,代码也不能总那么屎下去吧,小胡子跟我说,厉害的人写代码的境界,能用原生的js实现任何他能想到的结果...

于是我又开始看我的代码,写的封装啊什么的很好的那种,我还没有那个能力,那我的代码,我还能做些什么呢?然后我就开始打开浏览器各种试,到IE的时候,我就傻了,它不起作用了~~这会想不改进也不行了,那就做些力所能及的吧~~~

 

那我第一个版本的实现,问题有哪些呢?实际上:

 

1. 细心的人应该早就注意到了,IE并不支持getElementsByClassName()这个函数;

2. 事件绑定没有注意跨浏览器的问题;

3. data-*的兼容性问题!!

 

这有个data-*的兼容性情况:

  • Internet Explorer 11+
  • Chrome 8+
  • Firefox 6.0+
  • Opera 11.10+
  • Safari 6+

对于第一个问题:本来的做法就可以是,先使用getElementsByTagName("*")取出文档中所有元素,然后进行遍历,使用正则表达式找出匹配的元素放入一个数组返回。

可是,看了正美大神的下面这篇文章: document.getElementsByClassName()的理想实现  我觉得就想用这个方法了。而且心里无比崇拜。

第二个问题:事件绑定的跨浏览器问题,IE低版本根本不支持getElementsByClassName这个方法,哎,不能总自己小打小闹呀,养成好的习惯吧,快快改进。然后又看到正美大神的这篇文章:javascript处理时间的一些兼容写法 总结的很全面的。

第三个问题:由于data-*也存在兼容性问题,所以对于IE的低版本只能用getAttribute()方法获取。

 

然后改进改进改进:

 1 <script type="text/javascript">
 2          var counter = 0,
 3              counter1 = 0,
 4              counter2 = 0,
 5              counter3 = 0;
 6          var timer = null;
 7          function timerCounter(div,counter,num){
 8              addEvent(div,'mouseenter',function(){
 9                 timer = setInterval(count,1000);             
10              }); 
11              addEvent(div,'mouseleave',function(){
12              clearInterval(timer);
13              //这里设置null是很有必要的,为了重复计数
14              timer = null;
15              });
16             var span = document.createElement('span');
17             div.appendChild(span);
18             function count(){
19                 counter++;
20                 span.innerHTML = 'STAY:'+counter;
21                 console.log('区块'+num+'上停留的时间为:'+counter);
22             } 
23             return counter;
24          }
25 
26          //跨浏览器的事件处理函数
27          var addEvent = function( obj, type, fn ) {
28                 if (obj.addEventListener)
29                     obj.addEventListener( type, fn, false );
30                 else if (obj.attachEvent) {
31                     obj["e"+type+fn] = fn;
32                     obj.attachEvent( "on"+type, function() {
33                         obj["e"+type+fn]();
34                     } );
35                 }
36             };
37            
38             //解决getElementsByClassName在低版本ie中不支持的问题
39             var getElementsByClassName = function(searchClass,node,tag) {
40                     if(document.getElementsByClassName){
41                         return  document.getElementsByClassName(searchClass)
42                     }else{    
43                         node = node || document;
44                         tag = tag || '*';
45                         var returnElements = []
46                         var els =  (tag === "*" && node.all)? node.all : node.getElementsByTagName(tag);
47                         var i = els.length;
48                         searchClass = searchClass.replace(/\-/g, "\\-");
49                         var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
50                         while(--i >= 0){
51                             if (pattern.test(els[i].className) ) {
52                                 returnElements.push(els[i]);
53                             }
54                         }
55                         return returnElements;
56                     }
57                 }
58 
59           var div = getElementsByClassName('mod-spm');
60           var block1 = num(div[0]);
61           var block2 = num(div[1]);
62           var block3 = num(div[2]);
63           timerCounter(div[0],counter1,block1);
64           timerCounter(div[1],counter2,block2);
65           timerCounter(div[2],counter3,block3); 
66           //解决data-*的兼容性问题
67           function num(div){
68              if(div.dataset){
69                 return div.dataset['spmid'];
70              }else{
71                 return div.getAttribute('data-spmid');
72              }
73            }   
74 
75     </script>
View Code

 

这会换浏览器也不会出问题啦,各个浏览器下猛戳:跨浏览器的区块停留计时

总结:

1.知识点要分析的准确,比如说像mouseover与mouseenter这样的,这样写代码的时候才不会出现各种各样费解的问题;

2.既然意识到了自己的代码写的不太好有些乱,那么就后面努力在这方面下工夫,努力让它看起来“不错”些;

3.有机会听取别人的意见改进自己是件非常好的事情,欧耶~