self和this使用小结
一、self
这个非常简单。我们知道,打开任何一个网页,浏览器会首先创建一个窗口,这个窗口就是一个window对象,也是js运行所依附的全局环境对象和全局作用域对象。self 指窗口本身,它返回的对象跟window对象是一模一样的。也正因为如此,window对象的常用方法和函数都可以用self代替window。举个例子,常见的写法如“self.close();”,把它放在<a>标记中:“<a href="javascript:self.close();">关闭窗口</a>”,单击“关闭窗口”链接,当前页面关闭。
在讲this之前,看下面的一段代码:
<input id="btnTest" type="button" value="提交" /><script type="text/javascript"> function thisTest(){ alert(this.value); }document.getElementById("btnTest").onclick=thisTest; //给button的onclick事件注册一个函数</script>
分析:在前面的示例中,thisTest函数定义在全局作用域(这里就是window对象),所以this指代的是当前的window对象。而通过document.getElementById("btnTest").onclick=thisTest;这样的形式,其实是将btnTest的onclick属性设置为thisTest函数的一个副本,在btnTest的onclick属性的函数作用域内,this归btnTest所有,this也就指向了btnTest。其实如果有多个dom元素要注册该事件,我们可以利用不同的dom元素id,用下面的方式实现:
document.getElementById("domID").onclick=thisTest; //给button的onclick事件注册一个函数。
因为多个不同的HTML元素虽然创建了不同的函数副本,但每个副本的拥有者都是相对应的HTML元素,各自的this也都指向它们的拥有者,不会造成混乱。
为了验证上述说法,我们改进一下代码,让button直接弹出它们对应的触发函数:
//第一个按钮function onclick(){ thisTest()} //第二个按钮function thisTest(){ this.value="提交中";}
从上面的结果你一定理解的更透彻了。
By the way,每新建一个函数的副本,程序就会为这个函数副本分配一定的内存。而实际应用中,大多数函数并不一定会被调用,于是这部分内存就被白白浪费了。所以我们通常都这么写:
<input id="btnTest1" type="button" value="提交1" onclick="thisTest(this)" /><input id="btnTest2" type="button" value="提交2" onclick="thisTest(this)" /><input id="btnTest3" type="button" value="提交3" onclick="thisTest(this)" /><input id="btnTest4" type="button" value="提交4" onclick="thisTest(this)" /><script type="text/javascript"> function thisTest(obj){ alert(obj.value); }</script>
这是因为我们使用了函数引用的方式,程序就只会给函数的本体分配内存,而引用只分配指针。这样写一个函数,调用的地方给它分配一个(指针)引用,这样效率就高很多。当然,如果你觉得这样注册事件不能兼容多种浏览器,可以写下面的注册事件的通用脚本:
function thisTest() { var tmpName = 'jeff wong'; this.userName= 'jeff wong'; }var test= new thisTest();alert(test.userName==test.tmpName);//falsealert(test.userName); //jeff wongalert(test.tmpName); //undefined
分析一下结果,其实这里的this和c#里的是类似的。
(4)、为脚本对象添加原形方法
理解这里的前提是你必须了解js里的原型概念(说道这里,kao,我还真的需要面壁一下):js中对象的prototype属性,是用来返回对象类型原型的引用的。所有js内部对象都有只读的prototype属性,可以向其原型中动态添加功能(属性和方法),
但该对象不能被赋予不同的原型。但是对于用户定义的对象可以被赋给新的原型。看个简单的示例:
//js的内部对象String,向其原型中动态添加功能(属性和方法)//去掉字符串两端的空白字符String.prototype.Trim = function() { return this.replace(/(^\s+)|(\s+$)/g, "");}function thisTest() { var tmpName = 'jeff wong'; this.userName= ' jeff wong '; }//给用户定义的对象添加原型方法thisTest.prototype.ToString = function() { alert(this.userName); //jeff wong(*有空格*) alert(this.userName.Trim()); //jeff wong (*无空格*) //alert(tmpName); //脚本错误,tmpName未定义 }var test= new thisTest();test.ToString(); //调用原型的ToString()function myTest(){ this.userName= ' test ';}var test1=new myTest();//test1.ToString(); //这里暂时不支持调用ToString()方法//用户定义的对象被赋给新的原型myTest.prototype = new thisTest();test1.ToString(); //调用原型的ToString()
测试结果显示,这里的this指代的是被添加原形(方法或属性)的类的实例,和(3)中的定义基本相似。
(5)、在函数的内部函数中使用this关键字
这个你要是理解作用域和闭包,问题就迎刃而解。看最典型的示例:
function thisTest() { this.userName= 'outer userName'; function innerThisTest(){ var userName="inner userName"; alert(userName); //inner userName alert(this.userName); //outer userName } return innerThisTest; }thisTest()();
分析:thisTest()调用内部的innerThisTest函数,形成一个闭包。innerThisTest执行时,第一次弹出innerUserName,是因为innerThisTest函数作用域内有一个变量叫userName,所以直接弹出当前作用域下变量的指定值;第二次弹出outer userName是因为innerThisTest作用域内没有userName属性(示例中的this.userName),所以它向上一级作用域中找userName属性,这次在thisTest中找到(示例中的this.userName= 'outer userName';),所以弹出对应值。
(6)通过Function的call和apply函数指定特定的this
这个指定来指定去,this就有可能造成“你中有我,我中有你”的局面,不想把自己弄晕了的话,了解一下就可以了。改变this指定对象对于代码维护也是一件很不好的事情。贴出旧文中的示例代码结束吧:
function voidTest() { void (alert("it is a void test")); //执行函数 var oTestNum = 1; void (oTestNum++); //整数自加 alert(oTestNum); oTestNum = 1; void (oTestNum += " void test"); //整数加字符串 alert(oTestNum); } voidTest();
4、在a元素下使用void(0)
(1)适用情况
在网页中,我们经常看到html里的a标签不需要它导航到某一个页面时,href属性设置的写法:
<a href="#">link1</a><a href="javascript:void(0);">link2</a>
注意:第一种“#”的写法(其实#可以是多个,通常都是1个),当a元素所在的链接在浏览器一屏以下时,会导致页面回滚到顶部;所以当我们需要a标签不导航到其他页面,不需要网页位置的回滚,都会采取void(0)那种写法。
(2)ie6下void(0)造成的诡异问题
这个问题网上有很多讨论,个人认为“落叶满长沙”总结的很有代表性,这里就不再赘述了。