javascript 中的this 与事件注册 及event
本文不讨论 语法解释期和执行期的区别 以及上下文环境和闭包的概念 我们只从另一个角度来看问题.
js 中的this 是一个指针 他指象某个对象. 那么 一般来说 记住一条原则 即可.
如果一个函数不是作为类 来实例化某个对象 如 new func();而是当 函数 做为某个对象的 方法被调用时 则this就指向该对象. 如果该函数是直接被调用 则this指向全局变量...这句话似乎不好理解. 我们看下例子
function test(){
this.name='franky';
}
var o={};
o.t=test;
o.t();//此处test方法是作为 对象o的方法t 被调用的.则 其内部的this 就指向 对象o .则此时 test内部的 this.name='franky' 等价于 o.name='franky'
alert(o.name);//打印franky
但 如果我们 直接调用 test(); 方法 则 因为他不是作为某个对象的方法被调用. 而是直接被调用 所以 this指向了全局变量
此时 我们可以直接 alert(window.name);或 alert(name);来 访问全局变量的 属性name了.
那么我们现在来看一看 事件注册
事件注册 有3种方式
1 硬编码 . 即在 html element 中直接写事件注册.如:
<div id="div1" onclick="alert(this.id)">测试</div>
2 js块中的传统方式 如:
document.getElementById('div1').onclick=function(){alert(this.id);}
3 使用 attachEvent 和addEventListener 来注册事件.
这当中比较经典的 则是 Jquery作者封装的 addEvent方法.虽然他有很多不足. 具体的后面解释.
那么现在. 我们来看看 1 硬编码格式
<div id="div1" onclick="alert(this.id)">测试</div>
为什么这里的this 会指向 div1 这个element呢?
答案是 .. 硬编码格式 的事件注册. 浏览器会把 你的代码 封装到一个 (匿名)函数中去.
我们此时 可以通过 打印div1的onclick属性来 看一看
alert(document.getElementById('div1').onclick)//注意这里不是onclick()
我们会看到 ie打印了 function anonymous(){} //看名字就知道. 他就叫匿名函数
opera 打印 function (event){}//恩. 这个是标准的匿名函数了
safari chrome ff 打印 function click(event){}// 这里 此三个浏览器 给 包装函数 一个对应事件的名字 .
到了这里.我们就能看出 . 当div1 click事件发生时. 浏览器会做什么呢? 是的 他会试图去执行 div1.onclick()//如果他现在是一个方法的话
大概的过程 是 if(div1.onclick)div1.onclick(); 所以呢 现在包装函数 是作为 div1这个element对象的方法被浏览器调用的 则 其内部的this 指向div1 就顺理成章了.
那么 情况也就明朗了 硬编码 和 js块 中 的 document.getElementById('div1')=function(){alert(this.id)}
是一回事. 所以 此时 我们 甚至可以 直接 执行这个方法 而不是通过事件去触发它. 即 div1.onclick();//直接执行..
但有一点 我们要注意. 我们人工 调用 该方法 不会产生浏览器事件. 他只会执行 alert(this.id); 这一句.
那么 我们看看 浏览器事件触发执行又是什么过程呢?
如果您仔细看了 硬编码的 onclick 应该不难发现 w3c浏览器的包装函数 无论叫什么 . 但他总是有个叫event的形参.
是的. 这个就是事件对象. 所以 我们在 硬编码环境中 不仅仅可以 直接用this来引用 当前的element 还可以直接用event 来引用 事件对象.
而且这样写. 是兼容全部浏览器的. 原因是 w3c的包装函数有一个叫event的型参 而ie 呢? window.event可以简写为 event 则同样获取了当前的事件对象....
那么 最后再老生常谈一下. w3c浏览器 在事件触发 回调函数时 会传过去一个参数 .但ie不会
所以 我们在非硬编码的情况下 可以如此获取参数
obj.onclick=function(e){
e=e||window.event;//w3c浏览器 e是对象 是对象做逻辑运算时 即true 即使 是 if(new Boolen(false))//这里也是true
//而ie中 因为 不会传事件对象过来. 所以此时e==undefined.则 e=window.event
}
好了. 这就是 为什么 很久很久以前我们 常用的硬编码注册事件 可以直接兼容所有浏览器的原因. 事实上 这是因为 各个浏览器在向 ie 致敬 ....包装函数型参名叫event 所导致的..
但 你千万不要以为 我们 把 型参叫event 就可以 在js块中 不需要e=e||window.event了
比如:
obj.onclick=function(event){
event//记得 这个和硬编码是不同的. 这里的event 在ie中仍然是undefiend 这是由于 作用域所导致的. event此时是局部变量.具体原因请自己思考.
}
那么 关于硬编码 最后要提的 就只剩下一条了. 硬编码环境中 我们可以省略this. 如:
<div id="div1" onclick="alert(this.id)">测试</div>
完全等价于
<div id="div1" onclick="alert(id)">测试</div>
各个浏览器都支持这样写 是因为 局部变量 他会先到 element的属性表中去找.然后才是全局. 但我十分不建议这样用.甚至也不建议硬编码. 原因大家都能理解.
那么 既然今天的主题 首先是this. 我们则接着说说this 的2个特别情况.
1. call 和applay
是的 这两个方法的作用就是修改 this指针 所指向的对象. 而他们的唯一区别是传参的方式. 具体的请去查看手册. 这里就不再罗嗦了. 看代码:
function test(){
this.name='franky';
}
var o={};
var o2={};
o.t=test;
o.t();// 此时 o.name=='franky';
但是如果是
o.t.call(o2);//只执行此句 则 o.name=='undefined' o2.name=='franky'. 是的 call 把方法t中的 this 强制指向了 o2.
这就是 call和applay的 作用 . 在js的继承中 你可能会看到 类似下面的代码:
function ClassA(){
this.name='franky';
}
function ClassB(){
ClassA.call(this);//这里执行ClassA时 只是把ClassA当作普通的 函数来执行.不同的是 通过call修改了 ClassA方法中this的引用.让它指向了ClassB的实例.
}
此时. 我们
var obj=new ClassB();
实例化一个ClassB 创建一个副本即 对象obj . 我们要知道的是. ClassB() 和new ClassB() 都会导致 ClassB的执行. 即使我们写做 var obj=new ClassB;
那么根据js的规则. 此时 ClassB中的this会;指向obj 那么 其内部的 ClassA.call(this); 等价于 ClassA.call(obj); 即ClassA中的this 被call 修改为指向obj
那么 ClassA内部的 this.name='franky' 最终就等价于 obj.name='franky';// 这就实现了 动态属性和方法的继承.
如果我们真的理解了这里 . 我们就应该明白. 为什么 这种继承方式. 无法继承 ClassA原型属性和方法. 而只能继承动态属性和方法.
再看看 new 到底干了什么
function NEW (Class){//山寨版的 new Class为欲实例化的构造函数..
var obj={};
obj.__proto__=Class.prototype;
// __proto__属性对于ie来说是不可见的。你可以在ff中测试 这里是要先绑定 然后再call的
//所以 为什么 我们可以用 在构造函数内 用 if(this instanceof arguments.callee) 来判断 是不是 用new 来调用构造函数.
//当然 这个判断方法 并不是100%的 如果构造函数 作为其实例的方法被调用 上面的条件一样成立...不过这种需求 基本不可能出现.
var returnValue=Class.call(obj);
if(typeof returnValue=='object' &&returnValue!=null) return returnValue;
return obj;
}
到了这里 随着AJAX 的广泛应用.我们不得不提一个很特别的事 就是
ie中的 xhr对象 的onreadystatechange 方法中的this 不指向xhr. 这一事实.
这非常不符合 我们所学习到的 理论体系.
var xhr=new XMLHttpRequest;
xhr.onreadystatechange=function(){this//ie中 this很遗憾的不指向xhr}
但ie7中不知道是不是因为支持了本地的 XMLHttpRequest 而不是 activex的缘故.似乎解决了这个问题.但为了兼容性.
所以我们要这样做
xhr.onreadystatechange=function(){handler.call(xhr);}
function handler(){......}
好了. 到了这里 基本上就把this 搞清楚了...
如果你想深入的理解 this的关系 是如何被维护的 则 可以看一看 ECMAScript标准. 不过标准的描述,其实就是让你不必关心这些东西,只要知道如何用就够了.
ps: 似乎写着写着 就没收住.. 越写越多. 那么只好把 关于 事件注册的就简单说一下好了 关于jquery作者的写的 addEvent 以及他的缺点 :
1. 内存泄露
2.无法解决attachEvent忽略重复注册
3.注册同一对象的同一事件的几个方法后 执行顺序相反的问题
4. 因为 其修复了this引用问题. 却导致了一个新的问题. 同一个构造函数的多个实例的 同一原型方法 注册到 同一element的同一事件上时 会导致 _this指向最后一个被 实例化对象的问题.
以上这些问题 1和4 是 其为了修复this引用而导致的新问题 而2 和3 则是attachEvent所固有的问题.
而 addEventListener 则完全没有任何一个问题 实在是非常完美.
而 Dean Edwards 在几年前写了 2个版本的 addEvent 你可以去 他的blog看一看. 虽然仍然有些问题. 不过却 也有优点.
他完全放弃了 attachEvent和addEventListener .而使用传统方式 借助guid 来实现 灵活注册事件和删除事件注册
.那么 所有头疼的 问题 都来自ie的 attachEvent .所以借助老外们常说的一句话 结束本章 : IE SUCKS !!!