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 !!!

posted @ 2009-03-23 21:47  Franky  阅读(...)  评论(...编辑  收藏