我注意到,似乎没有明确解释this
关键字是什么以及如何在 Stack Overflow 站点上的 JavaScript 中正确(和错误地)使用它。
我亲眼目睹了一些非常奇怪的行为,并且无法理解为什么会发生这种行为。
怎么this
时候它应该被用来工作?
我建议先阅读Mike West的文章Scope in JavaScript ( mirror )。它是对 JavaScript 中this
和范围链的概念的一个出色,友好的介绍。
一旦你开始习惯this
,规则实际上非常简单。 ECMAScript 5.1 标准定义了this
:
§11.1.1的
this
关键字
this
关键字的计算结果为当前执行上下文的 ThisBinding 的值
ThisBinding 是 JavaScript 解释器在评估 JavaScript 代码时维护的东西,比如一个特殊的 CPU 寄存器,它保存对象的引用。每当在三种不同情况之一中建立执行上下文时,解释器就会更新 ThisBinding:
这是在顶层评估的 JavaScript 代码的情况,例如直接在<script>
:
<script>
alert("I'm evaluated in the initial global execution context!");
setTimeout(function () {
alert("I'm NOT evaluated in the initial global execution context.");
}, 1);
</script>
在初始全局执行上下文中计算代码时,ThisBinding 设置为全局对象window
(第10.4.1.1 节 )。
... 通过直接调用eval()
ThisBinding 保持不变; 它与调用执行上下文的ThisBinding ( §10.4.2 (2)(a))的值相同。
... 如果不是直接调用eval()
ThisBinding 设置为全局对象,就像在初始全局执行上下文中执行一样(第10.4.2 (1)节)。
§15.1.2.1.1 定义了对eval()
的直接调用。基本上, eval(...)
是直接调用,而类似于(0, eval)(...)
或var indirectEval = eval; indirectEval(...);
是对eval()
的间接调用。 在 JavaScript 中查看chuckj对(1,eval)('this')vs eval('this') 的回答 ?和Dmitry Soshnikov 的 ECMA-262-5 详细介绍。第 2 章严格模式。当你可能使用间接eval()
调用时。
调用函数时会发生这种情况。如果在对象上调用函数,例如在obj.myMethod()
或等效的obj["myMethod"]()
,则将 ThisBinding 设置为对象(示例中的obj
; §13.2.1 )。在大多数其他情况下,ThisBinding 设置为全局对象(第10.4.3 节 )。
在 “大多数其他情况下” 编写的原因是因为有八个 ECMAScript 5 内置函数允许在参数列表中指定 ThisBinding。这些特殊函数采用所谓的thisArg
,它在调用函数时成为 ThisBinding(第10.4.3 节 )。
这些特殊的内置功能是:
Function.prototype.apply( thisArg, argArray )
Function.prototype.call( thisArg [ , arg1 [ , arg2, ... ] ] )
Function.prototype.bind( thisArg [ , arg1 [ , arg2, ... ] ] )
Array.prototype.every( callbackfn [ , thisArg ] )
Array.prototype.some( callbackfn [ , thisArg ] )
Array.prototype.forEach( callbackfn [ , thisArg ] )
Array.prototype.map( callbackfn [ , thisArg ] )
Array.prototype.filter( callbackfn [ , thisArg ] )
对于Function.prototype
函数,它们在函数对象上调用,但不是将 ThisBinding 设置为函数对象,而是将 ThisBinding 设置为thisArg
。
对于Array.prototype
函数,在执行上下文中调用给定的callbackfn
,其中 ThisBinding 设置为thisArg
如果提供); 否则,到全局对象。
这些是纯 JavaScript 的规则。当你开始使用 JavaScript 库(例如 jQuery 的),你可能会发现某些库函数操作的值this
。这些 JavaScript 库的开发人员这样做是因为它倾向于支持最常见的用例,并且库的用户通常会发现这种行为更方便。当传递回调函数引用this
库函数,你应该参考文档什么的价值任何保证this
是当函数被调用。
如果你想知道一个 JavaScript 库如何操纵值this
,图书馆是简单地使用一个内置的 JavaScript 函数接受thisArg
。您也可以使用回调函数和thisArg
编写自己的函数:
function doWork(callbackfn, thisArg) {
//...
if (callbackfn != null) callbackfn.call(thisArg);
}
我还没有提到一个特例。通过new
运算符构造新对象时,JavaScript 解释器创建一个新的空对象,设置一些内部属性,然后在新对象上调用构造函数。因此,在构造函数上下文中调用函数时, this
的值是解释器创建的新对象:
function MyType() {
this.someData = "a string";
}
var instance = new MyType();
// Kind of like the following, but there are more steps involved:
// var instance = {};
// MyType.call(instance);
箭头功能 (在 ECMA6 引入)改变的范围this
。请参阅现有的规范问题, 箭头函数与函数声明 / 表达式:它们是等效 / 可交换的吗?欲获得更多信息。但简而言之:
箭函数不自己有
this
.... 结合。相反,这些标识符在词法范围内像任何其他变量一样被解析。这意味着,一个箭头函数内部,this
... 参照(多个)给的值this
在环境中的箭头函数是在所定义。
要显示答案,请将鼠标悬停在浅黄色框上。
什么是值this
在标线?为什么?
window
- 标记的行在初始全局执行上下文中计算。
if (true) {
// What is `this` here?
}
什么是价值this
当标线obj.staticFunction()
执行?为什么?
obj
- 在对象上调用函数时,ThisBinding 设置为该对象。
var obj = {
someData: "a string"
};
function myFun() {
return this // What is `this` here?
}
obj.staticFunction = myFun;
console.log("this is window:", obj.staticFunction() == window);
console.log("this is obj:", obj.staticFunction() == obj);
什么是值this
在标线?为什么?
window
在此示例中,JavaScript 解释器输入函数代码,但由于未在对象上调用
myFun
/obj.myMethod
,因此将 ThisBinding 设置为window
。这与 Python 不同,其中访问方法(
obj.myMethod
)创建绑定方法对象 。
var obj = {
myMethod: function () {
return this; // What is `this` here?
}
};
var myFun = obj.myMethod;
console.log("this is window:", myFun() == window);
console.log("this is obj:", myFun() == obj);
什么是值this
在标线?为什么?
window
这个很棘手。在评估 eval 代码时,
this
是obj
。然而,在 EVAL 代码,myFun
不是在对象上调用,所以 ThisBinding 设置为window
的呼叫。
function myFun() {
return this; // What is `this` here?
}
var obj = {
myMethod: function () {
eval("myFun()");
}
};
什么是值this
在标线?为什么?
obj
行
myFun.call(obj);
正在调用特殊的内置函数Function.prototype.call()
,它接受thisArg
作为第一个参数。
function myFun() {
return this; // What is `this` here?
}
var obj = {
someData: "a string"
};
console.log("this is window:", myFun.call(obj) == window);
console.log("this is obj:", myFun.call(obj) == obj);
与其他语言相比, this
关键字在 JavaScript 中的行为有所不同。在面向对象语言中, this
关键字引用类的当前实例。在 JavaScript 中, this
的值主要由函数的调用上下文( context.function()
)以及调用它的位置决定。
1. 在全球范围内使用时
在全局上下文中使用this
时,它绑定到全局对象(浏览器中的window
)
document.write(this); //[object Window]
当您在全局上下文中定义的函数内使用this
, this
仍然绑定到全局对象,因为该函数实际上是一个全局上下文的方法。
function f1()
{
return this;
}
document.write(f1()); //[object Window]
上面的f1
是一个全局对象的方法。因此我们也可以在window
对象上调用它,如下所示:
function f()
{
return this;
}
document.write(window.f()); //[object Window]
2. 在对象内部使用时
当您使用this
关键字的对象方法中, this
势必会 “立即” 包围对象。
var obj = {
name: "obj",
f: function () {
return this + ":" + this.name;
}
};
document.write(obj.f()); //[object Object]:obj
我在上面用双引号括起来。重点是,如果将对象嵌套在另一个对象中,则将this
绑定到直接父对象。
var obj = {
name: "obj1",
nestedobj: {
name:"nestedobj",
f: function () {
return this + ":" + this.name;
}
}
}
document.write(obj.nestedobj.f()); //[object Object]:nestedobj
即使你明确的对象添加功能的方法,但它仍然遵循上述规则,那就是this
仍然指向直接父对象。
var obj1 = {
name: "obj1",
}
function returnName() {
return this + ":" + this.name;
}
obj1.f = returnName; //add method to object
document.write(obj1.f()); //[object Object]:obj1
3. 调用无上下文函数时
当你使用this
在没有任何上下文的情况下调用的内部函数(即不在任何对象上)时,它被绑定到全局对象(浏览器中的window
)(即使函数在对象内部定义)。
var context = "global";
var obj = {
context: "object",
method: function () {
function f() {
var context = "function";
return this + ":" +this.context;
};
return f(); //invoked without context
}
};
document.write(obj.method()); //[object Window]:global
尝试所有功能
我们也可以尝试以上功能。但是有一些差异。
this
来添加成员到函数。指定它们。 new
运算符创建其实例。 下面我尝试了一切,我们做了与对象和东西this
上面,而是首先创建函数,而不是直接写入的对象。
/*********************************************************************
1. When you add variable to the function using this keyword, it
gets added to the function prototype, thus allowing all function
instances to have their own copy of the variables added.
*********************************************************************/
function functionDef()
{
this.name = "ObjDefinition";
this.getName = function(){
return this+":"+this.name;
}
}
obj1 = new functionDef();
document.write(obj1.getName() + "<br />"); //[object Object]:ObjDefinition
/*********************************************************************
2. Members explicitly added to the function protorype also behave
as above: all function instances have their own copy of the
variable added.
*********************************************************************/
functionDef.prototype.version = 1;
functionDef.prototype.getVersion = function(){
return "v"+this.version; //see how this.version refers to the
//version variable added through
//prototype
}
document.write(obj1.getVersion() + "<br />"); //v1
/*********************************************************************
3. Illustrating that the function variables added by both above
ways have their own copies across function instances
*********************************************************************/
functionDef.prototype.incrementVersion = function(){
this.version = this.version + 1;
}
var obj2 = new functionDef();
document.write(obj2.getVersion() + "<br />"); //v1
obj2.incrementVersion(); //incrementing version in obj2
//does not affect obj1 version
document.write(obj2.getVersion() + "<br />"); //v2
document.write(obj1.getVersion() + "<br />"); //v1
/*********************************************************************
4. `this` keyword refers to the immediate parent object. If you
nest the object through function prototype, then `this` inside
object refers to the nested object not the function instance
*********************************************************************/
functionDef.prototype.nestedObj = { name: 'nestedObj',
getName1 : function(){
return this+":"+this.name;
}
};
document.write(obj2.nestedObj.getName1() + "<br />"); //[object Object]:nestedObj
/*********************************************************************
5. If the method is on an object's prototype chain, `this` refers
to the object the method was called on, as if the method was on
the object.
*********************************************************************/
var ProtoObj = { fun: function () { return this.a } };
var obj3 = Object.create(ProtoObj); //creating an object setting ProtoObj
//as its prototype
obj3.a = 999; //adding instance member to obj3
document.write(obj3.fun()+"<br />");//999
//calling obj3.fun() makes
//ProtoObj.fun() to access obj3.a as
//if fun() is defined on obj3
4. 在构造函数内部使用时 。
当函数用作构造函数时(即使用new
关键字调用它时), this
内部函数体指向正在构造的新对象。
var myname = "global context";
function SimpleFun()
{
this.myname = "simple function";
}
var obj1 = new SimpleFun(); //adds myname to obj1
//1. `new` causes `this` inside the SimpleFun() to point to the
// object being constructed thus adding any member
// created inside SimipleFun() using this.membername to the
// object being constructed
//2. And by default `new` makes function to return newly
// constructed object if no explicit return value is specified
document.write(obj1.myname); //simple function
5. 在原型链中定义的函数内部使用时
如果方法在对象的原型链上, this
此类方法内部引用调用该方法的对象,就好像该方法是在对象上定义的一样。
var ProtoObj = {
fun: function () {
return this.a;
}
};
//Object.create() creates object with ProtoObj as its
//prototype and assigns it to obj3, thus making fun()
//to be the method on its prototype chain
var obj3 = Object.create(ProtoObj);
obj3.a = 999;
document.write(obj3.fun()); //999
//Notice that fun() is defined on obj3's prototype but
//`this.a` inside fun() retrieves obj3.a
6. 内部调用(),apply()和 bind()函数
Function.prototype
上定义。 this
同时被执行的功能是将被使用。它们还会在调用时将任何参数传递给原始函数。 fun.apply(obj1 [, argsArray])
设置obj1
作为的值this
里面fun()
并调用fun()
的通过元素argsArray
作为它的参数。 fun.call(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
设置obj1
作为价值this
里面fun()
并调用fun()
传arg1, arg2, arg3, ...
作为它的参数。 fun.bind(obj1 [, arg1 [, arg2 [,arg3 [, ...]]]])
- 返回对函数fun
的引用, this
内部函数绑定到obj1
和绑定到指定参数arg1, arg2, arg3,...
的fun
参数arg1, arg2, arg3,...
apply
, call
和bind
之间的区别必须明显。 apply
允许指定用作类数组对象的参数,即具有数字length
属性的对象和相应的非负整数属性。而call
允许直接指定函数的参数。 apply
和call
立即调用指定上下文中的函数并使用指定的参数。另一方面, bind
只返回绑定到指定this
值和参数的函数。我们可以通过将其赋值给变量来捕获对此返回函数的引用,之后我们可以随时调用它。 function add(inc1, inc2)
{
return this.a + inc1 + inc2;
}
var o = { a : 4 };
document.write(add.call(o, 5, 6)+"<br />"); //15
//above add.call(o,5,6) sets `this` inside
//add() to `o` and calls add() resulting:
// this.a + inc1 + inc2 =
// `o.a` i.e. 4 + 5 + 6 = 15
document.write(add.apply(o, [5, 6]) + "<br />"); //15
// `o.a` i.e. 4 + 5 + 6 = 15
var g = add.bind(o, 5, 6); //g: `o.a` i.e. 4 + 5 + 6
document.write(g()+"<br />"); //15
var h = add.bind(o, 5); //h: `o.a` i.e. 4 + 5 + ?
document.write(h(6) + "<br />"); //15
// 4 + 5 + 6 = 15
document.write(h() + "<br />"); //NaN
//no parameter is passed to h()
//thus inc2 inside add() is `undefined`
//4 + 5 + undefined = NaN</code>
7. this
内部事件处理程序
this
指的是相应的元素。这种直接函数赋值可以使用addeventListener
方法或通过传统的事件注册方法(如onclick
。 this
直接事件属性内(如<button onclick="...this..." >
的元素,它是指该元素。 this
函数会解析为全局对象window
。 attachEvent
将函数附加到事件处理程序时,可以实现相同的上述行为。它不是将函数赋值给事件处理程序(从而构成元素的函数方法),而是调用事件上的函数(在全局上下文中有效地调用它)。 我建议在JSFiddle 中更好地尝试这个。
<script>
function clickedMe() {
alert(this + " : " + this.tagName + " : " + this.id);
}
document.getElementById("button1").addEventListener("click", clickedMe, false);
document.getElementById("button2").onclick = clickedMe;
document.getElementById("button5").attachEvent('onclick', clickedMe);
</script>
<h3>Using `this` "directly" inside event handler or event property</h3>
<button id="button1">click() "assigned" using addEventListner() </button><br />
<button id="button2">click() "assigned" using click() </button><br />
<button id="button3" onclick="alert(this+ ' : ' + this.tagName + ' : ' + this.id);">used `this` directly in click event property</button>
<h3>Using `this` "indirectly" inside event handler or event property</h3>
<button onclick="alert((function(){return this + ' : ' + this.tagName + ' : ' + this.id;})());">`this` used indirectly, inside function <br /> defined & called inside event property</button><br />
<button id="button4" onclick="clickedMe()">`this` used indirectly, inside function <br /> called inside event property</button> <br />
IE only: <button id="button5">click() "attached" using attachEvent() </button>
this
考虑以下功能:
function foo() {
console.log("bar");
console.log(this);
}
foo(); // calling the function
请注意,我们在正常模式下运行它,即不使用严格模式。
当在浏览器中运行,值this
将被记录为window
。这是因为window
是 Web 浏览器范围中的全局变量。
如果你像 node.js 中的环境中运行该同一段代码, this
将涉及到全局变量在你的应用程序。
现在如果我们通过添加语句"use strict";
在严格模式下运行它"use strict";
到函数声明的开头, this
将不再引用任何一个环境中的全局变量。这样做是为了避免严格模式下的混淆。 this
会,在这种情况下,只要登录undefined
,因为那是它是什么,没有定义它。
在下列情况下,我们将看到如何操作的值this
。
有不同的方法来做到这一点。如果你在 Javascript 中调用了本机方法,比如forEach
和slice
,你应该已经知道在这种情况下this
变量引用你调用该函数的Object
(注意,在 javascript 中,几乎所有东西都是一个Object
,包括Array
s 和Function
s)。以下面的代码为例。
var myObj = {key: "Obj"};
myObj.logThis = function () {
// I am a method
console.log(this);
}
myObj.logThis(); // myObj is logged
如果Object
包含保存Function
的属性,则该属性称为方法。调用此方法时,将始终this
变量设置为与其关联的Object
。对于严格和非严格模式都是如此。
注意,如果一个方法被存储(或更确切地说,复制)在另一变量中,参照this
不再保留在新的变量。例如:
// continuing with the previous code snippet
var myVar = myObj.thisMethod;
myVar();
// logs either of window/global/undefined based on mode of operation
考虑更常见的实际情况:
var el = document.getElementById('idOfEl');
el.addEventListener('click', function() { console.log(this) });
// the function called by addEventListener contains this as the reference to the element
// so clicking on our element would log that element itself
new
关键字考虑 Javascript 中的构造函数:
function Person (name) {
this.name = name;
this.sayHello = function () {
console.log ("Hello", this);
}
}
var awal = new Person("Awal");
awal.sayHello();
// In `awal.sayHello`, `this` contains the reference to the variable `awal`
这是如何运作的?好吧,让我们看看当我们使用new
关键字时会发生什么。
new
关键字调用该函数将立即初始化Person
类型的Object
。 Object
的构造函数的构造函数设置为Person
。另请注意, typeof awal
仅返回Object
。 Object
将被赋予Person.prototype
的原型。这意味着Person
原型中的任何方法或属性都可用于Person
所有实例,包括awal
。 Person
本身; this
是对新构造的对象的awal
。 很简单,嗯?
请注意,官方的 ECMAScript 规范没有说明这类函数是实际的constructor
函数。它们只是普通函数, new
可以用于任何函数。只是我们这样使用它们,所以我们只将它们称为它们。
call
并apply
所以是的,因为function
s 也是Objects
(实际上是 Javascript 中的第一类变量),所以即使函数也有...... 好吧,函数本身。
所有的功能从全球继承Function
,及其两个多方法call
和apply
,都可以用来操纵的值this
在它们所调用的函数。
function foo () { console.log (this, arguments); }
var thisArg = {myObj: "is cool"};
foo.call(thisArg, 1, 2, 3);
这是使用call
的典型示例。它基本上取第一参数,并设置this
在函数foo
作为一参考thisArg
。传递给call
所有其他参数都作为参数传递给函数foo
。
所以上面的代码将在控制台中记录{myObj: "is cool"}, [1, 2, 3]
。相当不错的方法来改变的值this
在任何功能。
apply
几乎与call
accept 相同,它只需要两个参数: thisArg
和一个包含要传递给函数的参数的数组。所以上面的call
呼叫可以被转换成apply
这样的:
foo.apply(thisArg, [1,2,3])
请注意, call
和apply
可以通过我们在第二个项目符号中讨论的点方法调用来覆盖this
set 的值。很简单:)
bind
! bind
是call
和apply
的兄弟。它也是 Javascript 中全局Function
构造函数的所有函数继承的方法。 bind
和call
/ apply
之间的区别在于call
和apply
都会实际调用该函数。另一方面, bind
使用thisArg
和arguments
预设返回一个新函数。让我们举个例子来更好地理解这个:
function foo (a, b) {
console.log (this, arguments);
}
var thisArg = {myObj: "even more cool now"};
var bound = foo.bind(thisArg, 1, 2);
console.log (typeof bound); // logs `function`
console.log (bound);
/* logs `function () { native code }` */
bound(); // calling the function returned by `.bind`
// logs `{myObj: "even more cool now"}, [1, 2]`
看到三者之间的区别?它很微妙,但它们的使用方式不同。与call
和apply
一样, bind
也会通过点方法调用覆盖this
set 的值。
另请注意,这三个函数都不会对原始函数进行任何更改。 call
和apply
将从新构造的函数返回值,而bind
将返回新构造的函数本身,准备调用。
有时,您不喜欢this
随着范围而变化,尤其是嵌套范围。看一下下面的例子。
var myObj = {
hello: function () {
return "world"
},
myMethod: function () {
// copy this, variable names are case-sensitive
var that = this;
// callbacks ftw \o/
foo.bar("args", function () {
// I want to call `hello` here
this.hello(); // error
// but `this` references to `foo` damn!
// oh wait we have a backup \o/
that.hello(); // "world"
});
}
};
在上面的代码中,我们看到的价值this
与嵌套的范围内改变,但我们想要的值, this
从原来的范围。因此,我们 “复制” this
到that
和所使用的副本,而不是this
。聪明,是吗?
指数:
this
默认? new
关键字怎么办? call
和apply
操纵this
? bind
。 this
以解决嵌套范围问题。