第四章 javaScript事件
一、常用事件
事件 | 功能 | 适用于 |
onAbort | 当用户中断图像装载是发生 | 与img元素一起使用 |
onBlur | 当元素失去焦点时 | Label/input/select/texttarea/button |
onChange | 当元素失去焦点,并且值被改变时 | Input/select/texttara |
onClick | 单击时 | 与绝大多数元素 |
onDbclick | 双击时 | 与绝大多数元素 |
onDragDrop | 将对象拖放到窗口或框架是 | Body/frame/frameset |
onError | 当装入窗口、框架、图像期间出错时 | Body/frame/frameset |
onFocus | 获得焦点时 | Label/input/select/texttarea/button |
onKeyDown | 一个键按住不放时 | 与绝大多数元素 |
onKeyPress | 一个键按下后到松手时 | 与绝大多数元素 |
onKeyUp | 一个键被松手时 | 与绝大多数元素 |
onLoad | 鼠标在一个元素上按住时 | Body/frame/frameset |
onMouseDown | 鼠标在一个元素上移动时 | 与绝大多数元素 |
onMouseMove | 鼠标移动到元素上时(在元素上移动/抚摸事件) | 与绝大多数元素 |
onMouseOut | 鼠标离开元素时 | 与绝大多数元素 |
onMouseOver | 鼠标经过这一元素时 | 与绝大多数元素 |
onMouseUp | 鼠标在一个元素上释放时 | 与绝大多数元素 |
onMove | 移动窗口或框架时 | Body/frame/frameset |
onReset | 当表单被重置时 | form |
onSelect | 文本选择发生时 | Input/texttara |
onSubmit | 表单被提交时 | form |
onUnload | 当web浏览器从窗口或框架卸载一个文档时发生 | Body/frame/frameset |
oncontextmenu | 右键菜单事件 | return false 可以阻止右键功能 |
<1>UI事件:当用户与页面上的元素交互时触发。
例如:load、unload、select、scroll、resize(浏览器窗口宽高改变时)......
<2>焦点事件:获得或失去焦点时触发
blur、focus不会冒泡
<3>鼠标事件:通过鼠标在页面上操作。
click、dbclick、mousedown、mouseleave、mousemove、mouseout、mouseover...
<4>滚轮事件:当使用鼠标滚轮是触发。
IE6首先实现了mousewhere事件(移动为120倍数)
<5>键盘事件:通过键盘在页面上操作。
keydown、keypress、keyup
常用按键键码:Backspace(8)、Enter(13)、Ctrl(17)、Esc(27)、left Arrow(37)、up Arrow(38)、Right Arrow(39)、Down Arrow(40)
<6>合成事件:输入字符时。
<7>变动事件:DOM结构方式变化时。
<8>变动名称事件:元素或属性名称变动时。
二、事件调用方式
方式一:直接用js语句(适用于包含很简单的内容时)
例如:<intput type=”button” onClick = “alert(event.type)”>
方式二:使用函数进行事件绑定(易调试、模块化、多次调用)
例如:<intput type=”input” onClick = “yanz()”>
或者:在js中绑定:...btn[i].οnclick=function(){//代码块}
方式三:通过对象指定事件处理函数
(document.form1.button1.οnclick=showdata; //这里的showdata函数不要加()加了还没点击就直接执行了)
三、event对象
属性 | 功能 |
altKey | (返回boolean值)Alt键按下时为真 |
ctrlKey | Ctrl键按下时为真 |
shiftKey | Shift键按下时为真 |
clientX/clientY | 鼠标相对于事件所在窗口客户区域的水平/垂直坐标,不包括窗口修饰或滚动条 |
offsetX/offestY | 鼠标光标相对于事件所在对象(容器)的水平/垂直坐标 |
ScreenX/screenY | 鼠标光标相对于用户屏幕的水平/垂直坐标 |
fromElement | 被移动的元素 |
srcElement | 触发的事件的对象 |
type | 返回事件对象中的事件名称 |
X/y | 鼠标相对于事件所在文档的水平/垂直坐标 |
cancelBubble | 取消浮升 |
which | 按下左键是“1”,右键是“3” |
四、DOM事件监听
1、基本概述
对于浏览器来说,最佳的事件处理方式当然莫过于出自DOM Level2的事件监听了。addEventListener(‘事件’,监听事件,boolean),第三个参数可以决定我们是否采用捕捉法(Netscape)来处理事件,为了使代码适用更多的浏览器,最好还是设置为false,即采用冒泡法来处理(IE)所有现代浏览器都支持事件冒泡。这也被称作为事件流。设为true则采用捕捉法。
捕捉法:单击发生在document上,然后依次传给body....到达目标链。
冒泡法:单击首先发生在链接上,然后再逐层向上冒泡,直到document对象
document实际涵盖了整个页面。
阻断冒泡:对象.cancelBubble = true;//取消该对象的冒泡
但在DOM2级事件:规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
例如:
<body>
<div id=div>
<div id=div1>点我</div>
</div>
<script type="text/javascript">
var btn=document.getElementById('div');
var btn1=document.getElementById('div1');
btn1.addEventListener('click',function(event){alert(event.eventPhase+'div1内层--事件捕获');},true); //第二次:2 div1内层-事件捕获
btn.addEventListener('click',function(event){alert(event.eventPhase+'div外层--事件冒泡');},false); //第四次:3 div外层--事件冒泡
btn1.addEventListener('click',function(event){alert(event.eventPhase+'div1内层--事件冒泡');},false); //第三次:2 div1内层--事件冒泡
btn.addEventListener('click',function(event){alert(event.eventPhase+'div外层--事件捕获');},true); //第一次:1 div外层-事件捕获
</script>
<!--第三个属性 false表示在冒泡阶段调用事件处理,true在捕获阶段调用处理程序-->
<!--注释:event.eventPhase属性:0,事件目前没有发生。1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。该过程是从Window对象到Document节点,再到HTMLHtmlElement节点,直到目标节点的父节点为止。2,事件到达目标节点,即target属性指向的那个节点。3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。该过程是从父节点一直到Window对象。只有bubbles属性为true时,这个阶段才可能发生。-->
<!--解释:在捕获阶段依次为window、document、html、body、div,在冒泡阶段依次为div、body、html、document、window。从事件流来说,先捕获再冒泡,所以从上到下,第一次(1 div外层-事件捕获)、第二次(第二次:2 div1内层-事件捕获),捕获完成了。而冒泡,从div开始,第三次(第三次:2 div1内层--事件冒泡)、第四次(第三次:3 div1外层--事件冒泡)。当我把所有的都改为false时,这是就和定义事件的顺序有关了。所以第一次(2 div1内层--事件捕获)、第二次(2 div1内层--事件冒泡)、第三次(3 div外层--事件冒泡)、第四次(3 div外层--事件捕获)-->
</body>
2、阻断传播
方式一:
function handler(e){ e.stopPropagation();}
document.getElementById(‘目标’).addEventListener(‘click’,handler,false)
这样只会触发这个目标上的事件,其他事件不会在冒泡上去。
方式二:
document.getElementById(‘目标1’).removeEventListener(‘click’,事件,false)
被设定的目标,冒泡不会走目标1,但是如果上面还有还是会走的。这只是阻断了目标1这一个。
3、事件监听
通过addEventListener()添加的事件处理,只能使用removeEventListener()来移除。
第五章 原型
一、相关概念
1、原型定义
函数本身也是一个包含了方法和属性的对象,javaScript的所有function类型的对象都有一个length(参数的个数)和prototype属性。这个属性本身又是一个object类型的对象,我们可以给这个对象添加任意的属性和方法。这是javaScript特有的”原型继承”[每一个对象都从原型继承属性]。Prototype(原型)对象将自己的属性和方法供后代继承。类似于java中的继承。对象也可以掩盖原型对象的属性和方法,这种掩盖只是在对象自己身上创建的属性和方法,javascript就是通过这简单的掩盖机制实现了对象的“多态”性。(特殊之处在于可以自己扩充prototype)。例如toString()和valueOf()等方法实际是保存在prototype名下。
另外每个函数都包含了两个非继承而来的方法:apply()和call().
注:在了解原型前需要了解以下知识:
在js中,对象可谓贯穿一生。但是对象可以分为两类。一是普通对象(Object)二是函数对象(Function)。一般而言通过new Function()产生的对象是函数对象,其他对象就是普通对象了。
例如:
function f1(){}
var f2 = function(){}
var f3 = new Function("x","alert(x)")
var o1 = {}
var o2 = new Object();
var o3 = new f1();
f1属于函数的声明,最常见的函数的定义方式。f2实际上是一个匿名函数,只是把这个函数的值赋给了f2,f3是函数真正的构建方式。f1、f2在创建时js会自动帮我们解析通过new Function()来构建。
而创建Object对象的方法,o1和o2就不多说了。而o3我们可以通过instanceof判断对象的类型,是Object,他不是通过new Function()产生的。所以他是普通对象。
每创建一个函数对象时,js就会内置一些属性,其中就包括__proto__等。Prototype主要作用就是继承。说白了就是把prototype中定义的属性和方法留给后代使用。而__proto__有什么用呢?Js通过new表达式创建对象的时候通常会把父类的prototype赋值给新的对象。
例如:
function f(){}
f.prototype.foo = "abc";
console.log(f.foo); //undefined
var obj= new f();
console.log(obj.foo); //abc
解释:为啥第一个会打出undefined,因为我直接通过函数名去访问foo属性,这时f中并没有这个属性。Foo属性还在原型中,只有new之后才会被后代继承,f.prototype实际上是就是Object类型的对象。当我new过之后,会把父类的prototype赋值给新的对象的__proto__属性,而obj对象中的属性实际上是来自其原型上的属性,自身属性上找不到,会沿着原型链去查找,直到找到原型链终结为止,自身属性上找到了就不会再去原型上找了。
2、原型链
所有通过对象直接量创建的对象都具有同一个原型对象,并且可以通过Object.prototype获得原型对象的引用。通过new关键字和构造函数创建的对象的原型就是构造函数的prototype属性的引用。对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。
Object.prototype对象有没有它的原型呢?回答可以是有的,就是没有任何属性和方法的null对象,而null对象没有自己的原型。所以原型链到此为止。
“原型链”的作用是,读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overiding)。
需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
继续上面的例子,obj原型链如下:
注:prototype是函数的属性,指向原型对象。__proto__指向的是对象的属性,他也指向原型。对象可以访问__proto__属性指向的那个原型对象中的属性和方法。
function F(){}
var f = new F();
在浏览器中:
在f对象中:
f.__proto__ :保存的是F.prototype (f.__proto__ === F.prototype)
而F.prototype.__proto__:保存的是Object.prototype
而Object.prototype.__proto__就是null
在原型上定义属性和方法,目的是为了被子类继承和使用,prototype影响的是子类,而原型链的形成真正靠的是__proto__。
3、原型prototype和constructor的关系
Prototype与Constructor的关系
function Dog(){}
alert(Dog === Dog.prototype.constructor);//true
在 JavaScript 中,每个函数都有名为“prototype”的属性,用于引用原型对象。此原型对象又有名为“constructor”的属性,它反过来引用函数本身。这是一种循环引用,如图:
三、原型对象的属性和方法
1、constructor属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。作用,是分辨原型对象到底属于哪个构造函数。他返回的是创建此对象的函数的引用。
例如:
function P() {} P.prototype.constructor === P // true
//由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。
var p = new P();
p.constructor // function P() {} 指的是当前对象的构造函数
p.constructor === P //true
p.constructor.name === “P” //true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false --不是实例中的属性
//注: hasOwnProperty --用于检测一个属性时存在与实例中还是存在原型中
上面代码中,p是构造函数P的实例对象,但是p自身没有contructor属性,该属性其实是读取原型链上面的P.prototype.constructor属性。
有了constructor属性,就可以从实例新建另一个实例。
function Constr() {
var x = new Constr();
var y = new x.constructor();
y instanceof Constr // true
练手1:
function Foo(){};
var foo = new Foo();
var obj = new Object();
alert(foo.constructor); //(1)
alert(Foo.constructor); //(2)
alert(Object.constructor); //(3)
alert(obj.constructor); //(4)
alert(Function.constructor); //(5)
弹出结果:
(1)Foo或者function Foo(){}
(2)Function或者function Function(){}
(3)Function或者function Function(){}
(4)Object或者function Object(){}
(5)Function或者function Function(){}
解释:对象.constructor 指向的是该对象所属类型的构造函数(原函数的引用)。(1)中因为是foo对象,这是自定义的function Foo(){};构造函数 的一个对象,所以自然就指向了Foo这个构造函数;(2)因为是Foo对象,之前谈到过所有的函数都是Function类型的实例,Foo函数实际的格式应该是 var Foo = new Function()所以,它的构造函数是他所属类型的构造函数。即Function或者function Function(){};(3)同理Object对象的类型也属于Function;(4)Object或者function Object(){};(5)为Functionfunction Function(){};
练手2:
function Animal(){} //1
function Person(){} //2
var person = new Person(); //3
alert(person.constructor); //4
Person.prototype = new Animal(); //5
var person = new Person(); //6
alert(person.constructor); //7
解释:4处弹出结果为结果为Person。7处弹出的结果为Animal(function Animal(){})每个函数都有个prototype属性,这里他的原型是Object,说明他的构造属性在他继承的原型Object上。而在3处我访问constructor指向的就是person对象的引用,即为他的构造函数Person(或者function Person(){}),当我第五行对Person的原型进行修改后,person的原型就就是new Animal();了,当然在此访问constructor的时候就是访问new Animal();里面的constructor属性了,而这个的constructor属性就要追溯new Animal();对象的constructor属性了,他的constructor属性来自他继承的原型Object里面的constructor,而他指向了new Animal();对象的引用,即为Animal。所以7处弹出function Animal(){}(或者Animal)
练手3:当你真正能说出这个结果说明你已经懂了constructor属性
function Person(){ }
Person.prototype={
name:"Nicholas",
age: 25,
job:"Software Enginner",
sayName:function(){
console.log(this.name);
}
}
alert(Person.constructor) //1
var person =new Person();
alert(person.constructor) //2
解释:1处的结果是function Function(){}(或者简单说就是Function构造函数)。而二处的结果是function Object(){}(或者简单说就是Object构造函数),为什么不是Person呢,因为上面我们重写了Person默认的protptype,当我们再次new的person对象的原型指向的是一个新赋予的对象
Object({name:”Nicholas”,age:25..... })而Object对象的constructor属性自然指向的是Object的构造函数。
2、原型的方法
1)Object.getPrototypeOf()
Object.getPrototypeOf() //返回一个对象的原型。这是获取原型对象的标准方法
//注: 空对象的原型是Object.prototype
Object.getPrototypeOf({}) === Object.prototype // true
// 函数的原型是Function.prototype
function F() {} Object.getPrototypeOf(F) === Function.prototype // true
// f 为 F 的实例对象,则 f 的原型是 F.prototype
var f = new F(); Object.getPrototypeOf(f) === F.prototype // true
2) Object.setPrototypeOf()
Object.setPrototypeOf() //--为现有对象设置原型,返回一个新对象。
var F = function () {this.foo = 'bar';};
var f = new F();
// 等同于var f = Object.setPrototypeOf({}, F.prototype);
注:new命令通过构造函数新建实例对象,实质就是将实例对象的原型,指向构造函数的prototype属性,然后在实例对象上执行构造函数。
3)Object.create()
Object.create() --从原型对象生成新的实例对象,可以替代new命令。
var o1 = Object.create({}); //ECMAScript新增的方法原型式继承
var o2 = Object.create(Object.prototype); //传入继承的原型
var o3 = new Object();
注:以上三种创建方式是等价的
例如:var B = Object.create(A);
Object.create方法在A的基础上生成了B。此时,A就成了B的原型,B就继承了A的所有属性和方法。
除了对象的原型,Object.create方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到新对象。
var o = Object.create({}, {
p1: { value: 123, enumerable: true },
p2: { value: 'abc', enumerable: true }
});
// 等同于
var o = Object.create({});
o.p1 = 123;
o.p2 = 'abc';
4)Object.prototype.isPrototypeOf()——对象实例的isPrototypeOf方法,用来判断一个对象是否是另一个对象的原型。
var o1 = {};
var o2 = Object.create(o1);
o1.isPrototypeOf(o3) // true
注:只要某个对象处在原型链上,该方法就返回true
5)Object.prototype.__proto__ 注:(前后各两个下划线)可以改写某个对象的原型
var obj = {};
var p = {};
obj.__proto__ = p; //obj对象的原型设为p
Object.getPrototypeOf(obj) === p // true
注:这个本质是一个内部属性,尽量少用
总结:获取实例对象obj的原型对象,有三种方法。例如:
var obj = new Object();
第一种:obj.__proto__
第二种:obj.constructor.prototype
第三种:Object.getPrototypeOf(obj)
前两种都不怎么可靠,推荐使用第三种
三、原型添加方法和属性
方式一格式:
函数名.prototype.属性名 = “”;
函数名.prototype.方法名 = function(){};
方式二格式:
函数名.prototype={
属性名:“”,
方法名:function(){}
}
例如:
Test.prototype = {
name :”苹果”,
price :100,
getInfo: function(){
return this.name+”卖”+this.price;//原型是需要通过new方式访问
}
}
注意:如果对象的自身属性与原属性同名,那么对象自身的属性优先级高于原型
不使用prototype属性定义的对象方法,相当于(类的静态方法),只能直接用函数名(类名)进行调用!另外,此静态方法中无法使用this变量来调用对象其他的属性!因为每new 一次,就会创建新的对象,该方是不具备上诉方法的。
使用prototype属性定义的对象方法,(相当于类的实例方法)是非静态方法,只有在实例化后才能使用!其方法内部可以this来引用对象自身中的其他属性!
四、使用原型的属性和方法
使用方法:var 对象名 = new 函数名(属性名);
对象名.属性名
例如:var newtoy = new Grade()
newtoy.price;
注意:js中对象都是通过传递引用的方式来传递的。
如果我们访问某个属性时,js会去查询该对象的所有属性,但是在这个对象中并不存在这个属性的时候,就会去查询该函数的原型,依此搜索的原型的原型,直到最高的父级对象Object,如果找到就会立即使用。
总结:类,不具备实际的功能,只是用来构造对象,而对象真正有功能的东西,被类构造出来。当我们给某个类(函数)创建两个对象时,我们给其中一个对象添加了一个方法,事实上另外一个对象无法拥有该方法。但是我们给这个类(函数)去添加这个方法或属性时时,该类(函数)的对象都可以拥有该方法或属性。所以原型的一个重要功能就是可以扩展系统对象。我们通过给类(函数)的原型添加属性和方法就可以对这个类(函数)进行扩展。
Array.prototype.sum =function(){
var result = 0;
var i =0;
for(i=0;i<this.length;i++){
result += this[i];
}
return result;
}
var arr = new Array(1,2,3,4,5);
alert(arr.sum()); //15
var arr1 = new Array(1,2,3,4,5,6);
alert(arr1.sum()); //21
五、js中的继承
JS中没有类的概念,但是可以用构造函数来替代。ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想就是利用一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个原型对象,原型对象都包含了一个指向构造函数的指针,而实例都包含了一个指向原型对象的内部指针。假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询属性x。如果原型对象中也没有,但是这个原型对象也有原型,那么还会继续往上找,直到找到x或者找到一个原型为null的对象为止(即Object,Object对象的原型就是null,null没有原型)。对象的原型构成了一个链,通过这个链可以实现属性的继承。
六、其他实用内容
一、处理兼容性方式
我们都知道,js的兼容性如今依然没有得到改变。浏览器各自为政,方法各有千秋。
1、 或( || )的使用技巧
function foo(x){
x = x || 1;
console.log(x);
}
foo(2); //2
foo(0); //1
因为:undefined,null,"",0转换为逻辑值为false,其他均为true当使用z = x || y;这种模式时,前者为真返回x值,后者不执行,前者为假时返回后者的值
Var oFirst = ul.firstElementChild || ul.firstChild [前者火狐,后者IE]
var oEvent = ev || event; 可以 var oEvent = event || ev; 不可以
原因:前者ev在ie下返回undefined,即为fasle。而后者event只有ie支持,
其他浏览器会报错。用后者的话也就只能在IE下看到效果了。
2、if的使用
document.onclick=function(ev){
if(ev){
alert(ev.clientX); //IE不支持,但火狐支持
}else{
alert(event.clientX); //火狐下不支持event
}
}
扩展:
浏览器能力监测(特性监测):
模式:if(object.propertyInQuestion){//使用object.propertyInQuestion}
3、判断浏览器
1)方式一:
if(window.navigator.userAgent.indexOf(‘MISE’)>0){//IE}else{//非IE}
2)方式二:
if(window.对象){ //undefined 的逻辑值为false,支持IE}else{}
if(document.all){ alert("IE"); }else{ alert("not ie"); }
4、错误类型
1)Error:其他错误都继承自该类型
2)EvalError:使用eval()函数错误。
3)RangeError:数值超出范围。
4)ReferenceError:找不到对象,比如用了未声明的对象。
5)SyntaxError:语法错误
6)TypeError:类型错误
7)URIError:URI格式不正确
5、错误处理方式
1)try - catch
try{//可能导致错误的代码
}catch(error){//在错误发生时怎么处理
}finally{//一定会执行的
}
2)throw 随时跑出自定义错误
throw 1; //1,后面的不会出现,是因为遇到throw,代码立即停止执行
throw "hello world";
throw true;
throw {name:"javaScript"};
throw new SyntaxError("I don't like this")
6、错误调试方法
1、将消息记录到控制台:console.log(***);
2、跑出自定义错误:throw new Error(***);
3、显示一个对象的所有属性和方法在控制台console.dir(obj)