Javascript this关键字使用分析


Posted in Javascript onOctober 21, 2008

关于js中的this关键字的文章已经不少了,我看过几篇,我写这篇文章的目的是从实例中分析出this的工作原理,希望对大家有所帮助。

一、基本的:

function doSomething(){ 
alert(this.id); 
} 
alert(window.doSomething);//证明了doSomething是属于window的 
doSomething();//undefined 
window.onload = function(){ 
document.getElementById("div2").onclick = doSomething;//div2 
document.getElementById("div3").onclick = function(){doSomething();}//undefined 
}

1、对于doSomething这个函数:
function doSomething(){ 
alert(this.id); 
}

这个函数是全局函数,这种全局函数实际上是属于window的(可以通过window.doSomething来访问),如果直接调用,那么根据“this always refers to the “owner” of the function we're executing”,那么函数中的this就是window,但是window没有id属性,所以显示“undefined”;

2、在html元素中这样调用

<div id="div1" onclick="doSomething();">div1</div>

这时也会显示“undefined”,这就相当于如下代码:

document.getElementById("div1").onclick = function(){doSomething();}

当点击div1时,调用属于window的doSomething函数,所以也是显示“undefined”;

3、通过js来绑定事件,在div2载入过后:

document.getElementById("div2").onclick = doSomething;

当点击div2时,显示“div2”,因为在给div2的onclick赋值,是将doSomething拷贝了一次,这时拷贝的这个函数是属于div2的了,跟属于window的doSomething没有任何关系了。点击div2时,就会触发属于div2的doSomething,这里的this就是指div2。

二、attachEvent和addEventListener

attachEvent是在ie中绑定事件的方法,会将相应函数拷贝到全局(即响应函数的owner为window),但是在DOM标准中,addEventListener绑定的事件时拷贝的响应函数的owner为事件所绑定的对象

function doSomething(){ 
alert(this.id); 
alert(this == window); 
} 
window.onload = function(){ 
var div1 = document.getElementById("div1"); 
if(div1.attachEvent){ 
div1.attachEvent("onclick",doSomething); 
document.body.appendChild(document.createTextNode("attachEvent")); 
}else if(div1.addEventListener){ 
div1.addEventListener("click",doSomething,false); 
document.body.appendChild(document.createTextNode("addEventListener")); 
}else{ 
div.onclick = doSomething; 
} 
}

对于函数doSomething

function doSomething(){ 
alert(this.id); 
alert(this == window); 
}

1、使用attachEvent绑定到div1的click事件上,doSometing会被复制到window,这时doSomething里面的this指的是window,点击div1时会显示“undefined”和“true”

2、使用addEventListener绑定div1的click事件,这时将doSomething拷贝,这个拷贝过后的函数是属于div1的,所以点击div1时会显示“div1”和“false”

注:http://www.quirksmode.org/js/this.html里认为attachEvent只是使用了函数的引用,看如下代码:

var obj = new Object(); 
obj.color = "black"; 
obj.showColor = function(){ 
alert(this.color); 
alert(this == window); 
} 
obj.showColor(); var div1 = document.getElementById("div1"); 
div1.attachEvent("onclick",obj.showColor);

此时点击div1的时候,会显示“undefined”和“true”,如果attachEvent仅仅是引用obj.showColor的话,那么this还是应该指的是obj,但是实际上这里this指的是window,所以我认为这里不是引用,而是拷贝到全局的。

三、关于对象冒充的继承方式

1、new与不new的区别

对于如下function

function ClassA(sColor){ 
this.color = sColor; 
this.sayColor = function(){ 
alert(this.color); 
} 
}

这是一个类还是一个函数?随你而定!

如果你认为这是一个函数,那么我们可以这样来调用它:

ClassA("red");
“red”是传递的一个参数,ClassA中的this指的是当然就是指的window了,所以现在window有了color属性和sayColor方法,并且color有“red”这个值。

这是调用sayColor或者window.sayColor都可以显示“red”;

window.sayColor();

如果你认为这是一个类,那么我们应该这样使用它:

var obj = new ClassA("red");
new这个关键词的出现让上面这一句代码增加了不少内容:首先,创建一个Object实例,然后,将ClassA中的this指向创建的这个Object中,最后返回这个Object,所以返回的这个Object就赋值给了obj。所以我们可以说this指向的是obj,obj拥有了color属性和sayColor方法,并且color属性值为“red”。

2、函数的owener

function showId(){ 
alert(this.id); 
} 
window.onload = function(){ 
var div1 = document.getElementById("div1"); 
div1.onclick = showId; 
div1.show = showId; 
div1.show(); var obj = new Object(); 
obj.id = "obj"; 
obj.show = showId; 
obj.show(); 
}

我们可以将showId这个函数赋值给click事件,也可以赋值给任何一个对象的任何一个属性,这是也会拷贝showId这个方法的,所以我们在调用div1.show方法时,this是指向div1的,在调用obj.show时,this指向的是obj的。

3、对象冒充的原理

下面的代码是通过对象冒充方法实现的继承

function ClassA(sColor){ 
this.color = sColor; 
this.sayColor = function(){ 
alert(this.color); 
} 
} function ClassB(sColor,sName){ 
this.newMethod = ClassA; 
this.newMethod(sColor); 
delete this.newMethod; 
this.name = sName; 
this.sayName = function(){ 
alert(this.name); 
} 
} 
var objB = new ClassB("color of objB","name of objB"); 
objB.sayColor();

objB是ClassB的一个实例,objB是如何拥有color属性和sayColor方法的呢?

首先从实例化的代码看起:

var objB = new ClassB("color of objB","name of objB");

这里ClassB是个类,ClassB中的this当然就是指的objB这个对象;

在ClassB中,前三行代码会用到ClassA,这时就把ClassA看作一个函数,而不是类。

我们如果直接调用ClassA这个函数,那么很显然,ClassA中的this指的就是window对象了,所以我们先将ClassA拷贝到objB的newMethod这个属性中(this.newMethod = ClassA),

然后再调用this.newMethod,这是这个方法的owener明显的已经成了this,而ClassB中的this在当前指的是objB,所以此时ClassA中(严格的说是newMethod中,因为这是拷贝过后的,跟ClassA已经是两个方法了)的this就是指的objB,这样在通过newMethod的调用,就给objB赋值了color属性和sayColor方法。用call和apply方法来实现继承实际上也是一个原理,call和apply可以看作是改变方法的owner的方法,而这里ClassB中的前三句代码也就是起这个作用的。

四、prototype1.6中的Class.create

prototype1.6中的Class.create方法大致如下: var Class = { 
create: function() { 
// 
function klass() { 
this.initialize.apply(this, arguments); 
} 
// 
for (var i = 0; i < properties.length; i++) 
klass.addMethods(properties[i]); 
// 
return klass; 
} 
};

在使用的时候是这样的:
var Person = Class.create({ 
initialize:function(name){ 
this.name = name; 
}, 
say:function(message){ 
alert(this.name + ":" + message); 
} 
}); var aPerson = new Person("name1"); 
aPerson.say("hello1");

Person实际上是通过Class.create这个方法所返回的klass(klass是Class.create中的局部变量,是一个function),Class.create所传递的参数(initialize方法和say方法)传递到create方法中的properties数组中并且通过addMethods方法让klass的prototype拥有这些方法。那么最关键的地方也是最难以理解的地方是:klass中的this究竟是指的是什么。仔细想一想就不难得到答案,Person实际上就是klass,而我们在实例化Person对象的时候,是用了new关键词的:

var aPerson = new Person("name1");

这就等价于

var aPerson = new klass("name1");

虽然klass在外面不能被访问到,但是这样能很轻易的说明问题,klass是一个类而不是简单的一个函数(我们看作如此,因为用了new关键字),那么klass中的this就指的是声明的实例,在这里就是aPerson,aPerson通过klass的prototype能够拥有initialize方法和say方法,在new的过程中,也会执行klass中的代码,所以initialize在实例化的时候会执行,即构造函数。(在klass里两个this都是指的aPerson,为什么还要通过apply调用一次呢?这主要是为了传递构造函数的参数,用apply方法可以将数目不定的多个参数通过数组方便的传到initialize方法中去。)

五、再分析几个例子

从别的文章里看到的例子,我在这里分析一下:

1、运行如下代码

function OuterFoo(){ 
this.Name = 'Outer Name'; function InnerFoo(){ 
var Name = 'Inner Name'; 
alert(Name + ', ' + this.Name); 
} 
return InnerFoo; 
} 
OuterFoo()();

所显示的结果是“Inner Name, Outer Name”

OuterFoo是一个函数(而不是类),那么第一句

this.Name = 'Outer Name';

中的this指的是window对象,所以OuterFoo()过后window.Name = ‘Outer Name';

并且将InnerFoo返回,此时InnerFoo同样是一个函数(不是类),执行InnerFoo的时候,this同样指window,所以InnerFoo中的this.Name的值为”Outer Name”(window.Name充当了一个中转站的角色,让OuterFoo能够向InnerFoo传递“Outer Name”这个值),而Name的值即为局部变量”Inner Name”

2、运行如下代码

function JSClass(){ this.m_Text = 'division element'; 
this.m_Element = document.createElement('DIV'); 
this.m_Element.innerHTML = this.m_Text; 
if(this.m_Element.attachEvent) 
this.m_Element.attachEvent('onclick', this.ToString); 
else if(this.m_Element.addEventListener) 
this.m_Element.addEventListener('click', this.ToString,false); 
else 
this.m_Element.onclick = this.ToString; 
} 
JSClass.prototype.Render = function(){ 
document.body.appendChild(this.m_Element); 
} 
JSClass.prototype.ToString = function(){ 
alert(this.m_Text); 
alert(this == window); 
} 
window.onload = function(){ 
var jc = new JSClass(); 
jc.Render(); 
jc.ToString(); 
}

点击“division element”会显示“undefined”,在ie下还要显示“true”,其他浏览器中还要显示“false”。

实例声明和调用实例方法都没什么可说的,元素的click事件的绑定到了一个实例的方法,那么通过addEventListener绑定到的方法是拷贝过后的,所以this指的是html元素,这个元素没有m_Text属性(m_Text属性是属于JSClass的实例的,即属于jc的),所以点击元素显示undefined,attachEvent绑定的事件会将函数复制到全局,此时this指的是window对象,点击元素也会显示“undefined”。只有在调用jc.ToString()方法是,this指的是jc这个对象,因为jc拥有m_Text,所以能够显示“division element”。

六、总结

怎样在一个代码环境中快速的找到this所指的对象呢?我想要注意以下三个方面:
1、 要清楚的知道对于函数的每一步操作是拷贝还是引用(调用)
2、 要清楚的知道函数的拥有者(owner)是什么
3、 对于一个function,我们要搞清楚我们是把它当作函数使用还是在当作类使用

补充:
1.在实例和类上都可以直接定义函数
2.不能在实例上使用prototype定义函数,只能在类上使用prototype定义函数
3.类上直接定义的函数不能使用this访问对象的属性
4.在类的prototype上建立的函数可以用this,在类内部定义的函数可以使用this,在对象实例上建立的函数额可以this

window.alert=function (msg) 
{ 
document.write(msg+"<br>"); 
} 
function say() 
{ 
this.f="props"; 
this.func3=function(){alert("f3,"+this.f);} 
} 
say.func1=function(){alert("func1,"+this.f);}; //Error,类上直接定义的函数,不能使用this 
say.prototype.func2=function(){alert("func2,"+this.f);} 
say.func1(); 
(new say()).func2(); 
say.func2(); //Error, 在用prototype定义的函数,必须实例化对象才能调用 
say.func3(); //Error,在类上定义的函数,必须实例化才能调用 
(new say()).func3(); 
var obj={ 
fld1:10, 
func1:function(msg){alert(msg);}, 
func4:function(){alert(this.fld1);} 
} 
obj.prototype.func=function(){alert("func");}; //Error 实例对象上不能使用prototype定义对象 
obj.func2=function(){alert("func2,"+this.fld1);}; //ok,实例上直接定义的函数可以使用this,访问对象的属性 
alert(obj.fld1); 
obj.func1("func1"); 
obj.func2(); 
obj.func4();

javascript this用法小结
https://3water.com/article/16863.htm

JavaScript this 深入理解
https://3water.com/article/19425.htm

JAVASCRIPT THIS详解 面向对象
https://3water.com/article/17584.htm

Javascript this指针
https://3water.com/article/19434.htm

JavaScript中this关键字使用方法详解
https://3water.com/article/7954.htm

Javascript 相关文章推荐
使用prototype.js进行异步操作
Feb 07 Javascript
Prototype Selector对象学习
Jul 23 Javascript
JavaScript 页面编码与浏览器类型判断代码
Jun 03 Javascript
jquery实现聚光灯效果的方法
Feb 06 Javascript
Jquery网页内滑动缓冲导航的实现代码
Apr 05 Javascript
js实现倒计时关键代码
May 05 Javascript
vue用addRoutes实现动态路由的示例
Sep 15 Javascript
最新Javascript程序员面试试题和解题方法
Nov 23 Javascript
30分钟快速入门掌握ES6/ES2015的核心内容(上)
Apr 18 Javascript
微信小程序webview与h5通过postMessage实现实时通讯的实现
Aug 20 Javascript
three.js利用卷积法如何实现物体描边效果
Nov 27 Javascript
vue首次渲染全过程
Apr 21 Vue.js
JQuery AJAX实现目录浏览与编辑的代码
Oct 21 #Javascript
JavaScript confirm选择判断
Oct 18 #Javascript
javascript脚本编程解决考试分数统计问题
Oct 18 #Javascript
提高网站信任度的技巧
Oct 17 #Javascript
javascript检查日期格式的函数[比较全]
Oct 17 #Javascript
JAVASCRIPT下判断IE与FF的比较简单的方式
Oct 17 #Javascript
jQuery弹出层插件简化版代码下载
Oct 16 #Javascript
You might like
PHP版国家代码、缩写查询函数代码
2011/08/14 PHP
无需重新编译php加入ftp扩展的解决方法
2013/02/07 PHP
学习php设计模式 php实现命令模式(command)
2015/12/08 PHP
PhpStorm terminal无法输入命令的解决方法
2016/10/09 PHP
php连接mysql数据库最简单的实现方法
2019/09/24 PHP
Prototype 1.5.0_rc1 及 Prototype 1.5.0 Pre0小抄本
2006/09/22 Javascript
javascript动态添加表格数据行(ASP后台数据库保存例子)
2010/05/08 Javascript
js+css使DIV始终居于屏幕中间 左下 左上 右上 右下的代码集合
2011/03/10 Javascript
js显示时间 js显示最后修改时间
2013/01/02 Javascript
php跨域调用json的例子
2013/11/13 Javascript
Javascript中call的两种用法实例
2013/12/13 Javascript
使用documentElement正确取得当前可见区域的大小
2014/07/25 Javascript
js中setTimeout()与clearTimeout()用法实例浅析
2015/05/12 Javascript
HTML5 Shiv完美解决IE(IE6/IE7/IE8)不兼容HTML5标签的方法
2015/11/25 Javascript
全面了解javascript中的错误处理机制
2016/07/18 Javascript
Angularjs 实现动态添加控件功能
2017/05/25 Javascript
Vue.js实现分页查询功能
2020/11/15 Javascript
JavaScript函数的特性与应用实践深入详解
2018/12/30 Javascript
vue动画—通过钩子函数实现半场动画操作
2020/08/09 Javascript
[00:28]DOTA2北京网鱼队选拔赛
2015/04/08 DOTA
简介Python设计模式中的代理模式与模板方法模式编程
2016/02/02 Python
CentOS6.5设置Django开发环境
2016/10/13 Python
python正则表达式之对号入座篇
2018/07/24 Python
Python实现的对一个数进行因式分解操作示例
2019/06/27 Python
利用python计算时间差(返回天数)
2019/09/07 Python
Python如何访问字符串中的值
2020/02/09 Python
python+selenium定时爬取丁香园的新型冠状病毒数据并制作出类似的地图(部署到云服务器)
2020/02/09 Python
详解django使用include无法跳转的解决方法
2020/03/19 Python
下述程序的作用是计算机数组中的最大元素值及其下标
2012/11/26 面试题
中式餐厅创业计划书范文
2014/01/23 职场文书
小学生节约用水倡议书
2014/05/15 职场文书
创先争优一句话承诺
2014/05/29 职场文书
水电施工员岗位职责
2015/04/11 职场文书
大学班干部竞选稿
2015/11/20 职场文书
企业团队精神心得体会
2016/01/19 职场文书
2016年入党心得体会范文
2016/01/23 职场文书