而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,带来灵活性的同时,也为初学者带来不少困惑。本文仅就这一问题展开讨论,阅罢本文,读者若能正确回答 JavaScript 中的 What ’s this 问题,作为作者,我就会觉得花费这么多功夫,撰写这样一篇文章是值得的。

我们要记住一句话:this永远指向函数运行时所在的对象!而不是函数被创建时所在的对象。也即:谁调用,指向谁。切记…

本文将分三种情况来分析this对象到底身处何方。

1、普通函数中的this

无论this身处何处,第一要务就是要找到函数运行时的位置。


var name="全局";
function getName(){
var name="局部";
return this.name;
};
alert(getName());

当this出现在全局环境的函数getName中时,此时函数getName运行时的位置在

alert(getName());

显然,函数getName所在的对象是全局对象,即window,因此this的安身之处定然在window。此时的this指向window对象,则getName返回的this.name其实是window.name,因此alert出来的是“全局”!

那么,当this不是出现在全局环境的函数中,而是出现在局部环境的函数中时,又会身陷何方呢?

 var name="全局";
var xpg={
name:"局部",
getName:function(){
return this.name;
}
};
alert(xpg.getName());

其中this身处的函数getName不是在全局环境中,而是处在xpg环境中。无论this身处何处,一定要找到函数运行时的位置。此时函数getName运行时的位置

alert(xpg.getName());

显然,函数getName所在的对象是xpg,因此this的安身之处定然在xpg,即指向xpg对象,则getName返回的this.name其实是xpg.name,因此alert出来的是“局部”!

举个例子巩固一下:

var someone = {
name: "Bob",
showName: function(){
alert(this.name);
}
}; var other = {
name: "Tom",
showName: someone.showName
} other.showName();  //Tom

this关键字虽然是在someone.showName中声明的,但运行的时候是other.showName,所以this指向other.showName函数的当前对象,即other,故最后alert出来的是other.name。

2、闭包中的this

闭包也是个不安分子,本文暂且不对其过于赘述,简而言之:所谓闭包就是在一个函数内部创建另一个函数,且内部函数访问了外部的变量。

浪子this与痞子闭包混在一起,可见将永无宁日啊!

  var name="全局";
var xpg={
name:"局部",
getName:function(){
return function(){
return this.name;
};
}
};
alert(xpg.getName()());

此时的this明显身处困境,竟然处在getName函数中的匿名函数里面,而该匿名函数又调用了变量name,因此构成了闭包,即this身处闭包中。

无论this身处何处,一定要找到函数运行时的位置。此时不能根据函数getName运行时的位置来判断,而是根据匿名函数的运行时位置来判断。

function (){
return this.name;
};

显然,匿名函数所在的对象是window,因此this的安身之处定然在window,则匿名函数返回的this.name其实是window.name,因此alert出来的就是“全局”!

那么,如何在闭包中使得this身处在xpg中呢?—缓存this

  var name="全局";
var xpg={
name:"局部",
getName:function(){
var that=this;
return function(){
return that.name;
};
}
};
alert(xpg.getName()());

在getName函数中定义that=this,此时getName函数运行时位置在

alert(xpg.getName());

则this指向xpg对象,因此that也指向xpg对象。在闭包的匿名函数中返回that.name,则此时返回的that.name其实是xpg.name,因此就可以alert出来 “局部”!

3、new关键字创建新对象

new关键字后的构造函数中的this指向用该构造函数构造出来的新对象:

function Person(__name){
this.name = __name; //这个this指向用该构造函数构造的新对象,这个例子是Bob对象
}
Person.prototype.show = function(){
alert(this.name); //this 指向Person,this.name = Person.name;
} var Bob = new Person("Bob");
Bob.show(); //Bob

4、call与apply中的this

在JavaScript中能管的住this的估计也就非call与apply莫属了。

call与apply就像this的父母一般,让this住哪它就得住哪,不得不听话!当无参数时,当前对象为window

var name="全局";
var xpg={
name:"局部"
};
function getName(){
alert(this.name);
}
getName(xpg);
getName.call(xpg);
getName.call();

其中this身处函数getName中。无论this身处何处,一定要找到函数运行时的位置。此时函数getName运行时的位置

getName(xpg);

显然,函数getName所在的对象是window,因此this的安身之处定然在window,即指向window对象,则getName返回的this.name其实是window.name,因此alert出来的是“全局”!

那么,该call与apply登场了,因为this必须听他们的指挥!

getName.call(xpg);

其中,call指定this的安身之处就是在xpg对象,因为this被迫只能在xpg那安家,则此时this指向xpg对象, this.name其实是xpg.name,因此alert出来的是“局部”!

5、eval中的this

对于eval函数,其执行时候似乎没有指定当前对象,但实际上其this并非指向window,因为该函数执行时的作用域是当前作用域,即等同于在该行将里面的代码填进去。下面的例子说明了这个问题:

var name = "window";

var Bob = {
name: "Bob",
showName: function(){
eval("alert(this.name)");
}
}; Bob.showName(); //Bob

6、没有明确的当前对象时的this

当没有明确的执行时的当前对象时,this指向全局对象window。

例如对于全局变量引用的函数上我们有:

var name = "Tom";

var Bob = {
name: "Bob",
show: function(){
alert(this.name);
}
} var show = Bob.show;
show();  //Tom

你可能也能理解成show是window对象下的方法,所以执行时的当前对象时window。但局部变量引用的函数上,却无法这么解释:

var name = "window";

var Bob = {
name: "Bob",
showName: function(){
alert(this.name);
}
}; var Tom = {
name: "Tom",
showName: function(){
var fun = Bob.showName;
fun();
}
}; Tom.showName();  //window

在浏览器中setTimeout、setInterval和匿名函数执行时的当前对象是全局对象window,这条我们可以看成是上一条的一个特殊情况。

var name = "Bob";
var nameObj ={
name : "Tom",
showName : function(){
alert(this.name);
},
waitShowName : function(){
setTimeout(this.showName, 1000);
}
}; nameObj.waitShowName();

所以在运行this.showName的时候,this指向了window,所以最后显示了window.name。

7、dom事件中的this

(1)你可以直接在dom元素中使用

<input id="btnTest" type="button" value="提交" onclick="alert(this.value))" />

分析:对于dom元素的一个onclick(或其他如onblur等)属性,它为所属的html元素所拥有,直接在它触发的函数里写this,this应该指向该html元素。

(2)给dom元素注册js函数

a、不正确的方式

<script type="text/javascript">
function thisTest(){
alert(this.value); // 弹出undefined, this在这里指向??
}
</script> <input id="btnTest" type="button" value="提交" onclick="thisTest()" />

分析:onclick事件直接调用thisTest函数,程序就会弹出undefined。因为thisTest函数是在window对象中定义的,

所以thisTest的拥有者(作用域)是window,thisTest的this也是window。而window是没有value属性的,所以就报错了。

b、正确的方式

<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直接弹出它们对应的触发函数:

<input id="btnTest1" type="button" value="提交1" onclick="thisTest()" />
<input id="btnTest2" type="button" value="提交2" /> <script type="text/javascript">
function thisTest(){
this.value="提交中";
}
var btn=document.getElementById("btnTest1");
alert(btn.onclick); //第一个按钮函数 var btnOther=document.getElementById("btnTest2");
btnOther.onclick=thisTest;
alert(btnOther.onclick); //第二个按钮函数
</script>

其弹出的结果是:

//第一个按钮
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>

这是因为我们使用了函数引用的方式,程序就只会给函数的本体分配内存,而引用只分配指针。这样写一个函数,调用的地方给它分配一个(指针)引用,这样效率就高很多。当然,如果你觉得这样注册事件不能兼容多种浏览器,可以写下面的注册事件的通用脚本:

//js事件 添加 EventUtil.addEvent(dom元素,事件名称,事件触发的函数名) 移除EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名)
var EventUtil = new eventManager(); //js事件通用管理器 dom元素 添加或者移除事件
function eventManager() {
//添加事件
//oDomElement:dom元素,如按钮,文本,document等; ****** oEventType:事件名称(如:click,如果是ie浏览器,自动将click转换为onclick);****** oFunc:事件触发的函数名
this.addEvent = function(oDomElement, oEventType, oFunc) {
//ie
if (oDomElement.attachEvent) {
oDomElement.attachEvent("on" + oEventType, oFunc);
}
//ff,opera,safari等
else if (oDomElement.addEventListener) {
oDomElement.addEventListener(oEventType, oFunc, false);
}
//其他
else {
oDomElement["on" + oEventType] = oFunc;
}
} this.removeEvent = function(oDomElement, oEventType, oFunc) {
//ie
if (oDomElement.detachEvent) {
oDomElement.detachEvent("on" + oEventType, oFunc);
}
//ff,opera,safari等
else if (oDomElement.removeEventListener) {
oDomElement.removeEventListener(oEventType, oFunc, false);
}
//其他
else {
oDomElement["on" + oEventType] = null;
}
}
}

正像注释写的那样,要注册dom元素事件,用EventUtil.addEvent(dom元素,事件名称,事件触发的函数名)即可, 移除时可以这样写:EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名)。这是题外话,不说了。


系列文章导航:

1、你不知道的JavaScript–Item1 严格模式

2、你不知道的JavaScript–Item2 浮点数精度

3、你不知道的JavaScript–Item3 隐式强制转换

4、你不知道的JavaScript–Item4 基本类型和基本包装类型(引用类型)

5、你不知道的JavaScript–Item5 全局变量

6、你不知道的JavaScript–Item6 var预解析与函数声明提升(hoist )

7、你不知道的JavaScript–Item7 函数和(命名)函数表达式

8、你不知道的JavaScript–Item8 函数,方法,构造函数调用

9、你不知道的JavaScript–Item9 call(),apply(),bind()与回调

10、你不知道的JavaScript–Item10 闭包(closure)

11、你不知道的JavaScript–Item11 arguments对象

12、你不知道的JavaScript–Item12 undefined 与 null

13、你不知道的JavaScript–Item13 理解 prototype, getPrototypeOf 和_ proto_

14、你不知道的JavaScript–Item14 使用prototype的几点注意事项

15、你不知道的JavaScript–Item15 prototype原型和原型链详解

16、你不知道的JavaScript–Item16 for 循环和for…in 循环的那点事儿

17、你不知道的JavaScript–Item17 循环与prototype最后的几点小tips

18、你不知道的JavaScript–Item18 JScript的Bug与内存管理

19、你不知道的JavaScript–Item19 执行上下文(execution context)

20、你不知道的JavaScript–Item20 作用域与作用域链(scope chain)

21、你不知道的JavaScript–Item21 漂移的this


持续更新中……………….

版权声明:本文为小平果原创文章,转载请注明:http://blog.csdn.net/i10630226

你不知道的JavaScript--Item21 漂移的this的更多相关文章

  1. 《你不知道的JavaScript》系列分享专栏

    <你不知道的JavaScript>系列分享专栏 你不知道的JavaScript”系列就是要让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部 ...

  2. 《你不知道的JavaScript》整理(二)——this

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,这次研究了一下“this”. 当一个函数被调用时,会创建一个活动记录(执行上下文). 这个记录会包含函 ...

  3. 《你不知道的JavaScript》整理(一)——作用域、提升与闭包

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 ...

  4. 你不知道的Javascript(上卷)读书笔记之一 ---- 作用域

    你不知道的Javascript(上卷)这本书在我看来是一本还不错的书籍,这本书用比较简洁的语言来描述Js的那些"坑",在这里写一些博客记录一下笔记以便消化吸收. 1 编译原理 在此 ...

  5. 你不知道的JavaScript上卷笔记

    你不知道的JavaScript上卷笔记 前言 You don't know JavaScript是github上一个系列文章   初看到这一标题的时候,感觉怎么老外也搞标题党,用这种冲突性比较强的题目 ...

  6. 【读书笔记】-- 你不知道的JavaScript

    <你不知道的JavaScript>是一个不错的JavaScript系列书,书名可能有些标题党的意思,但实符其名,很多地方会让你有耳目一新的感觉. 1.typeof null === &qu ...

  7. 读书笔记-你不知道的JavaScript(上)

    本文首发在我的个人博客:http://muyunyun.cn/ <你不知道的JavaScript>系列丛书给出了很多颠覆以往对JavaScript认知的点, 读完上卷,受益匪浅,于是对其精 ...

  8. 读《你不知道的JavaScript(上卷)》后感-作用域闭包(二)

    github原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们, ...

  9. 你不知道的javaScript上卷(第一章 作用域是什么)

    在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉. ...

  10. JavaScript中的this(你不知道的JavaScript)

    JavaScript中的this,刚接触JavaScript时大家都在大肆渲染说其多么多么的灵巧重要,然而自己并不关心:随着自己对JavaScript一步步深入了解,突然恍然大悟,原来它真的很重要!所 ...

随机推荐

  1. android 防止反编译的若干方法

    第一种方式:混淆策略 混淆策略是每个应用必须增加的一种防护策略,同时他不仅是为了防护,也是为了减小应用安装包的大小,所以他是每个应用发版之前必须要添加的一项功能,现在混淆策略一般有两种: 对代码的混淆 ...

  2. Leetcode_232_Implement Queue using Stacks

    本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/48392363 Implement the followin ...

  3. Android UI之View的加载机制(二)

    转载请标明出处:http://blog.csdn.net/sk719887916/article/details/39961201,作者:skay 对于接触安卓开不到一年的自己来说,总结下view的生 ...

  4. MongoDB学习笔记(四)

    第四章 Mongodb聚合函数 插入 测试数据 for(var j=1;j<3;j++){ for(var i=1;i<3;i++){ var person={ Name:"ja ...

  5. Java + Selenium + TestNG + Maven

    环境准备: 1. Java: Install Java jdk: Version: java 1.8 or aboveConfigure Java Environment Variables:Add ...

  6. html5中的网页结构

    一.html5中的大纲 在html5中,使用各种结构元素所描述出来的整个网页的层次结构,就是该网页的大纲.因此在组织这份大纲的时候,不能使用div元素,因为div元素只能当做容器,用在需要对网页中某个 ...

  7. Spring多数据源解决方案

    Figure 2 多数据源的选择逻辑渗透至客户端 解决方案 Figure 3 采用Proxy模式来封转数据源选择逻辑 通过采用Proxy模式我们在方案实现中实现一个虚拟的数据源.并且通过它来封装数据源 ...

  8. 《MySQL必知必会》读书笔记_3

    PS:这次的信息量有点大. 聚集不同值 SELECT AVG(DISTINCT prod_price) AS avg_price FROM products WHERE vend_id = 1003 ...

  9. volatile的适用场景

    volatile保证线程间的数据是可见的(共享的),但不保证数据同步 volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有 ...

  10. 学习MACD指标

    概念 MACD叫指数平滑异同移动平均线指标. 零轴 MACD柱线 DIFF线 DEA线 使用 一般出现如下情形,股价处于或即将进入上涨趋势中: MACD指标在零轴上方出现金叉,其后DIFF快线一直位于 ...