JavaScript

Javascript基础知识

一、javascript的数组API

1. //定义数组   

2.  var pageIds = new Array();   

3.  pageIds.push('A');   

4.   数组长度   

5.    pageIds.length;   

6. //shift:删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined    

7. var a = [1,2,3,4,5];    

8. var b = a.shift(); //a:[2,3,4,5]   b:1    

9. //unshift:将参数添加到原数组开头,并返回数组的长度    

10. var a = [1,2,3,4,5];    

11. var b = a.unshift(-2,-1); //a:[-2,-1,1,2,3,4,5]   b:7    

12. //注:在IE6.0下测试返回值总为undefined,FF2.0下测试返回值为7,所以这个方法的返回值不可靠,需要用返回值时可用splice代替本方法来使用。    

13. //pop:删除原数组最后一项,并返回删除元素的值;如果数组为空则返回undefined    

14. var a = [1,2,3,4,5];    

15. var b = a.pop(); //a:[1,2,3,4]   b:5    

16. //push:将参数添加到原数组末尾,并返回数组的长度    

17. var a = [1,2,3,4,5];    

18. var b = a.push(6,7); //a:[1,2,3,4,5,6,7]   b:7    

19. //concat:返回一个新数组,是将参数添加到原数组中构成的    

20. var a = [1,2,3,4,5];    

21. var b = a.concat(6,7); //a:[1,2,3,4,5]   b:[1,2,3,4,5,6,7]    

22. //splice(start,deleteCount,val1,val2,):从start位置开始删除deleteCount项,并从该位置起插入val1,val2,    

23. var a = [1,2,3,4,5];    

24. var b = a.splice(2,2,7,8,9); //a:[1,2,7,8,9,5]   b:[3,4]    

25. var b = a.splice(0,1); //同shift    

26. a.splice(0,0,-2,-1); var b = a.length; //同unshift    

27. var b = a.splice(a.length-1,1); //同pop    

28. a.splice(a.length,0,6,7); var b = a.length; //同push    

29. //reverse:将数组反序    

30. var a = [1,2,3,4,5];    

31. var b = a.reverse(); //a:[5,4,3,2,1]   b:[5,4,3,2,1]    

32. //sort(orderfunction):按指定的参数对数组进行排序    

33. var a = [1,2,3,4,5];    

34. var b = a.sort(); //a:[1,2,3,4,5]   b:[1,2,3,4,5]    

35. //slice(start,end):返回从原数组中指定开始下标到结束下标之间的项组成的新数组    

36. var a = [1,2,3,4,5];    

37. var b = a.slice(2,5); //a:[1,2,3,4,5]   b:[3,4,5]    

38. //join(separator):将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符    

39. var a = [1,2,3,4,5];    

40. var b = a.join("|"); //a:[1,2,3,4,5]   b:"1|2|3|4|5"   

二、dom最常用API

1. document方法:    

2. getElementById(id) Node 返回指定结点的引用    

3. getElementsByTagName(name) NodeList 返回文档中所有匹配的元素的集合    

4. createElement(name) Node Node    

5. createTextNode(text) Node 创建一个纯文本结点    

6. ownerDocument Document 指向这个节点所属的文档    

7. documentElement Node 返回html节点    

8. document.body Node 返回body节点    

9. element方法:    

10. getAttribute(attributeName) String 返回指定属性的值    

11. setAttribute(attributeName,value) String 给属性赋值    

12. removeAttribute(attributeName) String 移除指定属性和它的值    

13. getElementsByTagName(name) NodeList 返回结点内所有匹配的元素的集合    

14. node方法:    

15. appendChild(child) Node 给指定结点添加一个新的子结点    

16. removeChild(child) Node 移除指定结点的子结点    

17. replaceChild(newChild,oldChild) Node 替换指定结点的子结点    

18. insertBefore(newChild,refChild) Node 在同一层级的结点前面插入新结点    

19. hasChildNodes() Boolean 如果结点有子结点则返回true    

20. node属性:    

21. nodeName String 以字符串的格式存放结点的名称    

22. nodeType String 以整型数据格式存放结点的类型    

23. nodeValue String 以可用的格式存放结点的值    

24. parentNode Node 指向结点的父结点的引用    

25. childNodes NodeList 指向子结点的引用的集合    

26. firstChild Node 指向子结点结合中的第一个子结点的引用    

27. lastChild Node 指向子结点结合中的最后一个子结点的引用    

28. previousSibling Node 指向前一个兄弟节点;如果这个节点就是兄弟节点,那么该值为null    

29. nextSibling Node 指向后一个兄弟节点;如果这个节点就是兄弟节点,那么该值为null   

三、网上搜藏的一个map对象

1. function HashMap()      

2.  {      

3.      /** Map 大小 **/     

4.      var size = 0;      

5.      /** 对象 **/     

6.      var entry = new Object();      

7.            

8.      /** 存 **/     

9.      this.put = function (key , value)      

10.      {      

11.          if(!this.containsKey(key))      

12.          {      

13.              size ++ ;      

14.          }      

15.          entry[key] = value;      

16.      }      

17.      /** 取 **/     

18.      this.get = function (key)      

19.      {      

20.          return this.containsKey(key) ? entry[key] : null;      

21.      }      

22.            

23.      /** 删除 **/     

24.      this.remove = function ( key )      

25.      {      

26.          if( this.containsKey(key) && ( delete entry[key] ) )      

27.          {      

28.              size --;      

29.          }      

30.      }      

31.      /** 是否包含 Key **/     

32.      this.containsKey = function ( key )      

33.      {      

34.          return (key in entry);      

35.      }      

36.            

37.      /** 是否包含 Value **/     

38.      this.containsValue = function ( value )      

39.      {      

40.          for(var prop in entry)      

41.          {      

42.              if(entry[prop] == value)      

43.              {      

44.                  return true;      

45.              }      

46.          }      

47.          return false;      

48.      }        

49.      /** 所有 Value **/     

50.      this.values = function ()      

51.      {      

52.          var values = new Array();      

53.          for(var prop in entry)      

54.          {      

55.              values.push(entry[prop]);      

56.          }      

57.          return values;      

58.      }         

59.      /** 所有 Key **/     

60.      this.keys = function ()      

61.      {      

62.          var keys = new Array();      

63.          for(var prop in entry)      

64.          {      

65.              keys.push(prop);      

66.          }      

67.          return keys;      

68.      }         

69.      /** Map Size **/     

70.      this.size = function ()      

71.      {      

72.          return size;      

73.      }      

74.      /* 清空 */     

75.      this.clear = function ()      

76.      {      

77.          size = 0;      

78.          entry = new Object();      

79.      }      

80.  }      

81.  var map = new HashMap();       

82.  /*    

83.  map.put("A","1");    

84.  map.put("B","2");    

85.  map.put("A","5");    

86.  map.put("C","3");    

87.  map.put("A","4");    

88.  */     

89.  /*    

90.  alert(map.containsKey("XX"));    

91.  alert(map.size());    

92.  alert(map.get("A"));    

93.  alert(map.get("XX"));    

94.  map.remove("A");    

95.  alert(map.size());    

96.  alert(map.get("A"));    

97.  */     

98.  /** 同时也可以把对象作为 Key **/     

99.  /*    

100.  var arrayKey = new Array("1","2","3","4");    

101.  var arrayValue = new Array("A","B","C","D");    

102.  map.put(arrayKey,arrayValue);    

103.  var value = map.get(arrayKey);    

104.  for(var i = 0 ; i < value.length ; i++)    

105.  {    

106.      //alert(value[i]);    

107.  }    

108.  */     

109.  /** 把对象做为Key时 ,自动调用了该对象的 toString() 方法 其实最终还是以String对象为Key**/     

110.        

111.  /** 如果是自定义对象 那自己得重写 toString() 方法 否则 . 就是下面的结果 **/     

112.  function MyObject(name)      

113.  {      

114.      this.name = name;      

115.  }      

116.        

117.  /**    

118.  function MyObject(name)    

119.  {    

120.      this.name = name;    

121.          

122.      this.toString = function ()    

123.      {    

124.          return this.name;    

125.      }    

126.  }    

127.  **/     

128.  var object1 = new MyObject("小张");      

129.  var object2 = new MyObject("小名");      

130.        

131.  map.put(object1,"小张");      

132.  map.put(object2,"小名");      

133.  alert(map.get(object1));      

134.  alert(map.get(object2));      

135.  map.remove("xxxxx");      

136.  alert(map.size());      

137.        

138.  /** 运行结果 小名 小名 size = 1 **/     

139.        

140.  /** 如果改成复写toString()方法的对象 , 效果就完全不一样了 **/     

141.        

142.  </script>    

四、常用的数字函数:

1. ·数字型(Number)    

2.   1.声明    

3.     var i = 1;    

4.     var i = new Number(1);      

5.   2.字符串与数字间的转换    

6.     var i = 1;    

7.     var str = i.toString();     //结果: "1"    

8.     var str = new String(i);    //结果: "1"    

9.     i = parseInt(str);          //结果: 1    

10.     i = parseFloat(str);        //结果: 1.0           

11.     //注意: parseInt,parseFloat会把一个类似于"32G"的字符串,强制转换成32    

12.   3.判断是否为有效的数字    

13.     var i = 123;  var str = "string";    

14.     if( typeof i == "number" ){ }   //true          

15.     //某些方法(如:parseInt,parseFloat)会返回一个特殊的值NaN(Not a Number)    

16.     //请注意第2点中的[注意],此方法不完全适合判断一个字符串是否是数字型!!    

17.     i = parseInt(str);    

18.     if( isNaN(i) ){ }          

19.   4.数字型比较    

20.     //此知识与[字符串比较]相同    

21.   5.小数转整数    

22.     var f = 1.5;    

23.     var i = Math.round(f);  //结果:2 (四舍五入)    

24.     var i = Math.ceil(f);   //结果:2 (返回大于f的最小整数)    

25.     var i = Math.floor(f);  //结果:1 (返回小于f的最大整数)    

26.   6.格式化显示数字    

27.     var i = 3.14159;    

28.     //格式化为两位小数的浮点数    

29.     var str = i.toFixed(2);     //结果: "3.14"          

30.     //格式化为五位数字的浮点数(从左到右五位数字,不够补零)    

31.     var str = i.toPrecision(5); //结果: "3.1415"    

32.   7.X进制数字的转换    

33.     var i = parseInt("0x1f",16);    

34.     var i = parseInt(i,10);    

35.     var i = parseInt("11010011",2);    

36.   8.随机数    

37.     //返回0-1之间的任意小数    

38.     var rnd = Math.random();    

39.     //返回0-n之间的任意整数(不包括n)       

40.     var rnd = Math.floor(Math.random() * n)   

五、网上搜藏的js堆栈

1. function stack(){   

2.       if(this.top==undefined){   

3.       //初始化堆栈的顶部指针和数据存放域   

4.             this.top=0;   

5.             this.unit=new Array();   

6.       }   

7.       this.push=function(pushvalue){   

8.       //定义压入堆栈的方法               

9.             this.unit[this.top]=pushvalue;   

10.             this.top+=1;               

11.       }   

12.       this.readAllElements=function(){   

13.       //定义读取所有数据的方法   

14.             if(this.top==0){   

15.                   alert("当前栈空,无法读取数据");   

16.                   return("");   

17.             }   

18.             var count=0;   

19.             var outStr="";                            

20.             for(count=0;count<this.top;count++){   

21.                   outStr+=this.unit[count]+",";   

22.             }               

23.             return(outStr);   

24.       }   

25.       this.pop=function(){   

26.       //定义弹出堆栈的方法   

27.             if(this.top==0){   

28.                   alert("当前栈空,无法弹出数据");   

29.                   return("");   

30.             }   

31.             var      popTo=this.unit[this.top-1];   

32.             this.top--;   

33.             return(popTo);   

34.             /* 从堆栈弹出数据,顶部指针减一,不过这里没有做到资源的释放,也  

35.             就是说数据仍然存在于this.unit的数组中,只不过无法访问罢了。目前  

36.             我也没想到好的办法解决。*/  

37.       }   

38. }  

六、最常用的JavaScript日期函数

1. ·日期型(Date)    

2.   1.声明    

3.     var myDate = new Date();    //系统当前时间     

4.     var myDate = new Date(yyyy, mm, dd, hh, mm, ss);    

5.     var myDate = new Date(yyyy, mm, dd);    

6.     var myDate = new Date("monthName dd, yyyy hh:mm:ss");    

7.     var myDate = new Date("monthName dd, yyyy");    

8.     var myDate = new Date(epochMilliseconds);    

9.   2.获取时间的某部份    

10.     var myDate = new Date();    

11.     myDate.getYear();       //获取当前年份(2位)    

12.     myDate.getFullYear();   //获取完整的年份(4位,1970-????)    

13.     myDate.getMonth();      //获取当前月份(0-11,0代表1月)    

14.     myDate.getDate();       //获取当前日(1-31)    

15.     myDate.getDay();        //获取当前星期X(0-6,0代表星期天)      

16.     myDate.getTime();       //获取当前时间(从1970.1.1开始的毫秒数)   时间戳!!   

17.     myDate.getHours();      //获取当前小时数(0-23)    

18.     myDate.getMinutes();    //获取当前分钟数(0-59)    

19.     myDate.getSeconds();    //获取当前秒数(0-59)    

20.     myDate.getMilliseconds();   //获取当前毫秒数(0-999)    

21.     myDate.toLocaleDateString();    //获取当前日期    

22.     myDate.toLocaleTimeString();    //获取当前时间    

23.     myDate.toLocaleString( );       //获取日期与时间    

24.   3.计算之前或未来的时间    

25.     var myDate = new Date();    

26.     myDate.setDate(myDate.getDate() + 10);  //当前时间加10天    

27.     //类似的方法都基本相同,以set开头,具体参考第2点    

28.   4.计算两个日期的偏移量    

29.     var i = daysBetween(beginDate,endDate); //返回天数    

30.     var i = beginDate.getTimezoneOffset(endDate); //返回分钟数    

31.   5.检查有效日期    

32.     //checkDate() 只允许"mm-dd-yyyy"或"mm/dd/yyyy"两种格式的日期    

33.     if( checkDate("2006-01-01") ){ }    

34.     //正则表达式(自己写的检查 yyyy-mm-dd, yy-mm-dd, yyyy/mm/dd, yy/mm/dd 四种)    

35.     var r = /^(\d{2}|\d{4})[\/-]\d{1,2}[\/-]\d{1,2}$/;    

36.     if( r.test( myString ) ){ }   

七、最常用字符串函数API:

1. ·字符串(String)    

2.   1.声明    

3.     var myString = new String("Every good boy does fine.");    

4.     var myString = "Every good boy does fine.";    

5.   2.字符串连接    

6.     var myString = "Every " + "good boy " + "does fine.";    

7.     var myString = "Every ";  myString += "good boy does fine.";    

8.   3.截取字符串    

9.     //截取第 6 位开始的字符    

10.     var myString = "Every good boy does fine.";    

11.     var section = myString.substring(6);    //结果: "good boy does fine."     

12.     //截取第 0 位开始至第 10 位为止的字符    

13.     var myString = "Every good boy does fine.";    

14.     var section = myString.substring(0,10); //结果: "Every good"    

15.     //截取从第 11 位到倒数第 6 位为止的字符    

16.     var myString = "Every good boy does fine.";    

17.     var section = myString.slice(11,-6);    //结果: "boy does"    

18.     //从第 6 位开始截取长度为 4 的字符    

19.     var myString = "Every good boy does fine.";    

20.     var section = myString.substr(6,4);     //结果: "good"    

21.   4.转换大小写    

22.     var myString = "Hello";    

23.     var lcString = myString.toLowerCase();  //结果: "hello"    

24.     var ucString = myString.toUpperCase();  //结果: "HELLO"    

25.   5.字符串比较    

26.     var aString = "Hello!";    

27.     var bString = new String("Hello!");    

28.     if( aString == "Hello!" ){ }    //结果: true    

29.     if( aString == bString ){ }     //结果: true    

30.     if( aString === bString ){ }    //结果: false (两个对象不同,尽管它们的值相同)    

31.   6.检索字符串    

32.     var myString = "hello everybody.";    

33.     // 如果检索不到会返回-1,检索到的话返回在该串中的起始位置    

34.     if( myString.indexOf("every") > -1 ){ } //结果: true    

35.   7.查找替换字符串    

36.     var myString = "I is your father.";    

37.     var result = myString.replace("is","am");   //结果: "I am your father."    

38.   8.特殊字符:    

39.     \b : 后退符         \t : 水平制表符    

40.     \n : 换行符         \v : 垂直制表符    

41.     \f : 分页符         \r : 回车符    

42.     \" : 双引号         \' : 单引号    

43.     \\ : 反斜杆    

44.   9.将字符转换成Unicode编码    

45.     var myString = "hello";    

46.     var code = myString.charCodeAt(3);  //返回"l"的Unicode编码(整型)    

47.     var char = String.fromCharCode(66); //返回Unicode为66的字符    

48.   10.将字符串转换成URL编码    

49.     var myString = "hello all";    

50.     var code = encodeURI(myString);     //结果: "hello%20all"    

51.     var str = decodeURI(code);          //结果: "hello all"    

52.     //相应的还有:  encodeURIComponent()  decodeURIComponent()   

八、数学函数:

1. ·Math对象    

2.   1. Math.abs(num) : 返回num的绝对值    

3.   2. Math.acos(num) : 返回num的反余弦值    

4.   3. Math.asin(num) : 返回num的反正弦值    

5.   4. Math.atan(num) : 返回num的反正切值    

6.   5. Math.atan2(y,x) : 返回y除以x的商的反正切值    

7.   6. Math.ceil(num) : 返回大于num的最小整数    

8.   7. Math.cos(num) : 返回num的余弦值    

9.   8. Math.exp(x) : 返回以自然数为底,x次幂的数    

10.   9. Math.floor(num) : 返回小于num的最大整数    

11.   10.Math.log(num) : 返回num的自然对数    

12.   11.Math.max(num1,num2) : 返回num1和num2中较大的一个    

13.   12.Math.min(num1,num2) : 返回num1和num2中较小的一个    

14.   13.Math.pow(x,y) : 返回x的y次方的值    

15.   14.Math.random() : 返回0到1之间的一个随机数    

16.   15.Math.round(num) : 返回num四舍五入后的值    

17.   16.Math.sin(num) : 返回num的正弦值    

18.   17.Math.sqrt(num) : 返回num的平方根    

19.   18.Math.tan(num) : 返回num的正切值    

20.   19.Math.E : 自然数(2.718281828459045)    

21.   20.Math.LN2 : 2的自然对数(0.6931471805599453)    

22.   21.Math.LN10 : 10的自然对数(2.302585092994046)    

23.   22.Math.LOG2E : log 2 为底的自然数(1.4426950408889634)    

24.   23.Math.LOG10E : log 10 为底的自然数(0.4342944819032518)    

25.   24.Math.PI : π(3.141592653589793)    

26.   25.Math.SQRT1_2 : 1/2的平方根(0.7071067811865476)    

27.   26.Math.SQRT2 : 2的平方根(1.4142135623730951)   

九、浏览器特征函数:

1. 1.浏览器名称    

2.    //IE : "Microsoft Internet Explorer"    

3.    //NS : "Netscape"    

4.    var browserName = navigator.appName;    

5.  2.浏览器版本    

6.    bar browserVersion = navigator.appVersion;    

7.  3.客户端操作系统    

8.    var isWin = ( navigator.userAgent.indexOf("Win") != -1 );    

9.    var isMac = ( navigator.userAgent.indexOf("Mac") != -1 );    

10.    var isUnix = ( navigator.userAgent.indexOf("X11") != -1 );    

11.  4.判断是否支持某对象,方法,属性    

12.    //当一个对象,方法,属性未定义时会返回undefined或null等,这些特殊值都是false    

13.    if( document.images ){ }    

14.    if( document.getElementById ){ }    

15.  5.检查浏览器当前语言    

16.    if( navigator.userLanguage ){ var l = navigator.userLanguage.toUpperCase(); }    

17.  6.检查浏览器是否支持Cookies    

18.    if( navigator.cookieEnabled ){ }   

十、JavaScript面向对象的方法实现继承:call方法

1. // 动物类 animal   

2. function animal(bSex){   

3.     this.sex = bSex   

4.     this.getSex = function(){   

5.         return this.sex   

6.     }   

7. }   

8. // 类静态变量 (如果你不修改它的话~~)   

9. animal.SEX_G = new Object();    // 雌性   

10. animal.SEX_B = new Object();    // 雄性   

11. // 动物子类 鸟   

12. function bird(bSex){   

13.     animal.call(this, bSex);   

14.     this.fly = function(iSpeed){   

15.         alert("飞行时速高达 " + iSpeed);   

16.     }   

17. }   

18. // 动物子类 鱼   

19. function fish(bSex){   

20.     animal.call(this, bSex);   

21.     this.swim = function(iSpeed){   

22.         alert("游动时速高达 " + iSpeed)   

23.     }   

24. }   

25. // 鱼 鸟 杂交品种。。。   

26. function crossBF(bSex){   

27.     bird.call(this, bSex);   

28.     fish.call(this, bSex);   

29. }   

30. var oPet = new crossBF(animal.SEX_G);    // 雌性 鱼鸟   

31.     alert(oPet.getSex() == animal.SEX_G ? "雌性" : "雄性");   

32.     oPet.fly(124)   

33.     oPet.swim(254)  

十一、用面向对象的编程方式写JavaScript:

1. MyTool = new function(){   

2.         /**  

3.          * 返回非空字符串,如果有默认值就返回默认字符串.  

4.          */  

5.         this.notNull = function(str,defaultStr){   

6.                 if(typeof(str)=="undefined"||str==null||str==''){   

7.                         if(defaultStr)   

8.                                 return defaultStr;   

9.                         else  

10.                                 return '';   

11.                 }   

12.                 else{   

13.                         return str;   

14.                 }   

15.         }   

16. }   

17.   

18. rootId = MyTool.notNull(rootId,'001000');    

十二、常用的js方法,包括表单校验的一些方法,下拉菜单常用的方法等等:

1. /**  

2.  * 对JSON对象转换为字符串.  

3.  * @param {json对象} json  

4.  * @return {json字符串}  

5.  */  

6. function jsonObj2Str(json) {   

7.         var str = "{";   

8.         for (prop in json) {   

9.                 str += prop + ":" + json[prop] + ",";   

10.         }   

11.         str = str.substr(0, str.length - 1);   

12.         str += "}";    

13.         return str;   

14. }   

15. /**  

16.  * 将json字符串转换为json对象.  

17.  * @param {json字符串} jsonstr  

18.  * @return {json对象}  

19.  */  

20. function jsonStr2Obj(jsonstr) {   

21.          return eval("("+jsonstr+")");   

22. }   

/**  

23.  * 得到一个元素的left坐标值.  

24.  * @param {dom对象} obj  

25.  * @return {位置值}  

26.  */  

27. function getLeft(obj){    

28.         var offset=e.offsetLeft;    

29.         if(e.offsetParent!=null) offset+=getLeft(e.offsetParent);    

30.         return offset;    

31. }    

32. /**  

33.  * 得到一个元素的绝对位置的top坐标值.  

34.  * @param {dom对象} obj  

35.  * @return {位置值}  

36.  */  

37. function getTop(obj){    

38.         var offset=e.offsetTop;    

39.         if(e.offsetParent!=null) offset+=getTop(e.offsetParent);    

40.         return offset;    

41. }    

42. /**  

43.  * 删除一个字符串的左右空格.  

44.  * @param {原始字符串} str  

45.  * @return {删除空格之后的字符串}  

46.  */  

47. function  trim(str)   

48. {     

49.    return  str.replace(/(^\s*)|(\s*$)/g,"");     

50. }   

51. /**  

52.  * 根据id取出一个元素.  

53.  * @param {元素id值} str  

54.  * @return {dom对象}  

55.  */  

56. function $(str) {   

57.         return document.getElementById(str);   

58. }   

59. /**  

60.  * 按name获取一个对象.  

61.  * @param {元素name值} str  

62.  * @return {根据name返回的第一个对象}  

63.  */  

64. function $byName(str) {   

65.         var arr = document.getElementsByName(str);   

66.         if (arr)   

67.                 return arr[0];   

68.         else  

69.                 return null;   

70. }   

71. /***************以下方法和表单验证相关*************************************************/  

72. /**  

73.  * 返回非空字符串,如果有默认值就返回默认字符串.  

74.  * @param {要进行转换的原字符串} str  

75.  * @param {默认值} defaultStr  

76.  * @return {返回结果}  

77.  */  

78. function notNull(str, defaultStr) {   

79.         if (typeof(str) == "undefined" || str == null || str == '') {   

80.                 if (defaultStr)   

81.                         return defaultStr;   

82.                 else  

83.                         return '';   

84.         } else {   

85.                 return str;   

86.         }   

87. }     

88. /**  

89.  * 比较两个日期大小.  

90.  * @param {较小日期的文本框id} smallDate  

91.  * @param {较大日期的文本框id} bigDate  

92.  * @param {出错的提示信息} msg  

93.  */  

94. function compareTwoDate(smallDate, bigDate, msg) {   

95.         var v1 = $(smallDate).value;   

96.         var v2 = $(bigDate).value;   

97.         if (v1 >= v2) {   

98.                 alert(msg);   

99.                                 v2.focus();   

100.                 return false;   

101.         }   

102.         return true;   

103. }   

104. /**  

105.  * 比较两个金额大小的方法.  

106.  * @param {较小的金额} smallNum  

107.  * @param {较大的金额} bigNum  

108.  * @param {出错提示信息} msg  

109.  * @return {Boolean}  

110.  */  

111. function compareTwoNum(smallNum, bigNum, msg) {   

112.         var v1 = $(smallNum).value;   

113.         var v2 = $(bigNum).value;    

114.         if (parseFloat(v1) >= parseFloat(v2)) {   

115.                 alert(msg);   

116.                                 v2.focus();   

117.                 return false;   

118.         }   

119.         return true;   

120. }   

121. /**  

122.  * 检查文本框的长度是否超出指定长度.  

123.  * @param {文本id} textId  

124.  * @param {文本框的最大长度} len  

125.  * @param {文本框描述内容} msg  

126.  * @return {有错就返回false,否则返回true}  

127.  */  

128. function checkLength(textId, len, msg) {   

129.         obj = $(textId);   

130.         str = obj.value;   

131.         str = str.replace(/[^\x00-\xff]/g, "**");   

132.         realLen = str.length;   

133.         if (realLen > len) {   

134.                 alert("[" + msg + "]" + "长度最大为" + len + "位," + "请重新输入!\n注意:一个汉字占2位。");   

135.                 obj.focus();   

136.                 return false;   

137.         } else  

138.                 return true;   

139. }   

140. /**  

141.  * 判断某个文本框不可以为空.  

142.  * @param {文本框id} textId  

143.  * @param {文本框描述内容} msg  

144.  * @return {有错就返回false,否则返回true}  

145.  */  

146. function checkIfEmpty(textId, msg) {   

147.         var textObj = $(textId);   

148.         var textValue = textObj.value;   

149.         if (trim(textValue) == '') {   

150.                 alert('[' + msg + ']不得为空!');   

151.                 textObj.focus();   

152.                 return false;   

153.         } else {   

154.                 return true;   

155.         }   

156. }    

157. /**  

158.  * 判断指定文本框内容必须为邮件.  

159.  * @param {文本框id} textId  

160.  * @param {文本框描述} msg  

161.  * @return {如果是邮件内容就返回true否则返回false}  

162.  */  

163. function checkIsMail(textId, msg) {   

164.         var obj = $(textId);   

165.         if (!_isEmail(obj.value)) {   

166.                 alert('[' + msg + ']不是合法的邮件地址!');   

167.                 obj.focus();   

168.                 return false;   

169.         } else  

170.                 return true;   

171. }   

/**  

172.  * 验证是不是邮件.  

173.  * @param {要验证的字符串} strEmail  

174.  * @return {Boolean}  

175.  */  

176. function _isEmail(strEmail) {   

177.         //接下来的验证是否有两个以上的‘.’号,有的话就是错的!   

178.         var first = strEmail.indexOf('.');     

179.         if (strEmail.indexOf('@')== -1) {   

180.                 return false;   

181.         }   

182.         var tempStr = strEmail.substring(first + 1);   

183.          if (tempStr.indexOf('.') != -1) {   

184.                 return false;   

185.         }   

186.         if (strEmail   

187.                         .search(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/) != -1) {   

188.                 return true;   

189.         } else  

190.                 return false;   

191. }    

192. /**  

193.  * 判断某个文本框是否数字.  

194.  * @param {文本框id} textId  

195.  * @param {文本框描述内容} msg  

196.  * @return {Boolean}  

197.  */  

198. function checkIsNum(textId, msg) {   

199.         obj = $(textId);   

200.         if (isNaN(obj.value)) {   

201.                 alert('[' + msg + ']必须为数字。');   

202.                 obj.focus();   

203.                 return false;   

204.         } else  

205.                 return true;   

206. }     

207. /**  

208.  * 判断某个文本框是否含有非法字符.  

209.  * @param {文本框的id} textId  

210.  * @param {文本框描述内容} msg  

211.  * @return {有错就返回false否则返回true}  

212.  */  

213. function checkIsValid(textId, msg) {   

214.         obj = $(textId);   

215.         if (!_isValidString(obj.value, '[' + msg + ']不得含有非法字符。')) {   

216.                 obj.focus();   

217.                 return false;   

218.         }   

219.         return true;   

220. }   

221. /**  

222.  * 判断是不是合法字符串.  

223.  * @param {要进行判断的字符串} szStr  

224.  * @param {文本描述} errMsg   

225.  * @return {合法则返回true否则返回false}  

226.  */  

227. function _isValidString(szStr,errMsg) {   

228.         voidChar = "'\"><`~!@#$%^&\(\)()!¥……??“”‘’*";   

229.         for (var i = 0; i < voidChar.length; i++) {   

230.                 aChar = voidChar.substring(i, i + 1);   

231.                 if (szStr.indexOf(aChar) > -1){   

232.                         alert(errMsg)   

233.                         return false;   

234.                 }   

235.         }   

236.         return true;   

237. }    

238. /*************** 以下方法和下拉菜单相关*************************************************/  

239. /**  

240.  * 控制下拉菜单不可以为-1(未选择情况value=-1)  

241.  * @param {下拉菜单id} selectId  

242.  * @param {下拉菜单描述内容} msg  

243.  * @param {下拉菜单的空值对应的value,默认为-1} nullValue  

244.  * @return {Boolean}  

245.  */  

246. function checkChooseSelect(selectId, msg ,nullValue) {   

247.         var obj = $(selectId);    

248.         if (obj.value == notNull(nullValue,'-1')) {   

249.                 alert('[' + msg + ']必选!');   

250.                 obj.focus();   

251.                 return false;   

252.         } else  

253.                 return true;   

254. }   

255.   

256. /**  

257.  * 得到下拉菜单的显示的文字.  

258.  * @param {下拉菜单dom对象} selectObj  

259.  * @return {返回下拉菜单的显示的"文本"}  

260.  */  

261. function getSelectText(selectObj) {   

262.        return selectObj.options[selectObj.selectedIndex].text;   

263. }   

264.   

265. /**  

266.  * 得到下拉菜单的显示的值.  

267.  * @param {下拉菜单dom对象} selectObj  

268.  * @return {得到下拉菜单的显示的"值"}  

269.  */  

270. function getSelectValue(selectObj) {   

271.         return selectObj.options[selectObj.selectedIndex].value;   

272. }   

273. /**  

274.  * 设置下拉菜单的选择状态到指定的值.  

275.  * @param {下拉菜单对象} obj  

276.  * @param {要选择的值} value  

277.  */  

278. function setSelectValue(obj, value) {   

279.         /*for (i = obj.options.length - 1; i >= 0; i--) {  

280.                 if (obj.options[i].value == value) {  

281.                         obj.options[i].selected = true;  

282.                         return;  

283.                 }  

284.         }  

285. */  

286.       obj.value= value;   

287. }   

288. /**  

289.  * 根据键值串的内容进行下拉菜单的动态组装  

290.  * @param {要进行下拉菜单组装的dom对象} obj  

291.  * @param {键值对用,和;分割,例如'1,男;2,女;3,未知'} valAndText  

292.  */  

293. function setSelectContent(obj,valAndText){   

294.         if(trim(valAndText)==''){   

295.            alert('没有要进行组装下拉菜单的数据!');   

296.                 return false;                   

297.         }   

298.         clearSelect(obj);   

299.         var keyandvalues = valAndText.split(';');   

300.         for(var i=0;i<keyandvalues.length;i++){   

301.                 var arr = keyandvalues[i].split(',');   

302.                 if(arr){   

303.                         var value =arr[0];    

304.                         var text =arr[1];    

305.                         var objOption = new Option(text,value);   

306.                         obj.add(objOption);   

307.                 }   

308.         }   

309. }   

310. /**  

311.  * 清空下拉菜单里面的内容.  

312.  * @param {下拉菜单对象} obj  

313.  */  

314. function clearSelect(obj) {    

315.     for (var i=obj.options.length; i >0; i--) {   

316.         obj.remove(0);    

317.     }           

318. }    

319. /*************** 以下方法和多选框相关*************************************************/  

320. /**  

321.  * 返回选中的checks的id组成的字符串,逗号隔开.  

322.  * @param {checks数组} checks  

323.  * @return 选择的id组成的字符串  

324.  */  

325. function getCheckedIds(checks){   

326.         var selectedValue = '';   

327.         var len = checks.length;   

328.         for(var index=0; index<len; index++) {   

329.         if(checks[index].checked==true) {   

330.             selectedValue +=  checks[index].value+",";    

331.         }   

332.     }   

333.         if(selectedValue.length>0)   

334.                 return selectedValue.substring(0,selectedValue.length-1);   

335.     return selectedValue;   

336. }  

测试

1. <HTML>  

2.         <HEAD>  

3.                 <script language="javascript" src="aaaaa.js"></script>  

4.         </HEAD>  

5.         <SCRIPT LANGUAGE="JavaScript">  

6. <!--   

7. /**   

8.  * 表单验证的示例js方法.   

9.  */   

10. function check(){   

11.         if(checkIfEmpty('a','非空校验')   

12.                 &&checkIsMail('b','邮箱校验')   

13.                 &&checkIsNum('c','数字校验')   

14.                 &&checkIsValid('d','合法性校验')   

15.                 &&compareTwoDate('e','f','小日期与大日期关系错误!')   

16.                 &&checkLength('g',5,'长度校验')   

17.                 &&checkChooseSelect('h','下拉菜单非空','-1')   

18.                 &&compareTwoNum('k','l','大小数目关系不正确!')){   

19.                 alert('校验通过!');   

20.                 return true;           

21.         }else{   

22.                 return false;   

23.         }   

24. }   

25.   

26. /**   

27.  * 取下拉菜单的值和文本的示例js方法.   

28.  */   

29. function getSelect(){   

30.         var sss = $('h');   

31.         alert('下拉菜单选择的文本是:'+getSelectText(sss)+'\n'   

32.                         +'下拉菜单选择的值是:'+getSelectValue(sss));   

33. }   

34. /**   

35.  * 根据键值字符串设置下拉菜单的显示内容.   

36.  */   

37. function setSelect(){   

38.         var sss = $('i').value;   

39.         setSelectContent($('h'),sss);   

40. }   

/**   

41.  * 返回多选框数组选择状态的id的字符串,结果以逗号隔开.   

42.  */   

43. function getMulti(){    

44.         alert('多选选择的id是:'+getCheckedIds(document.getElementsByName('j')));   

45. }   

46. //-->  

47. </SCRIPT>  

48.  <BODY>  

49.  <table>  

50.  <tr>  

51.      <td>  非空:   <input id='a'>  </td>  

52.      <td>  checkIfEmpty('a','非空校验')  </td>  

53. </tr>  

54. <tr>  

55.      <td>  邮箱:   <input id='b' value='323232@2323.com'>  </td>  

56.      <td>  checkIsMail('b','邮箱校验')   </td>  

57.  </tr>  

58.  <tr>  

59.       <td>  数字: <input id='c' value='aaaa'>  </td>  

60.       <td>  checkIsNum('c','数字校验')   </td>  

61. </tr>  

62. <tr>  

63.     <td>  合法字符:   <input id='d' value='@$@$#$#!%%#'>  </td>  

64.     <td>  checkIsValid('d','合法性校验')   </td>  

65. </tr>  

66. <tr>  

67.     <td>  小的日期:   <input id='e' value='2010-1-1'>  

68.           大的日期:   <input id='f' value='2011-1-1'>  

69.     </td>  

70.     <td>  compareTwoDate('e','f','小日期与大日期关系错误!')  </td>  

71. </tr>  

72. <tr>  

73.     <td>  小的数: <input id='k' value='12.3'>  

74.           大的数: <input id='l' value='4564'>  

75.     </td>  

76.     <td>  compareTwoNum('k','l','大小数目关系不正确!')   </td>  

77. </tr>  

78. <tr>  

79.    <td>  字符长度校验(<5):   <input id='g'>  </td>  

80.    <td>  checkLength('g',5,'长度校验')   </td>  

81. </tr>  

82. <tr>  

83.     <td>  下拉菜单非空校验:   <select id='h'>  

84.                                  <option value='-1'> 请选择</option>  

85.                                  <option value='1'> 立项   </option>  

86.                                  <option value='2'> 可研   </option>  

87.                                </select>  

88.     </td>  

89.     <td> checkChooseSelect('h','下拉菜单非空','-1')   </td>  

90. </tr>  

91. <tr>  

92.    <td colspan='2'>  

93.        <button onclick='check()'>  测试表单校验方法   </button>  

94.    </td>  

95. </tr>  

96. <tr>  

97. <td>  <button onclick='getSelect()'>  得到下拉菜单的值   </button>  

98. </td>  

99. <td>  getSelectText(sss)和getSelectValue(sss)    </td>  

100. </tr>  

101. <tr>  

102.      <td>  输入下拉菜单的键值字符串(如右所示)   

103.           <input id='i' value='1,男;2,女;3,未知'>  

104.           <button onclick='setSelect()'>  设置下拉菜单的值   

105.           </button>  

106.      </td>  

107.      <td>  setSelectContent($('h'),sss)   </td>  

108.  </tr>  

109.  <tr>  

110.      <td>  

111.   <input type='checkbox' name='j' value='aaa1'>  

112.   <input type='checkbox' name='j' value='aaa2'>  

113.   <input type='checkbox' name='j' value='aaa3'>  

114.   <input type='checkbox' name='j' value='aaa4'>  

115.   <button onclick='getMulti()'> 得到多选选择的id </button>  

116.      </td>  

117.      <td>  getCheckedIds(document.getElementsByName('j'))   </td>  

118. </tr>  

119.                 </table>  

120.         </BODY>  

121. </HTML>  

 

详解 JavaScript 的类

Javascript从当初的一个“弹窗语言”,一步步发展成为现在前后端通吃的庞然大物。javascript的受欢迎程度也是与日俱增,今年最受欢迎编程语言又是花落javascript,这实在是一个充满了活力的语言。如今,随随便一个网页 JS 代码量都不下数千行,要是node后端那更不止几千行了。(WTF)代码量的增加给编程带来的首要的问题就是如何去编写和维护如此庞大的代码呢?答案就是模块化思想,其实就是面向对象(OOP)编程,如今比较流行的三大前端框架(angular,react,vue)基本都实现了组件化编程,但组件化和我们所说的模块化又有所不同,应该可以这么理解,组件化是模块化的升级版,模块化是组件化的基础。那么,问题来了,javascript怎么面向对象编程呢?我想这很多老鸟也不定说的清楚吧,前端杂而活跃的各种社区,很多前端er一度依赖各种插件,根本不需要去了解这些深层的东西却照样能完成任务。但我想作为一个有追求的前端er还是很有必要去学习这些看似用不着的东西的,别的不说,就单单因为我们是前端er,只会用jquery一个稍微厉害的后端都能代替你。好吧,废话不多说,到底如何面向对象coding呢…..

很不幸,es5中并没有给出明确的定义「类」的概念,所以传统的面向对象编程似乎是行不通的,那么又该肿么办呢?值得庆幸的是,前辈们通过不断探索总结,成功的用javascript模拟出了「类」。那么,javascript的类又该怎么定义呢?

在面向对象编程中,类(class)是对象(object)的模板,定义了同一组对象(又称”实例”)共有的属性和方法。

正文

说到 JS 中的“类”就不得不说原型链和继承了,因为 JS 是没有真正意义上的类的,所谓的类就是基于原型链和继承来实现的,即使ES6中加入了class、extends关键字实现类和继承,但实际上还是基于原型链和继承, ES6 类(class)是 JavaScript 现有的原型继承的语法糖。

1. 原型链和继承

“ 在 javaScript 中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为 null 为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构就称为原型链(prototype chain)。 ”

原型链

实际上在定义一个对象的时候原型链本身就已经生成了,javascript处处皆对象的思想放在这里理解起来就很容易了,看完后你会发现万物始于Object.prototype。那么我们都是如何定义一个对象的呢,博主总结的方法如下几个:

先初步来个demo具体解释下原型链是咋回事吧:

//someObject.[[Prototype]] 符号是用于指派 someObject 的原型。这个等同于 JavaScript 的 proto 属性(现已弃用)。。从 ES6 开始, [[Prototype]] 可以用Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。这个方法可以放心使用博主亲测,主流浏览器已经支持了 
// 假定有一个对象 o, 其自身的属性(own properties)有 a 和 b: 
// {a: 1, b: 2} 
// o 的原型 o.[[Prototype]]有属性 b 和 c: 
// {b: 3, c: 4} 
// 最后, o.[[Prototype]].[[Prototype]] 是 null. 
// 这就是原型链的末尾,即 null, 
// 根据定义,null 没有[[Prototype]]. 
// 综上,整个原型链如下: 
// {a:1, b:2}—> {b:3, c:4} —> null

来来来,撸袖子搞出个对象!!!

使用普通方法来创建一个对象 
demo如下:

var o={ 
a:0, 
b:function(){ 
console.log(this.a) 


//创建一个对象 
//原型链如下: 
//o—>Object.prototype—>null 
var a = [“yo”, “whadup”, “?”]; 
//创建一个数组对象 
//(indexOf, forEach等方法都是从它继承而来). 
//原型链如下: 
//a—>Array.prototype —> Object.prototype —> null 
function f(){ 
return 1; 

//创建一个函数对象 
//(call, bind等方法都是从它继承而来): 
//原型链如下: 
//f —> Function.prototype —> Object.prototype —> null 
var date=new Date(); 
//创建一个日期对象 
//原型链如下: 
//date—>Date.prototype—>Object.pprototype—>null

使用构造函数的方法 
JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。——MDN 
demo如下:

function fun(){ 
this.propA=’prop’; 
this.propB=3; 

fun.prototype={ 
methodA:function(){ 
console.log(this.propA) 


var o=new fun(); 
o.methodA();//prop

// o是生成的对象,他的自身属性有’propA’和’propB’. 
// 在o被实例化时,o.[[Prototype]]指向了fun.prototype.

使用Object.create创建对象 
ECMAScript 5 中引入了一个新方法:Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数: 
var a = {a: 1}; 
// a —> Object.prototype —> null

var b = Object.create(a); 
// b —> a —> Object.prototype —> null 
console.log(b.a); // 1 (继承而来)继承概念下面会讲

var c = Object.create(b); 
// c —> b —> a —> Object.prototype —> null

var d = Object.create(null); 
// d —> null 
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype

使用 class 关键字 
ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不一样的。 JavaScript 仍然是基于原型的。这些新的关键字包括 class, constructor, static, extends, 和 super.

“use strict”;

class Polygon { 
constructor(height, width) { 
this.height = height; 
this.width = width; 


//定义一个类Polygon 
class Square extends Polygon { 
constructor(sideLength) { 
super(sideLength, sideLength); 
}//使用super引用父类 
get area() { 
return this.height * this.width; 

set sideLength(newLength) { 
this.height = newLength; 
this.width = newLength; 


//使用extends定义Squeare继承父类Polygon 
var square = new Square(2);//实例对象

//此时的原型链为: 
//square—>Square.prototype—>Polygon.prototype—>Object.prototype—>null 
//如果不理解为什么是这样,不要紧接着往下看类的说明

继承

其实在上面讲原型链的时候难以避免的也提到了继承,比如来自MDN的这个实例:

// 假定有一个对象 o, 其自身的属性(own properties)有 a 和 b: 
// {a: 1, b: 2} 
// o 的原型 o.[[Prototype]]有属性 b 和 c:(someObject.[[Prototype]] 符号是用于指派 someObject 的原型。这个等同于 JavaScript 的 proto 属性(现已弃用)。。从 ES6 开始, [[Prototype]] 可以用Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。) 
// {b: 3, c: 4} 
// 最后, o.[[Prototype]].[[Prototype]] 是 null. 
// 这就是原型链的末尾,即 null, 
// 根据定义,null 没有[[Prototype]]. 
// 综上,整个原型链如下: 
// {a:1, b:2} —> {b:3, c:4} —> null 
console.log(o.a); // 1 
// a是o的自身属性吗?是的,该属性的值为1

console.log(o.b); // 2 
// b是o的自身属性吗?是的,该属性的值为2 
// o.[[Prototype]]上还有一个’b’属性,但是它不会被访问到.这种情况称为”属性遮蔽 (property shadowing)”.

console.log(o.c); // 4 
// c是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有. 
// c是o.[[Prototype]]的自身属性吗?是的,该属性的值为4

console.log(o.d); // undefined 
// d是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有. 
// d是o.[[Prototype]]的自身属性吗?不是,那看看o.[[Prototype]].[[Prototype]]上有没有. 
// o.[[Prototype]].[[Prototype]]为null,停止搜索, 
// 没有d属性,返回undefined

—-以上内容来自MDN继承与原型链

我想看到这里还是有些似懂非懂吧,那么来个例子吧:

var object1={ 
a:1, 
b:function(){ 
console.log(this.a+1); 


//定义一个对象 
var object2=Object.create(object1); 
//调用Object.create来创建一个新对象,新对象的原型就是调用 create 方法时传入的第一个参数,现在的原型链是: 
//object2—>object1—>object1.prototype—>null 
object2.d=4; 
object2.a; 
//1 
//继承了object1的属性a 
object2.b(); 
//2 
//继承了object1的方法b 
Object.getPrototypeOf(object2); 
//object1得到object2的原型object1

我想现在应该是明白了吧,再不明白博主也是无能为力了,表达能力实在有限。

1. 

总算说到类了,由于javascript的类基于原型链和继承,因此在上面的内容中就已经定义了很多的类。咱们javascript的类同样能实现传统类的多态,封装,继承等特性,这里主要讲解了继承这个概念,但实际上很多时候不经意可能就用了这三个特性。很好玩不是么 
首先,我们先看下在ES5中定义一个类的形式:

function Animal(name) { 
this.name = name; 
this.sleep = function() { 
console.log(this.name+’正在睡觉’); 

}//定义Animal类 
Animal.prototype = { 
eat: function(food) { 
console.log(this.name+”正在吃”+food); 


function Cat() {


Cat.prototype = new Animal(‘Tom’); 
var Tom = new Cat(‘Tom’);/Cat实例对象 
Tom.eat(‘猫粮’); 
//Tom正在吃猫粮 
//继承Animal方法 
Tom.sleep(); 
//Tom正在睡觉 
//继承Animal方法 
//现在的原型链: 
//Tom(Cat实例对象)—>Cat.prototype(Animal实例对象)—>Animal.prototype—>Object.prototype—>null

好的,然后我们看下在ES6中改写上面的类:

class Animal { 
constructor(name) { 
this.name = name; 
}

sleep() { 
console.log(this.name + ’ 正在睡觉’); 

eat(food){ 
console.log(this.name+’正在吃’+food) 

}

class Cat extends Animal {

}

const Tom = new Cat(‘Tom’); 
Tom.eat(‘猫粮’); 
//Tom正在吃猫粮 
//继承Animal方法 
Tom.sleep(); 
//Tom正在睡觉 
//继承Animal方法 
//现在的原型链: 
//Tom(Cat实例对象)—>Cat.prototype(Animal实例对象)—>Animal.prototype—>Object.prototype—>null 
定义一个类的方法实际上也是上面所说的定义一个对象的方法,类本身就是一个对象,只不过这个对象里面的方法和属性可以供许多实例对象调用而已。

后记

总的来说对于类的理解还是需要不断探索的,路漫漫其修远兮,吾将上下而求索。

 

 

JavaScript事件详解 

Event对

目录

事件是一种异步编程的实现方式,本质上是程序各个组成部分之间的通信。DOM支持大量的事件,本节介绍DOM的事件编程。

EventTarget接口

DOM的事件操作(监听和触发),都定义在EventTarget接口。Element节点、document节点和window对象,都部署了这个接口。此外,XMLHttpRequestAudioNodeAudioContext等浏览器内置对象,也部署了这个接口。

该接口就是三个方法,addEventListenerremoveEventListener用于绑定和移除监听函数,dispatchEvent用于触发事件。

addEventListener()

addEventListener方法用于在当前节点或对象上,定义一个特定事件的监听函数。

// 使用格式target.addEventListener(type, listener[, useCapture]);

// 实例window.addEventListener('load', function () {...}, false);request.addEventListener('readystatechange', function () {...}, false);

addEventListener方法接受三个参数。

· type:事件名称,大小写敏感。

· listener:监听函数。事件发生时,会调用该监听函数。

· useCapture:布尔值,表示监听函数是否在捕获阶段(capture)触发(参见后文《事件的传播》部分),默认为false(监听函数只在冒泡阶段被触发)。老式浏览器规定该参数必写,较新版本的浏览器允许该参数可选。为了保持兼容,建议总是写上该参数。

下面是一个例子。

function hello() {

  console.log('Hello world');}

var button = document.getElementById('btn');button.addEventListener('click', hello, false);

上面代码中,addEventListener方法为button元素节点,绑定click事件的监听函数hello,该函数只在冒泡阶段触发。

addEventListener方法可以为当前对象的同一个事件,添加多个监听函数。这些函数按照添加顺序触发,即先添加先触发。如果为同一个事件多次添加同一个监听函数,该函数只会执行一次,多余的添加将自动被去除(不必使用removeEventListener方法手动去除)。

function hello() {

  console.log('Hello world');}

document.addEventListener('click', hello, false);document.addEventListener('click', hello, false);

执行上面代码,点击文档只会输出一行Hello world

如果希望向监听函数传递参数,可以用匿名函数包装一下监听函数。

function print(x) {

  console.log(x);}

var el = document.getElementById('div1');el.addEventListener('click', function () { print('Hello'); }, false);

上面代码通过匿名函数,向监听函数print传递了一个参数。

removeEventListener()

removeEventListener方法用来移除addEventListener方法添加的事件监听函数。

div.addEventListener('click', listener, false);div.removeEventListener('click', listener, false);

removeEventListener方法的参数,与addEventListener方法完全一致。它的第一个参数“事件类型”,大小写敏感。

注意,removeEventListener方法移除的监听函数,必须与对应的addEventListener方法的参数完全一致,而且必须在同一个元素节点,否则无效。

dispatchEvent()

dispatchEvent方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true

target.dispatchEvent(event)

dispatchEvent方法的参数是一个Event对象的实例。

para.addEventListener('click', hello, false);var event = new Event('click');para.dispatchEvent(event);

上面代码在当前节点触发了click事件。

如果dispatchEvent方法的参数为空,或者不是一个有效的事件对象,将报错。

下面代码根据dispatchEvent方法的返回值,判断事件是否被取消了。

var canceled = !cb.dispatchEvent(event);

  if (canceled) {

    console.log('事件取消');

  } else {

    console.log('事件未取消');

  }}

监听函数

监听函数(listener)是事件发生时,程序所要执行的函数。它是事件驱动编程模式的主要编程方式。

DOM提供三种方法,可以用来为事件绑定监听函数。

HTML标签的on-属性

HTML语言允许在元素标签的属性中,直接定义某些事件的监听代码。

<body onload="doSomething()"><div onclick="console.log('触发事件')">

上面代码为body节点的load事件、div节点的click事件,指定了监听函数。

使用这个方法指定的监听函数,只会在冒泡阶段触发。

注意,使用这种方法时,on-属性的值是将会执行的代码,而不是“监听函数”。

<!-- 正确 --><body onload="doSomething()">

<!-- 错误 --><body onload="doSomething">

一旦指定的事件发生,on-属性的值是原样传入JavaScript引擎执行。因此如果要执行函数,不要忘记加上一对圆括号。

另外,Element节点的setAttribute方法,其实设置的也是这种效果。

el.setAttribute('onclick', 'doSomething()');

Element节点的事件属性

Element节点有事件属性,可以定义监听函数。

window.onload = doSomething;

div.onclick = function(event){

  console.log('触发事件');};

使用这个方法指定的监听函数,只会在冒泡阶段触发。

addEventListener方法

通过Element节点、document节点、window对象的addEventListener方法,也可以定义事件的监听函数。

window.addEventListener('load', doSomething, false);

addEventListener方法的详细介绍,参见本节EventTarget接口的部分。

在上面三种方法中,第一种“HTML标签的on-属性”,违反了HTML与JavaScript代码相分离的原则;第二种“Element节点的事件属性”的缺点是,同一个事件只能定义一个监听函数,也就是说,如果定义两次onclick属性,后一次定义会覆盖前一次。因此,这两种方法都不推荐使用,除非是为了程序的兼容问题,因为所有浏览器都支持这两种方法。

addEventListener是推荐的指定监听函数的方法。它有如下优点:

可以针对同一个事件,添加多个监听函数。

能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发回监听函数。

除了DOM节点,还可以部署在window、XMLHttpRequest等对象上面,等于统一了整个JavaScript的监听函数接口。

this对象的指向

实际编程中,监听函数内部的this对象,常常需要指向触发事件的那个Element节点。

addEventListener方法指定的监听函数,内部的this对象总是指向触发事件的那个节点。

// HTML代码为// <p id="para">Hello</p>

var id = 'doc';var para = document.getElementById('para');

function hello(){

  console.log(this.id);}

para.addEventListener('click', hello, false);

执行上面代码,点击p节点会输出para。这是因为监听函数被“拷贝”成了节点的一个属性,使用下面的写法,会看得更清楚。

para.onclick = hello;

如果将监听函数部署在Element节点的on-属性上面,this不会指向触发事件的元素节点。

<p id="para" onclick="hello()">Hello</p><!-- 或者使用JavaScript代码  --><script>

  pElement.setAttribute('onclick', 'hello()');</script>

执行上面代码,点击p节点会输出doc。这是因为这里只是调用hello函数,而hello函数实际是在全局作用域执行,相当于下面的代码。

para.onclick = function(){

  hello();}

一种解决方法是,不引入函数作用域,直接在on-属性写入所要执行的代码。因为on-属性是在当前节点上执行的。

<p id="para" onclick="console.log(id)">Hello</p><!-- 或者 --><p id="para" onclick="console.log(this.id)">Hello</p>

上面两行,最后输出的都是para。

总结一下,以下写法的this对象都指向Element节点。

// JavaScript代码element.onclick = printelement.addEventListener('click', print, false)element.onclick = function () {console.log(this.id);}

// HTML代码<element onclick="console.log(this.id)">

以下写法的this对象,都指向全局对象。

// JavaScript代码element.onclick = function (){ doSomething() };element.setAttribute('onclick', 'doSomething()');

// HTML代码<element onclick="doSomething()">

事件的传播

传播的三个阶段

当一个事件发生以后,它会在不同的DOM节点之间传播(propagation)。这种传播分成三个阶段:

第一阶段:从window对象传导到目标节点,称为“捕获阶段”(capture phase)。

第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。

第三阶段:从目标节点传导回window对象,称为“冒泡阶段”(bubbling phase)。

这种三阶段的传播模型,会使得一个事件在多个节点上触发。比如,假设div节点之中嵌套一个p节点。

<div>

  <p>Click Me</p></div>

如果对这两个节点的click事件都设定监听函数,则click事件会被触发四次。

var phases = {

  1: 'capture',

  2: 'target',

  3: 'bubble'};

var div = document.querySelector('div');var p = document.querySelector('p');

div.addEventListener('click', callback, true);p.addEventListener('click', callback, true);div.addEventListener('click', callback, false);p.addEventListener('click', callback, false);

function callback(event) {

  var tag = event.currentTarget.tagName;

  var phase = phases[event.eventPhase];

  console.log("Tag: '" + tag + "'. EventPhase: '" + phase + "'");}

// 点击以后的结果// Tag: 'DIV'. EventPhase: 'capture'// Tag: 'P'. EventPhase: 'target'// Tag: 'P'. EventPhase: 'target'// Tag: 'DIV'. EventPhase: 'bubble'

上面代码表示,click事件被触发了四次:p节点的捕获阶段和冒泡阶段各1次,div节点的捕获阶段和冒泡阶段各1次。

1. 捕获阶段:事件从div向p传播时,触发div的click事件;

2. 目标阶段:事件从div到达p时,触发p的click事件;

3. 目标阶段:事件离开p时,触发p的click事件;

4. 冒泡阶段:事件从p传回div时,再次触发div的click事件。

注意,用户点击网页的时候,浏览器总是假定click事件的目标节点,就是点击位置的嵌套最深的那个节点(嵌套在div节点的p节点)。

事件传播的最上层对象是window,接着依次是document,html(document.documentElement)和body(document.dody)。也就是说,如果body元素中有一个div元素,点击该元素。事件的传播顺序,在捕获阶段依次为window、document、html、body、div,在冒泡阶段依次为div、body、html、document、window。

事件的代理

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

var ul = document.querySelector('ul');

ul.addEventListener('click', function(event) {

  if (event.target.tagName.toLowerCase() === 'li') {

    // some code

  }});

上面代码的click事件的监听函数定义在ul节点,但是实际上,它处理的是子节点li的click事件。这样做的好处是,只要定义一个监听函数,就能处理多个子节点的事件,而且以后再添加子节点,监听函数依然有效。

如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法。

p.addEventListener('click', function(event) {

  event.stopPropagation();});

使用上面的代码以后,click事件在冒泡阶段到达p节点以后,就不再向上(父节点的方向)传播了。

但是,stopPropagation方法不会阻止p节点上的其他click事件的监听函数。如果想要不再触发那些监听函数,可以使用stopImmediatePropagation方法。

p.addEventListener('click', function(event) {

 event.stopImmediatePropagation();});

p.addEventListener('click', function(event) {

 // 不会被触发});

Event对象

事件发生以后,会生成一个事件对象,作为参数传给监听函数。浏览器原生提供一个Event对象,所有的事件都是这个对象的实例,或者说继承了Event.prototype对象。

Event对象本身就是一个构造函数,可以用来生成新的实例。

event = new Event(typeArg, eventInit);

Event构造函数接受两个参数。第一个参数是字符串,表示事件的名称;第二个参数是一个对象,表示事件对象的配置。该参数可以有以下两个属性。

bubbles:布尔值,可选,默认为false,表示事件对象是否冒泡。

cancelable:布尔值,可选,默认为false,表示事件是否可以被取消。

var ev = new Event("look", {"bubbles":true, "cancelable":false});document.dispatchEvent(ev);

上面代码新建一个look事件实例,然后使用dispatchEvent方法触发该事件。

IE8及以下版本,事件对象不作为参数传递,而是通过window对象的event属性读取,并且事件对象的target属性叫做srcElement属性。所以,以前获取事件信息,往往要写成下面这样。

function myEventHandler(event) {

  var actualEvent = event || window.event;

  var actualTarget = actualEvent.target || actualEvent.srcElement;

  // ...}

上面的代码只是为了说明以前的程序为什么这样写,在新代码中,这样的写法不应该再用了。

以下介绍Event实例的属性和方法。

bubbles,eventPhase

以下属性与事件的阶段有关。

1)bubbles

bubbles属性返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性,只能在新建事件时改变。除非显式声明,Event构造函数生成的事件,默认是不冒泡的。

function goInput(e) {

  if (!e.bubbles) {

    passItOn(e);

  } else {

    doOutput(e);

  }}

上面代码根据事件是否冒泡,调用不同的函数。

2)eventPhase

eventPhase属性返回一个整数值,表示事件目前所处的节点。

var phase = event.eventPhase;

0,事件目前没有发生。

1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。该过程是从Window对象到Document节点,再到HTMLHtmlElement节点,直到目标节点的父节点为止。

2,事件到达目标节点,即target属性指向的那个节点。

3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。该过程是从父节点一直到Window对象。只有bubbles属性为true时,这个阶段才可能发生。

cancelable,defaultPrevented

以下属性与事件的默认行为有关。

1)cancelable

cancelable属性返回一个布尔值,表示事件是否可以取消。该属性为只读属性,只能在新建事件时改变。除非显式声明,Event构造函数生成的事件,默认是不可以取消的。

var bool = event.cancelable;

如果要取消某个事件,需要在这个事件上面调用preventDefault方法,这会阻止浏览器对某种事件部署的默认行为。

2)defaultPrevented

defaultPrevented属性返回一个布尔值,表示该事件是否调用过preventDefault方法。

if (e.defaultPrevented) {

  // ...}

currentTarget,target

以下属性与事件的目标节点有关。

1)currentTarget

currentTarget属性返回事件当前所在的节点,即正在执行的监听函数所绑定的那个节点。作为比较,target属性返回事件发生的节点。如果监听函数在捕获阶段和冒泡阶段触发,那么这两个属性返回的值是不一样的。

function hide(e){

  console.log(this === e.currentTarget);  // true

  e.currentTarget.style.visibility = "hidden";}

para.addEventListener('click', hide, false);

上面代码中,点击para节点,该节点会不可见。另外,在监听函数中,currentTarget属性实际上等同于this对象。

2)target

target属性返回触发事件的那个节点,即事件最初发生的节点。如果监听函数不在该节点触发,那么它与currentTarget属性返回的值是不一样的。

function hide(e){

  console.log(this === e.target);  // 有可能不是true

  e.target.style.visibility = "hidden";}

// HTML代码为// <p id="para">Hello <em>World</em></p>para.addEventListener('click', hide, false);

上面代码中,如果在para节点的em子节点上面点击,则e.target指向em子节点,导致em子节点(即World部分)会不可见,且输出false。

IE6—IE8之中,该属性的名字不是target,而是srcElement,因此经常可以看到下面这样的代码。

function hide(e) {

  var target = e.target || e.srcElement;

  target.style.visibility = 'hidden';}

type,detail,timeStamp,isTrusted

以下属性与事件对象的其他信息相关。

1)type

type属性返回一个字符串,表示事件类型,大小写敏感。

var string = event.type;

2)detail

detail属性返回一个数值,表示事件的某种信息。具体含义与事件类型有关,对于鼠标事件,表示鼠标按键在某个位置按下的次数,比如对于dblclick事件,detail属性的值总是2。

function giveDetails(e) {

  this.textContent = e.detail;}

el.onclick = giveDetails;

3)timeStamp

timeStamp属性返回一个毫秒时间戳,表示事件发生的时间。

var number = event.timeStamp;

Chrome在49版以前,这个属性返回的是一个整数,单位是毫秒(millisecond),表示从Unix纪元开始的时间戳。从49版开始,该属性返回的是一个高精度时间戳,也就是说,毫秒之后还带三位小数,精确到微秒。并且,这个值不再从Unix纪元开始计算,而是从PerformanceTiming.navigationStart开始计算,即表示距离用户导航至该网页的时间。如果想将这个值转为Unix纪元时间戳,就要计算event.timeStamp + performance.timing.navigationStart

下面是一个计算鼠标移动速度的例子,显示每秒移动的像素数量。

var previousX;var previousY;var previousT;

window.addEventListener('mousemove', function(event) {

  if (!(previousX === undefined ||

        previousY === undefined ||

        previousT === undefined)) {

    var deltaX = event.screenX - previousX;

    var deltaY = event.screenY - previousY;

    var deltaD = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));

    var deltaT = event.timeStamp - previousT;

    console.log(deltaD / deltaT * 1000);

  }

  previousX = event.screenX;

  previousY = event.screenY;

  previousT = event.timeStamp;});

4)isTrusted

isTrusted属性返回一个布尔值,表示该事件是否为真实用户触发。

var bool = event.isTrusted;

用户触发的事件返回true,脚本触发的事件返回false

preventDefault()

preventDefault方法取消浏览器对当前事件的默认行为,比如点击链接后,浏览器跳转到指定页面,或者按一下空格键,页面向下滚动一段距离。该方法生效的前提是,事件的cancelable属性为true,如果为false,则调用该方法没有任何效果。

该方法不会阻止事件的进一步传播(stopPropagation方法可用于这个目的)。只要在事件的传播过程中(捕获阶段、目标阶段、冒泡阶段皆可),使用了preventDefault方法,该事件的默认方法就不会执行。

// HTML代码为// <input type="checkbox" id="my-checkbox" />

var cb = document.getElementById('my-checkbox');

cb.addEventListener(

  'click',

  function (e){ e.preventDefault(); },

  false);

上面代码为点击单选框的事件,设置监听函数,取消默认行为。由于浏览器的默认行为是选中单选框,所以这段代码会导致无法选中单选框。

利用这个方法,可以为文本输入框设置校验条件。如果用户的输入不符合条件,就无法将字符输入文本框。

function checkName(e) {

  if (e.charCode < 97 || e.charCode > 122) {

    e.preventDefault();

  }}

上面函数设为文本框的keypress监听函数后,将只能输入小写字母,否则输入事件的默认事件(写入文本框)将被取消。

如果监听函数最后返回布尔值false(即return false),浏览器也不会触发默认行为,与preventDefault方法有等同效果。

stopPropagation()

stopPropagation方法阻止事件在DOM中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上新定义的事件监听函数。

function stopEvent(e) {

  e.stopPropagation();}

el.addEventListener('click', stopEvent, false);

将上面函数指定为监听函数,会阻止事件进一步冒泡到el节点的父节点。

stopImmediatePropagation()

stopImmediatePropagation方法阻止同一个事件的其他监听函数被调用。

如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了stopImmediatePropagation方法,其他的监听函数就不会再执行了。

function l1(e){

  e.stopImmediatePropagation();}

function l2(e){

  console.log('hello world');}

el.addEventListener('click', l1, false);el.addEventListener('click', l2, false);

上面代码在el节点上,为click事件添加了两个监听函数l1和l2。由于l1调用了stopImmediatePropagation方法,所以l2不会被调用。

鼠标事件

事件种类

鼠标事件指与鼠标相关的事件,主要有以下一些。

1)click事件

click事件当用户在Element节点、document节点、window对象上,单击鼠标(或者按下回车键)时触发。

“鼠标单击”定义为,用户在同一个位置完成一次mousedown动作和mouseup动作。它们的触发顺序是:mousedown首先触发,mouseup接着触发,click最后触发。

下面是一个设置click事件监听函数的例子。

div.addEventListener("click", function( event ) {

  // 显示在该节点,鼠标连续点击的次数

  event.target.innerHTML = "click count: " + event.detail;}, false);

下面的代码是利用click事件进行CSRF攻击(Cross-site request forgery)的一个例子。

<a href="http://www.harmless.com/" onclick="

  var f = document.createElement('form');

  f.style.display = 'none';

  this.parentNode.appendChild(f);

  f.method = 'POST';

  f.action = 'http://www.example.com/account/destroy';

  f.submit();

  return false;">伪装的链接</a>

2)dblclick事件

dblclick事件当用户在elementdocumentwindow对象上,双击鼠标时触发。该事件会在mousedownmouseupclick之后触发。

3)mouseup事件,mousedown事件

mouseup事件在释放按下的鼠标键时触发。

mousedown事件在按下鼠标键时触发。

4)mousemove事件

mousemove事件当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次代码。

5)mouseover事件,mouseenter事件

mouseover事件和mouseenter事件,都是鼠标进入一个节点时触发。

两者的区别是,mouseenter事件只触发一次,而只要鼠标在节点内部移动,mouseover事件会在子节点上触发多次。

// HTML代码为// <ul id="test">//   <li>item 1</li>//   <li>item 2</li>//   <li>item 3</li>// </ul>

var test = document.getElementById('test');

// 进入test节点以后,该事件只会触发一次// event.target 是 ul 节点test.addEventListener('mouseenter', function (event) {

  event.target.style.color = 'purple';

  setTimeout(function () {

    event.target.style.color = '';

  }, 500);}, false);

// 进入test节点以后,只要在子Element节点上移动,该事件会触发多次// event.target 是 li 节点test.addEventListener('mouseover', function (event) {

  event.target.style.color = 'orange';

  setTimeout(function () {

    event.target.style.color = '';

  }, 500);}, false);

6)mouseout事件,mouseleave事件

mouseout事件和mouseleave事件,都是鼠标离开一个节点时触发。

两者的区别是,mouseout事件会冒泡,mouseleave事件不会。子节点的mouseout事件会冒泡到父节点,进而触发父节点的mouseout事件。mouseleave事件就没有这种效果,所以离开子节点时,不会触发父节点的监听函数。

7)contextmenu

contextmenu事件在一个节点上点击鼠标右键时触发,或者按下“上下文菜单”键时触发。

MouseEvent对象

鼠标事件使用MouseEvent对象表示,它继承UIEvent对象和Event对象。浏览器提供一个MouseEvent构造函数,用于新建一个MouseEvent实例。

event = new MouseEvent(typeArg, mouseEventInit);

MouseEvent构造函数的第一个参数是事件名称(可能的值包括click、mousedown、mouseup、mouseover、mousemove、mouseout),第二个参数是一个事件初始化对象。该对象可以配置以下属性。

screenX,设置鼠标相对于屏幕的水平坐标(但不会移动鼠标),默认为0,等同于MouseEvent.screenX属性。

screenY,设置鼠标相对于屏幕的垂直坐标,默认为0,等同于MouseEvent.screenY属性。

clientX,设置鼠标相对于窗口的水平坐标,默认为0,等同于MouseEvent.clientX属性。clientY,设置鼠标相对于窗口的垂直坐标,默认为0,等同于MouseEvent.clientY属性。

ctrlKey,设置是否按下ctrl键,默认为false,等同于MouseEvent.ctrlKey属性。

shiftKey,设置是否按下shift键,默认为false,等同于MouseEvent.shiftKey属性。

altKey,设置是否按下alt键,默认为false,等同于MouseEvent.altKey属性。

metaKey,设置是否按下meta键,默认为false,等同于MouseEvent.metaKey属性。

button,设置按下了哪一个鼠标按键,默认为0。-1表示没有按键,0表示按下主键(通常是左键),1表示按下辅助键(通常是中间的键),2表示按下次要键(通常是右键)。

buttons,设置按下了鼠标哪些键,是一个3个比特位的二进制值,默认为0。1表示按下主键(通常是左键),2表示按下次要键(通常是右键),4表示按下辅助键(通常是中间的键)。

relatedTarget,设置一个Element节点,在mouseenter和mouseover事件时,表示鼠标刚刚离开的那个Element节点,在mouseout和mouseleave事件时,表示鼠标正在进入的那个Element节点。默认为null,等同于MouseEvent.relatedTarget属性。

以下属性也是可配置的,都继承自UIEvent构造函数和Event构造函数。

bubbles,布尔值,设置事件是否冒泡,默认为false,等同于Event.bubbles属性。

cancelable,布尔值,设置事件是否可取消,默认为false,等同于Event.cancelable属性。

view,设置事件的视图,一般是window或document.defaultView,等同于Event.view属性。

detail,设置鼠标点击的次数,等同于Event.detail属性。

下面是一个例子。

function simulateClick() {

  var event = new MouseEvent('click', {

    'bubbles': true,

    'cancelable': true

  });

  var cb = document.getElementById('checkbox');

  cb.dispatchEvent(event);}

上面代码生成一个鼠标点击事件,并触发该事件。

以下介绍MouseEvent实例的属性。

altKey,ctrlKey,metaKey,shiftKey

以下属性返回一个布尔值,表示鼠标事件发生时,是否按下某个键。

altKey属性:alt键

ctrlKey属性:key键

metaKey属性:Meta键(Mac键盘是一个四瓣的小花,Windows键盘是Windows键)

shiftKey属性:Shift键

// HTML代码为// <body onclick="showkey(event);">

function showKey(e){

  console.log("ALT key pressed: " + e.altKey);

  console.log("CTRL key pressed: " + e.ctrlKey);

  console.log("META key pressed: " + e.metaKey);

  console.log("SHIFT key pressed: " + e.shiftKey);}

上面代码中,点击网页会输出是否同时按下Alt键。

button,buttons

以下属性返回事件的鼠标键信息。

1)button

button属性返回一个数值,表示按下了鼠标哪个键。

-1:没有按下键。

0:按下主键(通常是左键)。

1:按下辅助键(通常是中键或者滚轮键)。

2:按下次键(通常是右键)。

// HTML代码为// <button onmouseup="whichButton(event);">点击</button>

var whichButton = function (e) {

  switch (e.button) {

    case 0:

      console.log('Left button clicked.');

      break;

    case 1:

      console.log('Middle button clicked.');

      break;

    case 2:

      console.log('Right button clicked.');

      break;

    default:

      console.log('Unexpected code: ' + e.button);

  }}

2)buttons

buttons属性返回一个3个比特位的值,表示同时按下了哪些键。它用来处理同时按下多个鼠标键的情况。

1:二进制为001,表示按下左键。

2:二进制为010,表示按下右键。

4:二进制为100,表示按下中键或滚轮键。

同时按下多个键的时候,每个按下的键对应的比特位都会有值。比如,同时按下左键和右键,会返回3(二进制为011)。

clientX,clientY,movementX,movementY,screenX,screenY

以下属性与事件的位置相关。

1)clientX,clientY

clientX属性返回鼠标位置相对于浏览器窗口左上角的水平坐标,单位为像素,与页面是否横向滚动无关。

clientY属性返回鼠标位置相对于浏览器窗口左上角的垂直坐标,单位为像素,与页面是否纵向滚动无关。

// HTML代码为// <body onmousedown="showCoords(event)">

function showCoords(evt){

  console.log(

    "clientX value: " + evt.clientX + "\n" +

    "clientY value: " + evt.clientY + "\n"

  );}

2)movementX,movementY

movementX属性返回一个水平位移,单位为像素,表示当前位置与上一个mousemove事件之间的水平距离。在数值上,等于currentEvent.movementX = currentEvent.screenX - previousEvent.screenX。

movementY属性返回一个垂直位移,单位为像素,表示当前位置与上一个mousemove事件之间的垂直距离。在数值上,等于currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。

3)screenX,screenY

screenX属性返回鼠标位置相对于屏幕左上角的水平坐标,单位为像素。

screenY属性返回鼠标位置相对于屏幕左上角的垂直坐标,单位为像素。

// HTML代码为// <body onmousedown="showCoords(event)">

function showCoords(evt){

  console.log(

    "screenX value: " + evt.screenX + "\n"

    + "screenY value: " + evt.screenY + "\n"

  );}

relatedTarget

relatedTarget属性返回事件的次要相关节点。对于那些没有次要相关节点的事件,该属性返回null。

下表列出不同事件的target属性和relatedTarget属性含义。

事件名称

target属性

relatedTarget属性

focusin

接受焦点的节点

丧失焦点的节点

focusout

丧失焦点的节点

接受焦点的节点

mouseenter

将要进入的节点

将要离开的节点

mouseleave

将要离开的节点

将要进入的节点

mouseout

将要离开的节点

将要进入的节点

mouseover

将要进入的节点

将要离开的节点

dragenter

将要进入的节点

将要离开的节点

dragexit

将要离开的节点

将要进入的节点

下面是一个例子。

// HTML代码为// <div id="outer" style="height:50px;width:50px;border-width:1px solid black;">//   <div id="inner" style="height:25px;width:25px;border:1px solid black;"></div>// </div>

var inner = document.getElementById("inner");

inner.addEventListener("mouseover", function (){

  console.log('进入' + event.target.id + " 离开" + event.relatedTarget.id);});inner.addEventListener("mouseenter", function (){

  console.log('进入' + event.target.id + " 离开" + event.relatedTarget.id);});inner.addEventListener("mouseout", function (){

  console.log('离开' + event.target.id + " 进入" + event.relatedTarget.id);});inner.addEventListener("mouseleave", function (){

  console.log('离开' + event.target.id + " 进入" + event.relatedTarget.id);});

// 鼠标从outer进入inner,输出// 进入inner 离开outer// 进入inner 离开outer

// 鼠标从inner进入outer,输出// 离开inner 进入outer// 离开inner 进入outer

wheel事件

wheel事件是与鼠标滚轮相关的事件,目前只有一个wheel事件。用户滚动鼠标的滚轮,就触发这个事件。

该事件除了继承了MouseEvent、UIEvent、Event的属性,还有几个自己的属性。

deltaX:返回一个数值,表示滚轮的水平滚动量。

deltaY:返回一个数值,表示滚轮的垂直滚动量。

deltaZ:返回一个数值,表示滚轮的Z轴滚动量。

deltaMode:返回一个数值,表示滚动的单位,适用于上面三个属性。0表示像素,1表示行,2表示页。

浏览器提供一个WheelEvent构造函数,可以用来生成滚轮事件的实例。它接受两个参数,第一个是事件名称,第二个是配置对象。

var syntheticEvent = new WheelEvent("syntheticWheel", {"deltaX": 4, "deltaMode": 0});

键盘事件

键盘事件用来描述键盘行为,主要有keydown、keypress、keyup三个事件。

keydown:按下键盘时触发该事件。

keypress:只要按下的键并非Ctrl、Alt、Shift和Meta,就接着触发keypress事件

keyup:松开键盘时触发该事件。

下面是一个例子,对文本框设置keypress监听函数,只允许输入数字。

// HTML代码为// <input type="text"//   name="myInput"//   onkeypress="return numbersOnly(this, event);"//   onpaste="return false;"// />

function numbersOnly(oToCheckField, oKeyEvent) {

  return oKeyEvent.charCode === 0

    || /\d/.test(String.fromCharCode(oKeyEvent.charCode));}

如果用户一直按键不松开,就会连续触发键盘事件,触发的顺序如下。

1. keydown

2. keypress

3. keydown

4. keypress

5. (重复以上过程)

6. keyup

键盘事件使用KeyboardEvent对象表示,该对象继承了UIEvent和MouseEvent对象。浏览器提供KeyboardEvent构造函数,用来新建键盘事件的实例。

event = new KeyboardEvent(typeArg, KeyboardEventInit);

KeyboardEvent构造函数的第一个参数是一个字符串,表示事件类型,第二个参数是一个事件配置对象,可配置以下字段。

key,对应KeyboardEvent.key属性,默认为空字符串。

ctrlKey,对应KeyboardEvent.ctrlKey属性,默认为false。

shiftKey,对应KeyboardEvent.shiftKey属性,默认为false。

altKey,对应KeyboardEvent.altKey属性,默认为false。

metaKey,对应KeyboardEvent.metaKey属性,默认为false。

下面就是KeyboardEvent实例的属性介绍。

altKey,ctrlKey,metaKey,shiftKey

以下属性返回一个布尔值,表示是否按下对应的键。

altKey:alt键

ctrlKey:ctrl键

metaKey:meta键(mac系统是一个四瓣的小花,windows系统是windows键)

shiftKey:shift键

function showChar(e){

  console.log("ALT: " + e.altKey);

  console.log("CTRL: " + e.ctrlKey);

  console.log("Meta: " + e.metaKey);

  console.log("Meta: " + e.shiftKey);}

key,charCode

key属性返回一个字符串,表示按下的键名。如果同时按下一个控制键和一个符号键,则返回符号键的键名。比如,按下Ctrl+a,则返回a。如果无法识别键名,则返回字符串Unidentified。

主要功能键的键名(不同的浏览器可能有差异):Backspace,Tab,Enter,Shift,Control,Alt,CapsLock,CapsLock,Esc,Spacebar,PageUp,PageDown,End,Home,Left,Right,Up,Down,PrintScreen,Insert,Del,Win,F1~F12,NumLock,Scroll等。

charCode属性返回一个数值,表示keypress事件按键的Unicode值,keydown和keyup事件不提供这个属性。注意,该属性已经从标准移除,虽然浏览器还支持,但应该尽量不使用。

进度事件

进度事件用来描述一个事件进展的过程,比如XMLHttpRequest对象发出的HTTP请求的过程、<img>、<audio>、<video>、<style>、<link>加载外部资源的过程。下载和上传都会发生进度事件。

进度事件有以下几种。

abort事件:当进度事件被中止时触发。如果发生错误,导致进程中止,不会触发该事件。

error事件:由于错误导致资源无法加载时触发。

load事件:进度成功结束时触发。

loadstart事件:进度开始时触发。

loadend事件:进度停止时触发,发生顺序排在error事件\abort事件\load事件后面。

progress事件:当操作处于进度之中,由传输的数据块不断触发。

timeout事件:进度超过限时触发。

image.addEventListener('load', function(event) {

  image.classList.add('finished');});

image.addEventListener('error', function(event) {

  image.style.display = 'none';});

上面代码在图片元素加载完成后,为图片元素的class属性添加一个值“finished”。如果加载失败,就把图片元素的样式设置为不显示。

有时候,图片加载会在脚本运行之前就完成,尤其是当脚本放置在网页底部的时候,因此有可能使得load和error事件的监听函数根本不会被执行。所以,比较可靠的方式,是用complete属性先判断一下是否加载完成。

function loaded() {

  // code after image loaded}

if (image.complete) {

  loaded();} else {

  image.addEventListener('load', loaded);}

由于DOM没有提供像complete属性那样的,判断是否发生加载错误的属性,所以error事件的监听函数最好放在img元素的HTML属性中,这样才能保证发生加载错误时百分之百会执行。

<img src="/wrong/url" onerror="this.style.display='none';" />

error事件有一个特殊的性质,就是不会冒泡。这样的设计是正确的,防止引发父元素的error事件监听函数。

进度事件使用ProgressEvent对象表示。ProgressEvent实例有以下属性。

lengthComputable:返回一个布尔值,表示当前进度是否具有可计算的长度。如果为false,就表示当前进度无法测量。

total:返回一个数值,表示当前进度的总长度。如果是通过HTTP下载某个资源,表示内容本身的长度,不含HTTP头部的长度。如果lengthComputable属性为false,则total属性就无法取得正确的值。

loaded:返回一个数值,表示当前进度已经完成的数量。该属性除以total属性,就可以得到目前进度的百分比。

下面是一个例子。

var xhr = new XMLHttpRequest();

xhr.addEventListener("progress", updateProgress, false);xhr.addEventListener("load", transferComplete, false);xhr.addEventListener("error", transferFailed, false);xhr.addEventListener("abort", transferCanceled, false);

xhr.open();

function updateProgress (e) {

  if (e.lengthComputable) {

    var percentComplete = e.loaded / e.total;

  } else {

    console.log('不能计算进度');

  }}

function transferComplete(e) {

  console.log('传输结束');}

function transferFailed(evt) {

  console.log('传输过程中发生错误');}

function transferCanceled(evt) {

  console.log('用户取消了传输');}

loadend事件的监听函数,可以用来取代abort事件/load事件/error事件的监听函数。

req.addEventListener("loadend", loadEnd, false);

function loadEnd(e) {

  console.log('传输结束,成功失败未知');}

loadend事件本身不提供关于进度结束的原因,但可以用它来做所有进度结束场景都需要做的一些操作。

另外,上面是下载过程的进度事件,还存在上传过程的进度事件。这时所有监听函数都要放在XMLHttpRequest.upload对象上面。

var xhr = new XMLHttpRequest();

xhr.upload.addEventListener("progress", updateProgress, false);xhr.upload.addEventListener("load", transferComplete, false);xhr.upload.addEventListener("error", transferFailed, false);xhr.upload.addEventListener("abort", transferCanceled, false);

xhr.open();

浏览器提供一个ProgressEvent构造函数,用来生成进度事件的实例。

progressEvent = new ProgressEvent(type, {

  lengthComputable: aBooleanValue,

  loaded: aNumber,

  total: aNumber});

上面代码中,ProgressEvent构造函数的第一个参数是事件类型(字符串),第二个参数是配置对象,用来指定lengthComputable属性(默认值为false)、loaded属性(默认值为0)、total属性(默认值为0)。

拖拉事件

拖拉指的是,用户在某个对象上按下鼠标键不放,拖动它到另一个位置,然后释放鼠标键,将该对象放在那里。

拖拉的对象有好几种,包括Element节点、图片、链接、选中的文字等等。在HTML网页中,除了Element节点默认不可以拖拉,其他(图片、链接、选中的文字)都是可以直接拖拉的。为了让Element节点可拖拉,可以将该节点的draggable属性设为true。

<div draggable="true">

  此区域可拖拉</div>

draggable属性可用于任何Element节点,但是图片(img元素)和链接(a元素)不加这个属性,就可以拖拉。对于它们,用到这个属性的时候,往往是将其设为false,防止拖拉。

注意,一旦某个Element节点的draggable属性设为true,就无法再用鼠标选中该节点内部的文字或子节点了。

事件种类

Element节点或选中的文本被拖拉时,就会持续触发拖拉事件,包括以下一些事件。

drag事件:拖拉过程中,在被拖拉的节点上持续触发。

dragstart事件:拖拉开始时在被拖拉的节点上触发,该事件的target属性是被拖拉的节点。通常应该在这个事件的监听函数中,指定拖拉的数据。

dragend事件:拖拉结束时(释放鼠标键或按下escape键)在被拖拉的节点上触发,该事件的target属性是被拖拉的节点。它与dragStart事件,在同一个节点上触发。不管拖拉是否跨窗口,或者中途被取消,dragend事件总是会触发的

dragenter事件:拖拉进入当前节点时,在当前节点上触发,该事件的target属性是当前节点。通常应该在这个事件的监听函数中,指定是否允许在当前节点放下(drop)拖拉的数据。如果当前节点没有该事件的监听函数,或者监听函数不执行任何操作,就意味着不允许在当前节点放下数据。在视觉上显示拖拉进入当前节点,也是在这个事件的监听函数中设置。

dragover事件:拖拉到当前节点上方时,在当前节点上持续触发,该事件的target属性是当前节点。该事件与dragenter事件基本类似,默认会重置当前的拖拉事件的效果(DataTransfer对象的dropEffect属性)为none,即不允许放下被拖拉的节点,所以如果允许在当前节点drop数据,通常会使用preventDefault方法,取消重置拖拉效果为none。

dragleave事件:拖拉离开当前节点范围时,在当前节点上触发,该事件的target属性是当前节点。在视觉上显示拖拉离开当前节点,就在这个事件的监听函数中设置。

drop事件:被拖拉的节点或选中的文本,释放到目标节点时,在目标节点上触发。注意,如果当前节点不允许drop,即使在该节点上方松开鼠标键,也不会触发该事件。如果用户按下Escape键,取消这个操作,也不会触发该事件。该事件的监听函数负责取出拖拉数据,并进行相关处理。

关于拖拉事件,有以下几点注意事项。

拖拉过程只触发以上这些拖拉事件,尽管鼠标在移动,但是鼠标事件不会触发。

将文件从操作系统拖拉进浏览器,不会触发dragStart和dragend事件。

dragenter和dragover事件的监听函数,用来指定可以放下(drop)拖拉的数据。由于网页的大部分区域不适合作为drop的目标节点,所以这两个事件的默认设置为当前节点不允许drop。如果想要在目标节点上drop拖拉的数据,首先必须阻止这两个事件的默认行为,或者取消这两个事件。

<div ondragover="return false"><div ondragover="event.preventDefault()">

上面代码中,如果不取消拖拉事件或者阻止默认行为,就不可能在div节点上drop被拖拉的节点。

拖拉事件用一个DragEvent对象表示,该对象继承MouseEvent对象,因此也就继承了UIEvent和Event对象。DragEvent对象只有一个独有的属性DataTransfer,其他都是继承的属性。DataTransfer属性用来读写拖拉事件中传输的数据,详见下文《DataTransfer对象》的部分。

下面的例子展示,如何动态改变被拖动节点的背景色。

div.addEventListener("dragstart", function(e) {

  this.style.backgroundColor = "red";}, false);div.addEventListener("dragend", function(e) {

  this.style.backgroundColor = "green";}, false);

上面代码中,div节点被拖动时,背景色会变为红色,拖动结束,又变回绿色。

下面是一个例子,显示如何实现将一个节点从当前父节点,拖拉到另一个父节点中。

// HTML代码为// <div class="dropzone">//    <div id="draggable" draggable="true">//       该节点可拖拉//    </div>// </div>// <div class="dropzone"></div>// <div class="dropzone"></div>// <div class="dropzone"></div>

// 被拖拉节点var dragged;

document.addEventListener("dragstart", function( event ) {

  // 保存被拖拉节点

  dragged = event.target;

  // 被拖拉节点的背景色变透明

  event.target.style.opacity = 0.5;

  // 兼容Firefox

  event.dataTransfer.setData('text/plain', 'anything');}, false);

document.addEventListener('dragend', function( event ) {

  // 被拖拉节点的背景色恢复正常

  event.target.style.opacity = '';}, false);

document.addEventListener('dragover', function( event ) {

  // 防止拖拉效果被重置,允许被拖拉的节点放入目标节点

  event.preventDefault();}, false);

document.addEventListener('dragenter', function( event ) {

  // 目标节点的背景色变紫色

  // 由于该事件会冒泡,所以要过滤节点

  if ( event.target.className == 'dropzone' ) {

    event.target.style.background = 'purple';

  }}, false);

document.addEventListener('dragleave', function( event ) {

  // 目标节点的背景色恢复原样

  if ( event.target.className == 'dropzone' ) {

    event.target.style.background = "";

  }}, false);

document.addEventListener('drop', function( event ) {

  // 防止事件默认行为(比如某些Elment节点上可以打开链接)

  event.preventDefault();

  if ( event.target.className === 'dropzone' ) {

    // 恢复目标节点背景色

    event.target.style.background = '';

    // 将被拖拉节点插入目标节点

    dragged.parentNode.removeChild( dragged );

    event.target.appendChild( dragged );

  }}, false);

DataTransfer对象概述

所有的拖拉事件都有一个dataTransfer属性,用来保存需要传递的数据。这个属性的值是一个DataTransfer对象。

拖拉的数据保存两方面的数据:数据的种类(又称格式)和数据的值。数据的种类是一个MIME字符串,比如 text/plain或者image/jpeg,数据的值是一个字符串。一般来说,如果拖拉一段文本,则数据默认就是那段文本;如果拖拉一个链接,则数据默认就是链接的URL。

当拖拉事件开始的时候,可以提供数据类型和数据值;在拖拉过程中,通过dragenter和dragover事件的监听函数,检查数据类型,以确定是否允许放下(drop)被拖拉的对象。比如,在只允许放下链接的区域,检查拖拉的数据类型是否为text/uri-list。

发生drop事件时,监听函数取出拖拉的数据,对其进行处理。

DataTransfer对象的属性

DataTransfer对象有以下属性。

1)dropEffect

dropEffect属性设置放下(drop)被拖拉节点时的效果,可能的值包括copy(复制被拖拉的节点)、move(移动被拖拉的节点)、link(创建指向被拖拉的节点的链接)、none(无法放下被拖拉的节点)。设置除此以外的值,都是无效的。

target.addEventListener('dragover', function(e) {

  e.preventDefault();

  e.stopPropagation();

  e.dataTransfer.dropEffect = 'copy';});

dropEffect属性一般在dragenter和dragover事件的监听函数中设置,对于dragstart、drag、dragleave这三个事件,该属性不起作用。进入目标节点后,拖拉行为会初始化成用户设定的效果,用户可以通过按下Shift键和Control键,改变初始设置,在copy、move、link三种效果中切换。

鼠标箭头会根据dropEffect属性改变形状,提示目前正处于哪一种效果。这意味着,通过鼠标就能判断是否可以在当前节点drop被拖拉的节点。

2)effectAllowed

effectAllowed属性设置本次拖拉中允许的效果,可能的值包括copy(复制被拖拉的节点)、move(移动被拖拉的节点)、link(创建指向被拖拉节点的链接)、copyLink(允许copy或link)、copyMove(允许copy或move)、linkMove(允许link或move)、all(允许所有效果)、none(无法放下被拖拉的节点)、uninitialized(默认值,等同于all)。如果某种效果是不允许的,用户就无法在目标节点中达成这种效果。

dragstart事件的监听函数,可以设置被拖拉节点允许的效果;dragenter和dragover事件的监听函数,可以设置目标节点允许的效果。

event.dataTransfer.effectAllowed = "copy";

dropEffect属性和effectAllowed属性,往往配合使用。

event.dataTransfer.effectAllowed = "copyMove";event.dataTransfer.dropEffect = "copy";

上面代码中,copy是指定的效果,但是可以通过Shift或Ctrl键(根据平台而定),将效果切换成move。

只要dropEffect属性和effectAllowed属性之中,有一个为none,就无法在目标节点上完成drop操作。

3)files

files属性是一个FileList对象,包含一组本地文件,可以用来在拖拉操作中传送。如果本次拖拉不涉及文件,则属性为空的FileList对象。

下面就是一个接收拖拉文件的例子。

// HTML代码为// <div id="output" style="min-height: 200px;border: 1px solid black;">//   文件拖拉到这里// </div>

var div = document.getElementById('output');

div.addEventListener("dragenter", function( event ) {

  div.textContent = '';

  event.stopPropagation();

  event.preventDefault();}, false);

div.addEventListener("dragover", function( event ) {

  event.stopPropagation();

  event.preventDefault();}, false);

div.addEventListener("drop", function( event ) {

  event.stopPropagation();

  event.preventDefault();

  var files = event.dataTransfer.files;

  for (var i = 0; i < files.length; i++) {

    div.textContent += files[i].name + ' ' + files[i].size + '字节\n';

  }}, false);

上面代码中,通过files属性读取拖拉文件的信息。如果想要读取文件内容,就要使用FileReader对象。

div.addEventListener('drop', function(e) {

  e.preventDefault();

  e.stopPropagation();

  var fileList = e.dataTransfer.files;

  if (fileList.length > 0) {

    var file = fileList[0];

    var reader = new FileReader();

    reader.onloadend = function(e) {

      if (e.target.readyState == FileReader.DONE) {

        var content = reader.result;

        contentDiv.innerHTML = "File: " + file.name + "\n\n" + content;

      }

    }

    reader.readAsBinaryString(file);

  }});

4)types

types属性是一个数组,保存每一次拖拉的数据格式,比如拖拉文件,则格式信息就为File。

下面是一个例子,通过检查dataTransfer属性的类型,决定是否允许在当前节点执行drop操作。

function contains(list, value){

  for( var i = 0; i < list.length; ++i ){

    if(list[i] === value) return true;

  }

  return false;}

function doDragOver(event){

  var isLink = contains( event.dataTransfer.types, "text/uri-list");

  if (isLink) event.preventDefault();}

上面代码中,只有当被拖拉的节点是一个链接时,才允许在当前节点放下。

DataTransfer对象的方法

DataTransfer对象有以下方法。

1)setData()

setData方法用来设置事件所带有的指定类型的数据。它接受两个参数,第一个是数据类型,第二个是具体数据。如果指定的类型在现有数据中不存在,则该类型将写入types属性;如果已经存在,在该类型的现有数据将被替换。

event.dataTransfer.setData("text/plain", "Text to drag");

上面代码为事件加入纯文本格式的数据。

如果拖拉文本框或者拖拉选中的文本,会默认将文本数据添加到dataTransfer属性,不用手动指定。

<div draggable="true" ondragstart="

  event.dataTransfer.setData('text/plain', 'bbb')">

  aaa</div>

上面代码中,拖拉数据实际上是bbb,而不是aaa。

下面是添加其他类型的数据。由于text/plain是最普遍支持的格式,为了保证兼容性,建议最后总是将数据保存一份纯文本的格式。

var dt = event.dataTransfer;

// 添加链接dt.setData("text/uri-list", "http://www.example.com");dt.setData("text/plain", "http://www.example.com");// 添加HTML代码dt.setData("text/html", "Hello there, <strong>stranger</strong>");dt.setData("text/plain", "Hello there, <strong>stranger</strong>");// 添加图像的URLdt.setData("text/uri-list", imageurl);dt.setData("text/plain", imageurl);

可以一次提供多种格式的数据。

var dt = event.dataTransfer;dt.setData("application/x-bookmark", bookmarkString);dt.setData("text/uri-list", "http://www.example.com");dt.setData("text/plain", "http://www.example.com");

上面代码中,通过在同一个事件上面,存放三种类型的数据,使得拖拉事件可以在不同的对象上面,drop不同的值。注意,第一种格式是一个自定义格式,浏览器默认无法读取,这意味着,只有某个部署了特定代码的节点,才可能drop(读取到)这个数据。

2)getData()

getData方法接受一个字符串(表示数据类型)作为参数,返回事件所带的指定类型的数据(通常是用setData方法添加的数据)。如果指定类型的数据不存在,则返回空字符串。通常只有drop事件触发后,才能取出数据。如果取出另一个域名存放的数据,将会报错。

下面是一个drop事件的监听函数,用来取出指定类型的数据。

function onDrop(event){

  var data = event.dataTransfer.getData("text/plain");

  event.target.textContent = data;

  event.preventDefault();}

上面代码取出拖拉事件的文本数据,将其替换成当前节点的文本内容。注意,这时还必须取消浏览器的默认行为,因为假如用户拖拉的是一个链接,浏览器默认会在当前窗口打开这个链接。

getData方法返回的是一个字符串,如果其中包含多项数据,就必须手动解析。

function doDrop(event){

  var lines = event.dataTransfer.getData("text/uri-list").split("\n");

  for (let line of lines) {

    let link = document.createElement("a");

    link.href = line;

    link.textContent = line;

    event.target.appendChild(link);

  }

  event.preventDefault();}

上面代码中,getData方法返回的是一组链接,就必须自行解析。

类型值指定为URL,可以取出第一个有效链接。

var link = event.dataTransfer.getData("URL");

下面是一次性取出多种类型的数据。

function doDrop(event){

  var types = event.dataTransfer.types;

  var supportedTypes = ["text/uri-list", "text/plain"];

  types = supportedTypes.filter(function (value) types.includes(value));

  if (types.length)

    var data = event.dataTransfer.getData(types[0]);

  event.preventDefault();}

3)clearData()

clearData方法接受一个字符串(表示数据类型)作为参数,删除事件所带的指定类型的数据。如果没有指定类型,则删除所有数据。如果指定类型不存在,则原数据不受影响。

event.dataTransfer.clearData("text/uri-list");

上面代码清除事件所带的URL数据。

4)setDragImage()

拖动过程中(dragstart事件触发后),浏览器会显示一张图片跟随鼠标一起移动,表示被拖动的节点。这张图片是自动创造的,通常显示为被拖动节点的外观,不需要自己动手设置。setDragImage方法可以用来自定义这张图片,它接受三个参数,第一个是img图片元素或者canvas元素,如果省略或为null则使用被拖动的节点的外观,第二个和第三个参数为鼠标相对于该图片左上角的横坐标和右坐标。

下面是一个例子。

// HTML代码为// <div id="drag-with-image" class="dragdemo" draggable="true">

     drag me// </div>

var div = document.getElementById("drag-with-image");div.addEventListener("dragstart", function(e) {

  var img = document.createElement("img");

  img.src = "http://path/to/img";

  e.dataTransfer.setDragImage(img, 0, 0);}, false);

触摸事件

触摸API由三个对象组成。

Touch

TouchList

TouchEvent

Touch对象表示触摸点(一根手指或者一根触摸笔),用来描述触摸动作,包括位置、大小、形状、压力、目标元素等属性。有时,触摸动作由多个触摸点(多根手指或者多根触摸笔)组成,多个触摸点的集合由TouchList对象表示。TouchEvent对象代表由触摸引发的事件,只有触摸屏才会引发这一类事件。

很多时候,触摸事件和鼠标事件同时触发,即使这个时候并没有用到鼠标。这是为了让那些只定义鼠标事件、没有定义触摸事件的代码,在触摸屏的情况下仍然能用。如果想避免这种情况,可以用preventDefault方法阻止发出鼠标事件。

Touch对象

Touch对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。它有以下属性。

1)identifier

identifier属性表示Touch实例的独一无二的识别符。它在整个触摸过程中保持不变。

var id = touchItem.identifier;

TouchList对象的identifiedTouch方法,可以根据这个属性,从一个集合里面取出对应的Touch对象。

2)screenX,screenY,clientX,clientY,pageX,pageY

screenX属性和screenY属性,分别表示触摸点相对于屏幕左上角的横坐标和纵坐标,与页面是否滚动无关。

clientX属性和clientY属性,分别表示触摸点相对于浏览器视口左上角的横坐标和纵坐标,与页面是否滚动无关。

pageX属性和pageY属性,分别表示触摸点相对于当前页面左上角的横坐标和纵坐标,包含了页面滚动带来的位移。

3)radiusX,radiusY,rotationAngle

radiusX属性和radiusY属性,分别返回触摸点周围受到影响的椭圆范围的X轴和Y轴,单位为像素。

rotationAngle属性表示触摸区域的椭圆的旋转角度,单位为度数,在0到90度之间。

上面这三个属性共同定义了用户与屏幕接触的区域,对于描述手指这一类非精确的触摸,很有帮助。指尖接触屏幕,触摸范围会形成一个椭圆,这三个属性就用来描述这个椭圆区域。

4)force

force属性返回一个0到1之间的数值,表示触摸压力。0代表没有压力,1代表硬件所能识别的最大压力。

5)target

target属性返回一个Element节点,代表触摸发生的那个节点。

TouchList对象

TouchList对象是一个类似数组的对象,成员是与某个触摸事件相关的所有触摸点。比如,用户用三根手指触摸,产生的TouchList对象就有三个成员,每根手指对应一个Touch对象。

TouchList实例的length属性,返回TouchList对象的成员数量。

TouchList实例的identifiedTouch方法和item方法,分别使用id属性和索引值(从0开始)作为参数,取出指定的Touch对象。

TouchEvent对象

TouchEvent对象继承Event对象和UIEvent对象,表示触摸引发的事件。除了被继承的属性以外,它还有一些自己的属性。

1)键盘相关属性

以下属性都为只读属性,返回一个布尔值,表示触摸的同时,是否按下某个键。

altKey 是否按下alt键

ctrlKey 是否按下ctrl键

metaKey 是否按下meta键

shiftKey 是否按下shift键

2)changedTouches

changedTouches属性返回一个TouchList对象,包含了由当前触摸事件引发的所有Touch对象(即相关的触摸点)。

对于touchstart事件,它代表被激活的触摸点;对于touchmove事件,代表发生变化的触摸点;对于touchend事件,代表消失的触摸点(即不再被触碰的点)。

var touches = touchEvent.changedTouches;

3)targetTouches

targetTouches属性返回一个TouchList对象,包含了触摸的目标Element节点内部,所有仍然处于活动状态的触摸点。

var touches = touchEvent.targetTouches;

4)touches

touches属性返回一个TouchList对象,包含了所有仍然处于活动状态的触摸点。

var touches = touchEvent.touches;

触摸事件的种类

触摸引发的事件,有以下几类。可以通过TouchEvent.type属性,查看到底发生的是哪一种事件。

touchstart:用户接触触摸屏时触发,它的target属性返回发生触摸的Element节点。

touchend:用户不再接触触摸屏时(或者移出屏幕边缘时)触发,它的target属性与touchstart事件的target属性是一致的,它的changedTouches属性返回一个TouchList对象,包含所有不再触摸的触摸点(Touch对象)。

touchmove:用户移动触摸点时触发,它的target属性与touchstart事件的target属性一致。如果触摸的半径、角度、力度发生变化,也会触发该事件。

touchcancel:触摸点取消时触发,比如在触摸区域跳出一个情态窗口(modal window)、触摸点离开了文档区域(进入浏览器菜单栏区域)、用户放置更多的触摸点(自动取消早先的触摸点)。

下面是一个例子。

var el = document.getElementsByTagName("canvas")[0];el.addEventListener("touchstart", handleStart, false);el.addEventListener("touchmove", handleMove, false);

function handleStart(evt) {

  // 阻止浏览器继续处理触摸事件,

  // 也阻止发出鼠标事件

  evt.preventDefault();

  var touches = evt.changedTouches;

 

  for (var i = 0; i < touches.length; i++) {

    console.log(touches[i].pageX, touches[i].pageY);

  }}

function handleMove(evt) {

  evt.preventDefault();

  var touches = evt.changedTouches;

  for (var i = 0; i < touches.length; i++) {

    var id = touches[i].identifier;

    var touch = touches.identifiedTouch(id);

    console.log(touch.pageX, touch.pageY);

  }}

表单事件

Input事件,select事件,change事件

以下事件与表单成员的值变化有关。

1)input事件

input事件当<input>、<textarea>的值发生变化时触发。此外,打开contenteditable属性的元素,只要值发生变化,也会触发input事件。

input事件的一个特点,就是会连续触发,比如用户每次按下一次按键,就会触发一次input事件。

2)select事件

select事件当在<input>、<textarea>中选中文本时触发。

// HTML代码为// <input id="test" type="text" value="Select me!" />

var elem = document.getElementById('test');elem.addEventListener('select', function() {

  console.log('Selection changed!');}, false);

3)Change事件

Change事件当<input>、<select>、<textarea>的值发生变化时触发。它与input事件的最大不同,就是不会连续触发,只有当全部修改完成时才会触发,而且input事件必然会引发change事件。具体来说,分成以下几种情况。

激活单选框(radio)或复选框(checkbox)时触发。

用户提交时触发。比如,从下列列表(select)完成选择,在日期或文件输入框完成选择。

当文本框或textarea元素的值发生改变,并且丧失焦点时触发。

下面是一个例子。

// HTML代码为// <select size="1" onchange="changeEventHandler(event);">//   <option>chocolate</option>//   <option>strawberry</option>//   <option>vanilla</option>// </select>

function changeEventHandler(event) {

  console.log('You like ' + event.target.value + ' ice cream.');}

reset事件,submit事件

以下事件发生在表单对象上,而不是发生在表单的成员上。

1)reset事件

reset事件当表单重置(所有表单成员变回默认值)时触发。

2)submit事件

submit事件当表单数据向服务器提交时触发。注意,submit事件的发生对象是form元素,而不是button元素(即使它的类型是submit),因为提交的是表单,而不是按钮。

文档事件

beforeunload事件,unload事件,load事件,error事件,pageshow事件,pagehide事件

以下事件与网页的加载与卸载相关。

1)beforeunload事件

beforeunload事件在窗口将要关闭,或者网页(即document对象)将要卸载时触发。它可以用来防止用户不小心关闭网页。

根据标准,只要在该事件的回调函数中,调用了event.preventDefault(),或者event.returnValue属性的值是一个非空的值,就会自动跳出一个确认框,让用户确认是否关闭网页。如果用户点击“取消”按钮,网页就不会关闭。event.returnValue属性的值,会显示在确认对话框之中。

window.addEventListener('beforeunload', function( event ) {

  event.returnValue = '你确认要离开吗?';});

window.addEventListener('beforeunload', function( event ) {

  event.preventDefault();});

但是,浏览器的行为很不一致,Chrome就不遵守event.preventDefault(),还是会关闭窗口,而IE需要显式返回一个非空的字符串。而且,大多数浏览器在对话框中不显示指定文本,只显示默认文本。因此,可以采用下面的写法,取得最大的兼容性。

window.addEventListener('beforeunload', function (e) {

  var confirmationMessage = '确认关闭窗口?';

  e.returnValue = confirmationMessage;

  return confirmationMessage;});

需要特别注意的是,许多手机浏览器默认忽视这个事件,而桌面浏览器也可以这样设置,所以这个事件有可能根本不生效。所以,不能依赖它来阻止用户关闭窗口。

2)unload事件

unload事件在窗口关闭或者document对象将要卸载时触发,发生在window、body、frameset等对象上面。它的触发顺序排在beforeunload、pagehide事件后面。unload事件只在页面没有被浏览器缓存时才会触发,换言之,如果通过按下“前进/后退”导致页面卸载,并不会触发unload事件。

unload事件发生时,document对象处于一个特殊状态。所有资源依然存在,但是对用户来说都不可见,UI互动(window.open、alert、confirm方法等)全部无效。这时即使抛出错误,也不能停止文档的卸载。

window.addEventListener('unload', function(event) {

  console.log('文档将要卸载');});

如果在window对象上定义了该事件,网页就不会被浏览器缓存。

3)load事件,error事件

load事件在页面加载成功时触发,error事件在页面加载失败时触发。注意,页面从浏览器缓存加载,并不会触发load事件。

这两个事件实际上属于进度事件,不仅发生在document对象,还发生在各种外部资源上面。浏览网页就是一个加载各种资源的过程,图像(image)、样式表(style sheet)、脚本(script)、视频(video)、音频(audio)、Ajax请求(XMLHttpRequest)等等。这些资源和document对象、window对象、XMLHttpRequestUpload对象,都会触发load事件和error事件。

4)pageshow事件,pagehide事件

默认情况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进/后退”按钮时,浏览器就会从缓存中加载页面。

pageshow事件在页面加载时触发,包括第一次加载和从缓存加载两种情况。如果要指定页面每次加载(不管是不是从浏览器缓存)时都运行的代码,可以放在这个事件的监听函数。

第一次加载时,它的触发顺序排在load事件后面。从缓存加载时,load事件不会触发,因为网页在缓存中的样子通常是load事件的监听函数运行后的样子,所以不必重复执行。同理,如果是从缓存中加载页面,网页内初始化的JavaScript脚本(比如DOMContentLoaded事件的监听函数)也不会执行。

window.addEventListener('pageshow', function(event) {

  console.log('pageshow: ', event);});

pageshow事件有一个persisted属性,返回一个布尔值。页面第一次加载时,这个属性是false;当页面从缓存加载时,这个属性是true。

window.addEventListener('pageshow', function(event){

  if (event.persisted) {

    // ...

  }});

pagehide事件与pageshow事件类似,当用户通过“前进/后退”按钮,离开当前页面时触发。它与unload事件的区别在于,如果在window对象上定义unload事件的监听函数之后,页面不会保存在缓存中,而使用pagehide事件,页面会保存在缓存中。

pagehide事件的event对象有一个persisted属性,将这个属性设为true,就表示页面要保存在缓存中;设为false,表示网页不保存在缓存中,这时如果设置了unload事件的监听函数,该函数将在pagehide事件后立即运行。

如果页面包含frame或iframe元素,则frame页面的pageshow事件和pagehide事件,都会在主页面之前触发。

DOMContentLoaded事件,readystatechange事件

以下事件与文档状态相关。

1)DOMContentLoaded事件

HTML文档下载并解析完成以后,就会在document对象上触发DOMContentLoaded事件。这时,仅仅完成了HTML文档的解析(整张页面的DOM生成),所有外部资源(样式表、脚本、iframe等等)可能还没有下载结束。也就是说,这个事件比load事件,发生时间早得多。

document.addEventListener("DOMContentLoaded", function(event) {

  console.log("DOM生成");});

注意,网页的JavaScript脚本是同步执行的,所以定义DOMContentLoaded事件的监听函数,应该放在所有脚本的最前面。否则脚本一旦发生堵塞,将推迟触发DOMContentLoaded事件。

2)readystatechange事件

readystatechange事件发生在Document对象和XMLHttpRequest对象,当它们的readyState属性发生变化时触发。

document.onreadystatechange = function () {

  if (document.readyState == "interactive") {

    // ...

  }}

IE8不支持DOMContentLoaded事件,但是支持这个事件。因此,可以使用readystatechange事件,在低版本的IE中代替DOMContentLoaded事件。

scroll事件,resize事件

以下事件与窗口行为有关。

1)scroll事件

scroll事件在文档或文档元素滚动时触发,主要出现在用户拖动滚动条。

window.addEventListener('scroll', callback);

由于该事件会连续地大量触发,所以它的监听函数之中不应该有非常耗费计算的操作。推荐的做法是使用requestAnimationFramesetTimeout控制该事件的触发频率,然后可以结合customEvent抛出一个新事件。

(function() {

  var throttle = function(type, name, obj) {

    var obj = obj || window;

    var running = false;

    var func = function() {

      if (running) { return; }

      running = true;

      requestAnimationFrame(function() {

        obj.dispatchEvent(new CustomEvent(name));

        running = false;

      });

    };

    obj.addEventListener(type, func);

  };

 

  // 将scroll事件重定义为optimizedScroll事件

  throttle('scroll', 'optimizedScroll');})();

window.addEventListener('optimizedScroll', function() {

  console.log("Resource conscious scroll callback!");});

上面代码中,throttle函数用于控制事件触发频率,requestAnimationFrame方法保证每次页面重绘(每秒60次),只会触发一次scroll事件的监听函数。也就是说,上面方法将scroll事件的触发频率,限制在每秒60次。

改用setTimeout方法,可以放置更大的时间间隔。

(function() {

  window.addEventListener('scroll', scrollThrottler, false);

  var scrollTimeout;

  function scrollThrottler() {

    if (!scrollTimeout) {

      scrollTimeout = setTimeout(function() {

        scrollTimeout = null;

        actualScrollHandler();

      }, 66);

    }

  }

  function actualScrollHandler() {

    // ...

  }}());

上面代码中,setTimeout指定scroll事件监听函数,每66毫秒触发一次(每秒15次)。

下面是一个更一般的throttle函数的写法。

function throttle(fn, wait) {

  var time = Date.now();

  return function() {

    if ((time + wait - Date.now()) < 0) {

      fn();

      time = Date.now();

    }

  }}

window.addEventListener('scroll', throttle(callback, 1000));

上面的代码将scroll事件的触发频率,限制在一秒一次。

lodash函数库提供了现成的throttle函数,可以直接引用。

window.addEventListener('scroll', _.throttle(callback, 1000));

2)resize事件

resize事件在改变浏览器窗口大小时触发,发生在window、body、frameset对象上面。

var resizeMethod = function(){

  if (document.body.clientWidth < 768) {

    console.log('移动设备');

  }};

window.addEventListener("resize", resizeMethod, true);

该事件也会连续地大量触发,所以最好像上面的scroll事件一样,通过throttle函数控制事件触发频率。

hashchange事件,popstate事件

以下事件与文档的URL变化相关。

1)hashchange事件

hashchange事件在URL的hash部分(即#号后面的部分,包括#号)发生变化时触发。如果老式浏览器不支持该属性,可以通过定期检查location.hash属性,模拟该事件,下面就是代码。

(function(window) {

  if ( "onhashchange" in window.document.body ) { return; }

  var location = window.location;

  var oldURL = location.href;

  var oldHash = location.hash;

  // 每隔100毫秒检查一下URL的hash

  setInterval(function() {

    var newURL = location.href;

   var newHash = location.hash;

    if ( newHash != oldHash && typeof window.onhashchange === "function" ) {

      window.onhashchange({

        type: "hashchange",

        oldURL: oldURL,

        newURL: newURL

      });

      oldURL = newURL;

      oldHash = newHash;

    }

  }, 100);

})(window);

hashchange事件对象除了继承Event对象,还有oldURL属性和newURL属性,分别表示变化前后的URL。

2)popstate事件

popstate事件在浏览器的history对象的当前记录发生显式切换时触发。注意,调用history.pushState()或history.replaceState(),并不会触发popstate事件。该事件只在用户在history记录之间显式切换时触发,比如鼠标点击“后退/前进”按钮,或者在脚本中调用history.back()、history.forward()、history.go()时触发。

该事件对象有一个state属性,保存history.pushState方法和history.replaceState方法为当前记录添加的state对象。

window.onpopstate = function(event) {

  console.log("state: " + event.state);};history.pushState({page: 1}, "title 1", "?page=1");history.pushState({page: 2}, "title 2", "?page=2");history.replaceState({page: 3}, "title 3", "?page=3");history.back(); // state: {"page":1}history.back(); // state: nullhistory.go(2);  // state: {"page":3}

上面代码中,pushState方法向history添加了两条记录,然后replaceState方法替换掉当前记录。因此,连续两次back方法,会让当前条目退回到原始网址,它没有附带state对象,所以事件的state属性为null,然后前进两条记录,又回到replaceState方法添加的记录。

浏览器对于页面首次加载,是否触发popstate事件,处理不一样,Firefox不触发该事件。

cut事件,copy事件,paste事件

以下三个事件属于文本操作触发的事件。

cut事件:在将选中的内容从文档中移除,加入剪贴板后触发。

copy事件:在选中的内容加入剪贴板后触发

paste事件:在剪贴板内容被粘贴到文档后触发。

这三个事件都有一个clipboardData只读属性。该属性存放剪贴的数据,是一个DataTransfer对象,具体的API接口和操作方法,请参见《触摸事件》的DataTransfer对象章节。

焦点事件

焦点事件发生在Element节点和document对象上面,与获得或失去焦点相关。它主要包括以下四个事件。

focus事件:Element节点获得焦点后触发,该事件不会冒泡。

blur事件:Element节点失去焦点后触发,该事件不会冒泡。

focusin事件:Element节点将要获得焦点时触发,发生在focus事件之前。该事件会冒泡。Firefox不支持该事件。

focusout事件:Element节点将要失去焦点时触发,发生在blur事件之前。该事件会冒泡。Firefox不支持该事件。

这四个事件的事件对象,带有target属性(返回事件的目标节点)和relatedTarget属性(返回一个Element节点)。对于focusin事件,relatedTarget属性表示失去焦点的节点;对于focusout事件,表示将要接受焦点的节点;对于focus和blur事件,该属性返回null。

由于focus和blur事件不会冒泡,只能在捕获阶段触发,所以addEventListener方法的第三个参数需要设为true。

form.addEventListener("focus", function( event ) {

  event.target.style.background = "pink";}, true);form.addEventListener("blur", function( event ) {

  event.target.style.background = "";}, true);

上面代码设置表单的文本输入框,在接受焦点时设置背景色,在失去焦点时去除背景色。

浏览器提供一个FocusEvent构造函数,可以用它生成焦点事件的实例。

var focusEvent = new FocusEvent(typeArg, focusEventInit);

上面代码中,FocusEvent构造函数的第一个参数为事件类型,第二个参数是可选的配置对象,用来配置FocusEvent对象。

自定义事件和事件模拟

除了浏览器预定义的那些事件,用户还可以自定义事件,然后手动触发。

// 新建事件实例var event = new Event('build');

// 添加监听函数elem.addEventListener('build', function (e) { ... }, false);

// 触发事件elem.dispatchEvent(event);

上面代码触发了自定义事件,该事件会层层向上冒泡。在冒泡过程中,如果有一个元素定义了该事件的监听函数,该监听函数就会触发。

由于IE不支持这个API,如果在IE中自定义事件,需要使用后文的“老式方法”。

CustomEvent()

Event构造函数只能指定事件名,不能在事件上绑定数据。如果需要在触发事件的同时,传入指定的数据,需要使用CustomEvent构造函数生成自定义的事件对象。

var event = new CustomEvent('build', { 'detail': 'hello' });function eventHandler(e) {

  console.log(e.detail);}

上面代码中,CustomEvent构造函数的第一个参数是事件名称,第二个参数是一个对象,该对象的detail属性会绑定在事件对象之上。

下面是另一个例子。

var myEvent = new CustomEvent("myevent", {

  detail: {

    foo: "bar"

  },

  bubbles: true,

  cancelable: false});

el.addEventListener('myevent', function(event) {

  console.log('Hello ' + event.detail.foo);});

el.dispatchEvent(myEvent);

IE不支持这个方法,可以用下面的垫片函数模拟。

(function () {

  function CustomEvent ( event, params ) {

    params = params || { bubbles: false, cancelable: false, detail: undefined };

    var evt = document.createEvent( 'CustomEvent' );

    evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );

    return evt;

   }

  CustomEvent.prototype = window.Event.prototype;

  window.CustomEvent = CustomEvent;})();

事件的模拟

有时,需要在脚本中模拟触发某种类型的事件,这时就必须使用这种事件的构造函数。

下面是一个通过MouseEvent构造函数,模拟触发click鼠标事件的例子。

function simulateClick() {

  var event = new MouseEvent('click', {

    'bubbles': true,

    'cancelable': true

  });

  var cb = document.getElementById('checkbox');

  cb.dispatchEvent(event);}

自定义事件的老式写法

老式浏览器不一定支持各种类型事件的构造函数。因此,有时为了兼容,会用到一些非标准的方法。这些方法未来会被逐步淘汰,但是目前浏览器还广泛支持。除非是为了兼容老式浏览器,尽量不要使用。

1)document.createEvent()

document.createEvent方法用来新建指定类型的事件。它所生成的Event实例,可以传入dispatchEvent方法。

// 新建Event实例var event = document.createEvent('Event');

// 事件的初始化event.initEvent('build', true, true);

// 加上监听函数document.addEventListener('build', doSomething, false);

// 触发事件document.dispatchEvent(event);

createEvent方法接受一个字符串作为参数,可能的值参见下表“数据类型”一栏。使用了某一种“事件类型”,就必须使用对应的事件初始化方法。

事件类型

事件初始化方法

UIEvents

event.initUIEvent

MouseEvents

event.initMouseEvent

MutationEvents

event.initMutationEvent

HTMLEvents

event.initEvent

Event

event.initEvent

CustomEvent

event.initCustomEvent

KeyboardEvent

event.initKeyEvent

2)event.initEvent()

事件对象的initEvent方法,用来初始化事件对象,还能向事件对象添加属性。该方法的参数必须是一个使用Document.createEvent()生成的Event实例,而且必须在dispatchEvent方法之前调用。

var event = document.createEvent('Event');event.initEvent('my-custom-event', true, true, {foo:'bar'});someElement.dispatchEvent(event);

initEvent方法可以接受四个参数。

type:事件名称,格式为字符串。

bubbles:事件是否应该冒泡,格式为布尔值。可以使用event.bubbles属性读取它的值。

cancelable:事件是否能被取消,格式为布尔值。可以使用event.cancelable属性读取它的值。

option:为事件对象指定额外的属性。

事件模拟的老式写法

事件模拟的非标准做法是,对document.createEvent方法生成的事件对象,使用对应的事件初始化方法进行初始化。比如,click事件对象属于MouseEvent对象,也属于UIEvent对象,因此要用initMouseEvent方法或initUIEvent方法进行初始化。

1)event.initMouseEvent()

initMouseEvent方法用来初始化Document.createEvent方法新建的鼠标事件。该方法必须在事件新建(document.createEvent方法)之后、触发(dispatchEvent方法)之前调用。

initMouseEvent方法有很长的参数。

event.initMouseEvent(type, canBubble, cancelable, view,

  detail, screenX, screenY, clientX, clientY,

  ctrlKey, altKey, shiftKey, metaKey,

  button, relatedTarget);

上面这些参数的含义,参见MouseEvent构造函数的部分。

模仿并触发click事件的写法如下。

var simulateDivClick = document.createEvent('MouseEvents');

simulateDivClick.initMouseEvent('click',true,true,

  document.defaultView,0,0,0,0,0,false,

  false,false,0,null,null);

divElement.dispatchEvent(simulateDivClick);

2)UIEvent.initUIEvent()

UIEvent.initUIEvent()用来初始化一个UI事件。该方法必须在事件新建(document.createEvent方法)之后、触发(dispatchEvent方法)之前调用。

event.initUIEvent(type, canBubble, cancelable, view, detail)

该方法的参数含义,可以参见MouseEvent构造函数的部分。其中,detail参数是一个数值,含义与事件类型有关,对于鼠标事件,这个值表示鼠标按键在某个位置按下的次数。

鼠标事件

事件种类

鼠标事件指与鼠标相关的事件,主要有以下一些。

1)click事件

click事件当用户在Element节点、document节点、window对象上,单击鼠标(或者按下回车键)时触发。

“鼠标单击”定义为,用户在同一个位置完成一次mousedown动作和mouseup动作。它们的触发顺序是:mousedown首先触发,mouseup接着触发,click最后触发。

下面是一个设置click事件监听函数的例子。

div.addEventListener("click", function( event ) {

  // 显示在该节点,鼠标连续点击的次数

  event.target.innerHTML = "click count: " + event.detail;}, false);

下面的代码是利用click事件进行CSRF攻击(Cross-site request forgery)的一个例子。

<a href="http://www.harmless.com/" onclick="

  var f = document.createElement('form');

  f.style.display = 'none';

  this.parentNode.appendChild(f);

  f.method = 'POST';

  f.action = 'http://www.example.com/account/destroy';

  f.submit();

  return false;">伪装的链接</a>

2)dblclick事件

dblclick事件当用户在elementdocumentwindow对象上,双击鼠标时触发。该事件会在mousedownmouseupclick之后触发。

3)mouseup事件,mousedown事件

mouseup事件在释放按下的鼠标键时触发。

mousedown事件在按下鼠标键时触发。

4)mousemove事件

mousemove事件当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次代码。

5)mouseover事件,mouseenter事件

mouseover事件和mouseenter事件,都是鼠标进入一个节点时触发。

两者的区别是,mouseenter事件只触发一次,而只要鼠标在节点内部移动,mouseover事件会在子节点上触发多次。

// HTML代码为// <ul id="test">//   <li>item 1</li>//   <li>item 2</li>//   <li>item 3</li>// </ul>

var test = document.getElementById('test');

// 进入test节点以后,该事件只会触发一次// event.target 是 ul 节点test.addEventListener('mouseenter', function (event) {

  event.target.style.color = 'purple';

  setTimeout(function () {

    event.target.style.color = '';

  }, 500);}, false);

// 进入test节点以后,只要在子Element节点上移动,该事件会触发多次// event.target 是 li 节点test.addEventListener('mouseover', function (event) {

  event.target.style.color = 'orange';

  setTimeout(function () {

    event.target.style.color = '';

  }, 500);}, false);

6)mouseout事件,mouseleave事件

mouseout事件和mouseleave事件,都是鼠标离开一个节点时触发。

两者的区别是,mouseout事件会冒泡,mouseleave事件不会。子节点的mouseout事件会冒泡到父节点,进而触发父节点的mouseout事件。mouseleave事件就没有这种效果,所以离开子节点时,不会触发父节点的监听函数。

7)contextmenu

contextmenu事件在一个节点上点击鼠标右键时触发,或者按下“上下文菜单”键时触发。

MouseEvent对象

鼠标事件使用MouseEvent对象表示,它继承UIEvent对象和Event对象。浏览器提供一个MouseEvent构造函数,用于新建一个MouseEvent实例。

event = new MouseEvent(typeArg, mouseEventInit);

MouseEvent构造函数的第一个参数是事件名称(可能的值包括click、mousedown、mouseup、mouseover、mousemove、mouseout),第二个参数是一个事件初始化对象。该对象可以配置以下属性。

· screenX,设置鼠标相对于屏幕的水平坐标(但不会移动鼠标),默认为0,等同于MouseEvent.screenX属性。

· screenY,设置鼠标相对于屏幕的垂直坐标,默认为0,等同于MouseEvent.screenY属性。

· clientX,设置鼠标相对于窗口的水平坐标,默认为0,等同于MouseEvent.clientX属性。

· clientY,设置鼠标相对于窗口的垂直坐标,默认为0,等同于MouseEvent.clientY属性。

· ctrlKey,设置是否按下ctrl键,默认为false,等同于MouseEvent.ctrlKey属性。

· shiftKey,设置是否按下shift键,默认为false,等同于MouseEvent.shiftKey属性。

· altKey,设置是否按下alt键,默认为false,等同于MouseEvent.altKey属性。

· metaKey,设置是否按下meta键,默认为false,等同于MouseEvent.metaKey属性。

· button,设置按下了哪一个鼠标按键,默认为0。-1表示没有按键,0表示按下主键(通常是左键),1表示按下辅助键(通常是中间的键),2表示按下次要键(通常是右键)。

· buttons,设置按下了鼠标哪些键,是一个3个比特位的二进制值,默认为0。1表示按下主键(通常是左键),2表示按下次要键(通常是右键),4表示按下辅助键(通常是中间的键)。

· relatedTarget,设置一个Element节点,在mouseenter和mouseover事件时,表示鼠标刚刚离开的那个Element节点,在mouseout和mouseleave事件时,表示鼠标正在进入的那个Element节点。默认为null,等同于MouseEvent.relatedTarget属性。

以下属性也是可配置的,都继承自UIEvent构造函数和Event构造函数。

bubbles,布尔值,设置事件是否冒泡,默认为false,等同于Event.bubbles属性。

cancelable,布尔值,设置事件是否可取消,默认为false,等同于Event.cancelable属性。

view,设置事件的视图,一般是window或document.defaultView,等同于Event.view属性。

detail,设置鼠标点击的次数,等同于Event.detail属性。

下面是一个例子。

function simulateClick() {

  var event = new MouseEvent('click', {

    'bubbles': true,

    'cancelable': true

  });

  var cb = document.getElementById('checkbox');

  cb.dispatchEvent(event);}

上面代码生成一个鼠标点击事件,并触发该事件。

以下介绍MouseEvent实例的属性。

altKey,ctrlKey,metaKey,shiftKey

以下属性返回一个布尔值,表示鼠标事件发生时,是否按下某个键。

altKey属性:alt键

ctrlKey属性:key键

metaKey属性:Meta键(Mac键盘是一个四瓣的小花,Windows键盘是Windows键)

shiftKey属性:Shift键

// HTML代码为// <body onclick="showkey(event);">

function showKey(e){

  console.log("ALT key pressed: " + e.altKey);

  console.log("CTRL key pressed: " + e.ctrlKey);

  console.log("META key pressed: " + e.metaKey);

  console.log("SHIFT key pressed: " + e.shiftKey);}

上面代码中,点击网页会输出是否同时按下Alt键。

button,buttons

以下属性返回事件的鼠标键信息。

1)button

button属性返回一个数值,表示按下了鼠标哪个键。

-1:没有按下键。

0:按下主键(通常是左键)。

1:按下辅助键(通常是中键或者滚轮键)。

2:按下次键(通常是右键)。

// HTML代码为// <button onmouseup="whichButton(event);">点击</button>

var whichButton = function (e) {

  switch (e.button) {

    case 0:

      console.log('Left button clicked.');

      break;

    case 1:

      console.log('Middle button clicked.');

      break;

    case 2:

      console.log('Right button clicked.');

      break;

    default:

      console.log('Unexpected code: ' + e.button);

  }}

2)buttons

buttons属性返回一个3个比特位的值,表示同时按下了哪些键。它用来处理同时按下多个鼠标键的情况。

1:二进制为001,表示按下左键。

2:二进制为010,表示按下右键。

4:二进制为100,表示按下中键或滚轮键。

同时按下多个键的时候,每个按下的键对应的比特位都会有值。比如,同时按下左键和右键,会返回3(二进制为011)。

clientX,clientY,movementX,movementY,screenX,screenY

以下属性与事件的位置相关。

1)clientX,clientY

clientX属性返回鼠标位置相对于浏览器窗口左上角的水平坐标,单位为像素,与页面是否横向滚动无关。

clientY属性返回鼠标位置相对于浏览器窗口左上角的垂直坐标,单位为像素,与页面是否纵向滚动无关。

// HTML代码为// <body onmousedown="showCoords(event)">

function showCoords(evt){

  console.log(

    "clientX value: " + evt.clientX + "\n" +

    "clientY value: " + evt.clientY + "\n"

  );}

2)movementX,movementY

movementX属性返回一个水平位移,单位为像素,表示当前位置与上一个mousemove事件之间的水平距离。在数值上,等于currentEvent.movementX = currentEvent.screenX - previousEvent.screenX。

movementY属性返回一个垂直位移,单位为像素,表示当前位置与上一个mousemove事件之间的垂直距离。在数值上,等于currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。

3)screenX,screenY

screenX属性返回鼠标位置相对于屏幕左上角的水平坐标,单位为像素。

screenY属性返回鼠标位置相对于屏幕左上角的垂直坐标,单位为像素。

// HTML代码为// <body onmousedown="showCoords(event)">

function showCoords(evt){

  console.log(

    "screenX value: " + evt.screenX + "\n"

    + "screenY value: " + evt.screenY + "\n"

  );}

relatedTarget

relatedTarget属性返回事件的次要相关节点。对于那些没有次要相关节点的事件,该属性返回null。

下表列出不同事件的target属性和relatedTarget属性含义。

事件名称

target属性

relatedTarget属性

focusin

接受焦点的节点

丧失焦点的节点

focusout

丧失焦点的节点

接受焦点的节点

mouseenter

将要进入的节点

将要离开的节点

mouseleave

将要离开的节点

将要进入的节点

mouseout

将要离开的节点

将要进入的节点

mouseover

将要进入的节点

将要离开的节点

dragenter

将要进入的节点

将要离开的节点

dragexit

将要离开的节点

将要进入的节点

下面是一个例子。

// HTML代码为// <div id="outer" style="height:50px;width:50px;border-width:1px solid black;">//   <div id="inner" style="height:25px;width:25px;border:1px solid black;"></div>// </div>

var inner = document.getElementById("inner");

inner.addEventListener("mouseover", function (){

  console.log('进入' + event.target.id + " 离开" + event.relatedTarget.id);});inner.addEventListener("mouseenter", function (){

  console.log('进入' + event.target.id + " 离开" + event.relatedTarget.id);});inner.addEventListener("mouseout", function (){

  console.log('离开' + event.target.id + " 进入" + event.relatedTarget.id);});inner.addEventListener("mouseleave", function (){

  console.log('离开' + event.target.id + " 进入" + event.relatedTarget.id);});

// 鼠标从outer进入inner,输出// 进入inner 离开outer// 进入inner 离开outer

// 鼠标从inner进入outer,输出// 离开inner 进入outer// 离开inner 进入outer

wheel事件

wheel事件是与鼠标滚轮相关的事件,目前只有一个wheel事件。用户滚动鼠标的滚轮,就触发这个事件。

该事件除了继承了MouseEvent、UIEvent、Event的属性,还有几个自己的属性。

deltaX:返回一个数值,表示滚轮的水平滚动量。

deltaY:返回一个数值,表示滚轮的垂直滚动量。

deltaZ:返回一个数值,表示滚轮的Z轴滚动量。

deltaMode:返回一个数值,表示滚动的单位,适用于上面三个属性。0表示像素,1表示行,2表示页。

浏览器提供一个WheelEvent构造函数,可以用来生成滚轮事件的实例。它接受两个参数,第一个是事件名称,第二个是配置对象。

var syntheticEvent = new WheelEvent("syntheticWheel", {"deltaX": 4, "deltaMode": 0});

 

JavaScript之键盘事件

键盘事件

键盘事件用来描述键盘行为,主要有keydown、keypress、keyup三个事件。

keydown:按下键盘时触发该事件。

keypress:只要按下的键并非Ctrl、Alt、Shift和Meta,就接着触发keypress事件。

keyup:松开键盘时触发该事件。

下面是一个例子,对文本框设置keypress监听函数,只允许输入数字。

// HTML代码为// <input type="text"//   name="myInput"//   onkeypress="return numbersOnly(this, event);"//   onpaste="return false;"// />

function numbersOnly(oToCheckField, oKeyEvent) {

  return oKeyEvent.charCode === 0

    || /\d/.test(String.fromCharCode(oKeyEvent.charCode));}

如果用户一直按键不松开,就会连续触发键盘事件,触发的顺序如下。

1. keydown

2. keypress

3. keydown

4. keypress

5. (重复以上过程)

6. keyup

键盘事件使用KeyboardEvent对象表示,该对象继承了UIEvent和MouseEvent对象。浏览器提供KeyboardEvent构造函数,用来新建键盘事件的实例。

event = new KeyboardEvent(typeArg, KeyboardEventInit);

KeyboardEvent构造函数的第一个参数是一个字符串,表示事件类型,第二个参数是一个事件配置对象,可配置以下字段。

key,对应KeyboardEvent.key属性,默认为空字符串。

ctrlKey,对应KeyboardEvent.ctrlKey属性,默认为false。

shiftKey,对应KeyboardEvent.shiftKey属性,默认为false。

altKey,对应KeyboardEvent.altKey属性,默认为false。

metaKey,对应KeyboardEvent.metaKey属性,默认为false。

下面就是KeyboardEvent实例的属性介绍。

altKey,ctrlKey,metaKey,shiftKey

以下属性返回一个布尔值,表示是否按下对应的键。

altKey:alt键

ctrlKey:ctrl键

metaKey:meta键(mac系统是一个四瓣的小花,windows系统是windows键)

shiftKey:shift键

function showChar(e){

  console.log("ALT: " + e.altKey);

  console.log("CTRL: " + e.ctrlKey);

  console.log("Meta: " + e.metaKey);

  console.log("Meta: " + e.shiftKey);}

key,charCode

key属性返回一个字符串,表示按下的键名。如果同时按下一个控制键和一个符号键,则返回符号键的键名。比如,按下Ctrl+a,则返回a。如果无法识别键名,则返回字符串Unidentified。

主要功能键的键名(不同的浏览器可能有差异):Backspace,Tab,Enter,Shift,Control,Alt,CapsLock,CapsLock,Esc,Spacebar,PageUp,PageDown,End,Home,Left,Right,Up,Down,PrintScreen,Insert,Del,Win,F1~F12,NumLock,Scroll等。

charCode属性返回一个数值,表示keypress事件按键的Unicode值,keydown和keyup事件不提供这个属性。注意,该属性已经从标准移除,虽然浏览器还支持,但应该尽量不使用。

进度事件

进度事件用来描述一个事件进展的过程,比如XMLHttpRequest对象发出的HTTP请求的过程、<img>、<audio>、<video>、<style>、<link>加载外部资源的过程。下载和上传都会发生进度事件。

进度事件有以下几种。

abort事件:当进度事件被中止时触发。如果发生错误,导致进程中止,不会触发该事件。

error事件:由于错误导致资源无法加载时触发

load事件:进度成功结束时触发。

loadstart事件:进度开始时触发。

loadend事件:进度停止时触发,发生顺序排在error事件\abort事件\load事件后面。

progress事件:当操作处于进度之中,由传输的数据块不断触发。

timeout事件:进度超过限时触发。

image.addEventListener('load', function(event) {

  image.classList.add('finished');});

image.addEventListener('error', function(event) {

  image.style.display = 'none';});

上面代码在图片元素加载完成后,为图片元素的class属性添加一个值“finished”。如果加载失败,就把图片元素的样式设置为不显示。

有时候,图片加载会在脚本运行之前就完成,尤其是当脚本放置在网页底部的时候,因此有可能使得load和error事件的监听函数根本不会被执行。所以,比较可靠的方式,是用complete属性先判断一下是否加载完成。

function loaded() {

  // code after image loaded}

if (image.complete) {

  loaded();} else {

  image.addEventListener('load', loaded);}

由于DOM没有提供像complete属性那样的,判断是否发生加载错误的属性,所以error事件的监听函数最好放在img元素的HTML属性中,这样才能保证发生加载错误时百分之百会执行。

<img src="/wrong/url" onerror="this.style.display='none';" />

error事件有一个特殊的性质,就是不会冒泡。这样的设计是正确的,防止引发父元素的error事件监听函数。

进度事件使用ProgressEvent对象表示。ProgressEvent实例有以下属性。

lengthComputable:返回一个布尔值,表示当前进度是否具有可计算的长度。如果为false,就表示当前进度无法测量。

total:返回一个数值,表示当前进度的总长度。如果是通过HTTP下载某个资源,表示内容本身的长度,不含HTTP头部的长度。如果lengthComputable属性为false,则total属性就无法取得正确的值。

loaded:返回一个数值,表示当前进度已经完成的数量。该属性除以total属性,就可以得到目前进度的百分比。

下面是一个例子。

var xhr = new XMLHttpRequest();

xhr.addEventListener("progress", updateProgress, false);xhr.addEventListener("load", transferComplete, false);xhr.addEventListener("error", transferFailed, false);xhr.addEventListener("abort", transferCanceled, false);

xhr.open();

function updateProgress (e) {

  if (e.lengthComputable) {

    var percentComplete = e.loaded / e.total;

  } else {

    console.log('不能计算进度');

  }}

function transferComplete(e) {

  console.log('传输结束');}

function transferFailed(evt) {

  console.log('传输过程中发生错误');}

function transferCanceled(evt) {

  console.log('用户取消了传输');}

loadend事件的监听函数,可以用来取代abort事件/load事件/error事件的监听函数。

req.addEventListener("loadend", loadEnd, false);

function loadEnd(e) {

  console.log('传输结束,成功失败未知');}

loadend事件本身不提供关于进度结束的原因,但可以用它来做所有进度结束场景都需要做的一些操作。

另外,上面是下载过程的进度事件,还存在上传过程的进度事件。这时所有监听函数都要放在XMLHttpRequest.upload对象上面。

var xhr = new XMLHttpRequest();

xhr.upload.addEventListener("progress", updateProgress, false);xhr.upload.addEventListener("load", transferComplete, false);xhr.upload.addEventListener("error", transferFailed, false);xhr.upload.addEventListener("abort", transferCanceled, false);

xhr.open();

浏览器提供一个ProgressEvent构造函数,用来生成进度事件的实例。

progressEvent = new ProgressEvent(type, {

  lengthComputable: aBooleanValue,

  loaded: aNumber,

  total: aNumber});

上面代码中,ProgressEvent构造函数的第一个参数是事件类型(字符串),第二个参数是配置对象,用来指定lengthComputable属性(默认值为false)、loaded属性(默认值为0)、total属性(默认值为0)。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值