JavaScript中的this关键字

时间:2021-07-18 19:16:10

下文翻译自:http://davidshariff.com/blog/javascript-this-keyword/#first-article

 

“this”关键字是JavaScript中的一个常见特性,但它通常也是该语言中最混乱和容易误解的特性之一。“this”究竟意味着什么,它又是如何定义的呢?

本文试图澄清困惑并以清晰的方式解析答案。

“this”关键字对于已经使用过其他编程语言的人并不陌生,通常它指的是,通过构造函数实例化类时创建的新对象。例如,如果我有一个类Boat(),它有一个方法moveBoat(),当在moveBoat()方法中引用“this”时,我们实际上是访问新创建的Boat()对象。

在JavaScript中,当使用“new”关键字调用它时,我们在Function构造函数中也有这个概念,但它不是唯一的规则,“this”通常可以引用不同的执行上下文中的不同对象。如果您不熟悉JavaScript的执行上下文,我建议您在此处阅读我关于该主题的其他帖子。说的差不多了,让我们来看一些JavaScript的示例:

// global scope

foo = 'abc';
alert(foo); // abc

this.foo = 'def';
alert(foo); // def

任何时候,在全局上下文(不在函数体内)中使用"this"关键字,this总是指向全局对象。现在,我们来看一下this在函数体内的表现:

var boat = {
    size: 'normal',
    boatInfo: function() {
        alert(this === boat);
        alert(this.size);
    }
};

boat.boatInfo(); // true, 'normal'

var bigBoat = {
    size: 'big'
};

bigBoat.boatInfo = boat.boatInfo;
bigBoat.boatInfo(); // false, 'big'

所以上面的"this"如何确定呢?起码可以看见一个boat对象,其中有一个size属性和一个boatInfo()函数。boatInfo()函数里面,弹出this对象是否是boat对象的结果,和this对象中的size属性。然后,我们用boat.boatInfo()的方式调用boatInfo()函数可以看见this对象是boat对象和boat中的size属性的值“normal”。

接着我们创建另一个对象bigBoat,其中有一个值为big的size属性。然而,bigBoat对象中不存在boatInfo()方法,所以我们使用bigBoat.boatInfo = boat.boatInfo方式从boat对象中复制方法。现在,当我们调用bigBoat.boatInfo()时,发现this对象不在等于boat对象,this.size属性也变成了big。为什么会这样呢?boatInfo()内部的值是怎么改变的呢?

首先你需要了解的是在任何函数内部,其this对象的值都不是静态不变的,它的值总是在每次调用函数时确定,但在函数实际执行函数体代码之前。函数内部this的值实际上是由调用该函数的父作用域提供的。而更重要的是,这样的函数语法实际上是如何编写。

当一个函数被调用时,我们必须查看我们必须查看括号/括弧“()”的左侧。如果我们可以在括号的左侧看到一个引用,那么传递给函数调用的“this”值正是该属于该对象的值,否则“this”值便是全局对象。我们来看一些例子:

function bar() {
    alert(this);
}
bar(); // global - 因为方法bar()被调用时属于global对象(被全局对象调用)
var foo = {
    baz: function() {
        alert(this);
    }
}
foo.baz(); // foo -因为方法baz()被调用时属于foo对象(被foo对象调用)

如果到目前为止的这些对你来说是清晰的,那么以上的代码明显起了作用。我们可以通过两种不同的方式编写调用语法,来更改同一个函数中“this”值,将问题进一步复杂化:

var foo = {
    baz: function() {
        alert(this);
    }
}
foo.baz(); // foo - 因为baz()被调用时属于foo对象

var anotherBaz = foo.baz;
anotherBaz(); // global - 因为方法antherBaz()被调用时属于global对象,而不是foo对象

这里,我们可以看见两次baz()内部的“this”的值是不同的,因为它用两种不同的语法调用。现在,让我们在深层嵌套的对象中看一下“this”的值:

var anum = 0;

var foo = {
    anum: 10,
    baz: {
        anum: 20,
        bar: function() {
            console.log(this.anum);
        }
    }
}
foo.baz.bar(); // 20 - 因为()的左边是bar,它被调用时属于baz对象

var hello = foo.baz.bar;
hello(); // 0 - 因为()的左边是hello,它被调用时属于global对象

还有一个经常被问到的问题是如何在一个事件处理程序中确定“this”关键字?答案是事件处理程序内部的“this”始终引用它所触发的元素。我们来看一个例子:

<div id="test">I am an element with id #test</div>
function doAlert() { 
    alert(this.innerHTML); 
} 

doAlert(); // undefined 

var myElem = document.getElementById('test'); 
myElem.onclick = doAlert; 

alert(myElem.onclick === doAlert); // true 
myElem.onclick(); // I am an element

在这里我们可以看到,当首次调用doAlert()时,它弹出undefined,因为doAlert()从属于全局对象。然后我们编写myElem.onclick = doAlert,它将函数doAlert()复制给myElem的onclick()事件。这基本上意味着每当触发onclick()时,doAlert()就是myElem的一个方法,这意味着可以确定“this”的值将是myElem对象。

关于这个主题,我要添加的最后一点是,“this”的值也可以使用call()和apply()手动设置,覆盖我们今天在这里讨论的内容。同样有趣的是,当在函数构造函数中调用“this”时,“this”指的是构造函数内所有实例中新创建的对象。原因是函数构造函数使用“new”关键字调用,该关键字创建一个新对象,其中构造函数中的“this”始终引用刚刚创建的新对象。

总结

希望今天发布的博客已经解决了对“this”关键字的任何疑惑与误解,你可以随时了解“this”的正确价值。我们现在知道“this”的值永远不会是静态的,并且具有不同的值,具体取决于函数的调用方式。