Javascript中this关键字详解

Quiz

请看下面的代码,最后alert出来的是什么呢?

复制代码
 1 var name = "Bob";  
 2 var nameObj ={  
 3     name : "Tom",  
 4     showName : function(){  
 5         alert(this.name);  
 6     },  
 7     waitShowName : function(){  
 8         setTimeout(this.showName, 1000);  
 9     }  
10 };  
11 
12 nameObj.waitShowName();
复制代码

要解决这个问题我们需要了解Javascript的this关键字的用法。

 

this指向哪里?

一般而言,在Javascript中,this指向函数执行时的当前对象。

In JavaScript, as in most object-oriented programming languages, this is a special keyword that is used within methods to refer to the object on which a method is being invoked.

——jQuery Fundamentals (Chapter 2), by Rebecca Murphey

值得注意,该关键字在Javascript中和执行环境,而非声明环境有关。

The this keyword is relative to the execution context, not the declaration context.

我们举个例子来说明这个问题:

复制代码
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。

 

没有明确的当前对象时

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

By default, this refers to the global object.

为什么说是全局对象(the global object),因为非浏览器情况下(例如:nodejs)中全局变量并非window对象,而就是叫“全局变量”(the global object)。不过由于我们这片文章主要讨论的是前端开发知识,所以nodejs就被我们忽略了。

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

复制代码
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和匿名函数

文章开头的问题的答案是Bob。

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

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

浏览器中全局变量可以当成是window对象下的变量,例如全局变量a,可以用window.a来引用。

我们将代码改成匿名函数可能更好理解一些:

复制代码
var name = "Bob";  
 var nameObj ={  
     name : "Tom",  
     showName : function(){  
         alert(this.name);  
     },  
     waitShowName : function(){  
         function(__callback){
            __callback();
        }(this.showName);  
     }  
 };  
 
 nameObj.waitShowName();  //Bob
复制代码

在调用nameObj.waitShowName时候,我们运行了一个匿名函数,将nameObj.showName作为回调函数传进这个匿名函数,然后匿名函数运行时,运行这个回调函数。由于匿名函数的当前对象是window,所以当在该匿名函数中运行回调函数时,回调函数的this指向了window,所以alert出来window.name。

由此看来setTimeout可以看做是一个延迟执行的:

function(__callback){
    __callback();
}

setInterval也如此类比。

但如果我们的确想得到的回答是Tom呢?通过一些技巧,我们能够得到想要的答案:

复制代码
var name = "Bob";  
var nameObj ={  
    name : "Tom",  
    showName : function(){  
        alert(this.name);  
    },  
    waitShowName : function(){
        var that = this;
        setTimeout(function(){
            that.showName();
        }, 1000);
    }
}; 
 
 nameObj.waitShowName();  //Tom
复制代码

在执行nameObj.waitShowName函数时,我们先对其this赋给变量that(这是为了避免setTimeout中的匿名函数运行时,匿名函数中的this指向window),然后延迟运行匿名函数,执行that.showName,即nameObj.showName,所以alert出正确结果Tom。

 

eval

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

复制代码
var name = "window";

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

Bob.showName();    //Bob
复制代码

 

apply和call

apply和call能够强制改变函数执行时的当前对象,让this指向其他对象。因为apply和call较为类似,所以我们以apply为例:

复制代码
var name = "window";
    
var someone = {
    name: "Bob",
    showName: function(){
        alert(this.name);
    }
};

var other = {
    name: "Tom"
};    

someone.showName.apply();    //window
someone.showName.apply(other);    //Tom
复制代码

apply用于改变函数执行时的当前对象,当无参数时,当前对象为window,有参数时当前对象为该参数。于是这个例子Bob成功偷走了Tom的名字。

 

new关键字

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

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

var Bob = new Person("Bob");
Bob.show();        //Bob
复制代码

 

思考题

1.  请问下面代码会alert出什么,为什么?

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

2.  请问下面代码会alert出什么,为什么?

var fun = new Function("alert(this)");
fun();

3.  下面代码分别在IE和其他浏览器上运行有什么差异,可以用什么方法解决这个差异问题?

IE:

复制代码
<button id = "box" name = "box">Click Me!</button>

<script>
    var name = "window";

    function showName(){
        alert(this.name);
    }

    document.getElementById("box").attachEvent("onclick", showName);
</script>
复制代码

Others:

复制代码
<button id = "box" name = "box">Click Me!</button>

<script>
    var name = "window";

    function showName(){
        alert(this.name);
    }

    document.getElementById("box").addEventListener("click", showName, false);
</script>
复制代码

 

  1. #1楼 林J  2012-11-01 08:59
    支持一记,很有用。
  2. #2楼 bluescreen  2012-11-01 09:01
    1.第一个思考题你代码是不是错了? setTimeout("that.showName();", 1000); 这句会报错吧!如有不对指出还望谅解指证!谢谢!
    是不是该这样呢!
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var  name = "Bob"
    var  nameObj ={ 
         name : "Tom"
         showName : function (){ 
             alert( this .name); 
         }, 
         waitShowName : function (){
             var  that = this ;
             setTimeout(that.showName(), 1000);
         }
    };
      
    nameObj.waitShowName();
  3. #3楼[楼主] Justany_WhiteSnow  2012-11-01 09:16
    @bluescreen
    引用 1.第一个思考题你代码是不是错了? setTimeout("that.showName();", 1000); 这句会报错吧!如有不对指出还望谅解指证!谢谢!
    是不是该这样呢!

    答案就是会报错,有兴趣的话考虑下为什么吧。

    哦,刚没看你的代码。你的代码是没什么意义的。因为that.showName在传进setTimeout之前已经执行了。
  4. #4楼 bluescreen  2012-11-01 09:29
    @Justany_WhiteSnow
    引用 @bluescreen
    引用引用1.第一个思考题你代码是不是错了? setTimeout("that.showName();", 1000); 这句会报错吧!如有不对指出还望谅解指证!谢谢!
    是不是该这样呢!

    答案就是会报错,有兴趣的话考虑下为什么吧。

    哦,刚没看你的代码。你的代码是没什么意义的。因为that.showName在传进setTimeout之前已经执行了。


    对!是that.showName在传进setTimeout之前已经执行了。无论时间如何设置都会!
  5. #5楼 林J  2012-11-01 09:32
    @Justany_WhiteSnow
    会报错的原因是因为setTimeout对象是windows,相当于执行的是window.that.showName(),这里that没有定义?
    这样的话这个题目没什么意义啊。我是想看如何用setTimeout弹出Tom的。。
  6. #6楼[楼主] Justany_WhiteSnow  2012-11-01 09:37
    @林J
    引用 @Justany_WhiteSnow
    会报错的原因是因为setTimeout对象是windows,相当于执行的是window.that.showName(),这里that没有定义?
    这样的话这个题目没什么意义啊。我是想看如何用setTimeout弹出Tom的。。

    弹出Tom的方法,原文有啊。
    也不能说没有意义。思考题1是为了对比:
    setTimeout(函数名, 延迟)
    setTimeout(匿名函数, 延迟)
    setTimeout(字符串代码, 延迟)
    这是三种方法的差异的。
  7. #7楼 林J  2012-11-01 09:45
    @Justany_WhiteSnow
    哦对。顺便请教楼主一个问题啊:setTimeout(that.showName(),1000),setTimeout(that.showName,1000),
    setTimeout("that.showName()",1000)这三种写法在解析的时候有什么区别呢?我只知道加‘()’的会直接执行,但是不知道解析器究竟干了什么。
  8. #8楼[楼主] Justany_WhiteSnow  2012-11-01 10:02
    @林J
    引用 @Justany_WhiteSnow
    哦对。顺便请教楼主一个问题啊:setTimeout(that.showName(),1000),setTimeout(that.showName,1000),
    setTimeout("that.showName()",1000)这三种写法在解析的时候有什么区别呢?我只知道加‘()’的会直接执行,但是不知道解析器究竟干了什么。

    1.setTimeout(that.showName(),1000)
    that.showName是函数引用,that.showName()是函数运行。这种传递方式真正传进去的是that.showName函数的返回值。
    2.setTimeout(that.showName,1000)
    个人理解是相当于一个延迟执行的
    1
    2
    3
    (function(__callback){
         __callback();
    })(that.showName);

    这个我已在正文说明了。
    3.setTimeout("that.showName()",1000)
    相当于一个延迟执行的
    1
    ( new  Function( "that.showName()" ))()


    具体来说setTimeout(字符串代码, 延迟)是在一定延迟之后用字符串代码创建一个新的函数,因为该函数没有明确的当前对象,所以this指向全局变量。当然这里还有一个问题为什么that没法正常引用,因为他重新建立一个执行环境(context),且这个执行环境和原来执行环境无关。

    我想思考题1和2的答案也很清晰了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值