我注意到,对于this
关键字是什么以及如何在堆栈溢出站点上的JavaScript中正确(或不正确)使用该关键字,似乎没有明确的解释。
我目睹了它的一些非常奇怪的行为,我不明白为什么会发生这种情况。
这个
是如何工作的,什么时候应该使用它?
我建议先阅读Mike West的文章JavaScript(镜像)中的作用域。 它是对this
和JavaScript中作用域链概念的出色,友好的介绍。
一旦您开始习惯this
,规则实际上非常简单。 ECMAScript 5.1标准定义了以下:
code>this关键字计算为当前执行上下文的ThisBinding的值
这种绑定是JavaScript解释器在计算JavaScript代码时维护的,就像保存对象引用的特殊CPU寄存器一样。 只要在以下三种不同情况中的一种情况下建立执行上下文,解释器就会更新ThisBinding:
这种情况适用于在顶层求值的JavaScript代码,例如直接在:
<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的值相同(§10.4.2(2)(a))。
…如果不是通过直接调用eval()
将此绑定设置为全局对象,就像在初始全局执行上下文中执行一样(§10.4.2(1))。
§15.1.2.1.1定义了对eval()
的直接调用。 基本上,eval(...)
是一个直接调用,而类似(0,eval)(...)
或var indirectEval=eval; indirectEval(。。。);
是对eval()
的间接调用。 请参阅Chuckj对JavaScript中的(1,eval)('this')vs eval('this')的回答? 和Dmitry Soshnikov的ECMA-262-5。 第二章。 严格模式。 用于可能使用间接的eval()
调用。
在调用函数时会发生这种情况。 如果在对象上调用函数,例如在obj.MyMethod()
或等效的obj[“MyMethod”]()
中,则将ThisBinding设置为对象(在示例中为obj
;§13.2.1)。 在大多数其他情况下,ThisBinding被设置为全局对象(§10.4.3)。
之所以编写“在大多数其他情况下”,是因为有八个ECMAScript5内置函数允许在参数列表中指定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
函数,它们是在function对象上调用的,但不是将ThisBinding设置为function对象,而是将ThisBinding设置为thisarg
。
对于Array.Prototype
函数,给定的CallbackFN
将在执行上下文中调用,如果提供了该上下文,则ThisBinding将设置为ThisArg
; 否则,发送到全局对象。
这些是普通JavaScript的规则。 当您开始使用JavaScript库(例如jQuery)时,您可能会发现某些库函数操作this
的值。 这些JavaScript库的开发人员之所以这样做,是因为它倾向于支持最常见的用例,而库的用户通常会发现这种行为更为方便。 当将引用this
的回调函数传递给库函数时,应参考文档,以确保在调用该函数时this
的值是什么。
如果您想知道JavaScript库如何操作this
的值,那么该库只是使用一个接受thisarg
的内置JavaScript函数。 您也可以编写自己的函数,使用回调函数和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
的作用域。 参见现有的规范问题,箭头函数vs函数声明/表达式:它们是等价的/可交换的吗? 以获取更多信息。 但简而言之:
箭头函数没有自己的此
.。。。 绑定。 相反,像任何其他变量一样,这些标识符在词法范围内被解析。 这意味着在arrow函数内部,this
.。。引用定义arrow函数的环境中的this
的值。
要找出答案,请将鼠标移到浅灰色的盒子上。
>
标记行处的this
的值是多少? 为什么?
窗口
-标记行在初始全局执行上下文中计算。
if (true) {
// What is `this` here?
}
执行obj.StaticFunction()
时,标记行处this
的值是多少? 为什么?
obj
-在对象上调用函数时,ThisBinding被设置为对象。
null
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
关键字在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.在对象方法内部使用时
当您在object方法内部使用this
关键字时,this
将绑定到封闭对象的“immediate”。
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
即使您将function显式地作为方法添加到对象中,它仍然遵循上述规则,即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.调用无上下文函数时
当您使用在没有任何上下文(即不在任何对象上)的情况下调用的此
内部函数时,它将绑定到全局对象(浏览器中的窗口
)(即使该函数是在对象内部定义的)。
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
运算符创建它的实例。下面,我尝试了我们用Object和这个
做的所有事情,但是首先要创建函数,而不是直接编写一个Object。
/*********************************************************************
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.内部call(),apply()和bind()函数
function.prototype
上定义的。this
的值,该值将在执行函数时使用。 它们还接受调用原始函数时传递给原始函数的任何参数。fun.apply(obj1[,argsArray])
将obj1
设置为fun()
内部的this
的值,并调用fun()
,传递argsArray
的元素作为参数。fun.call(obj1[,arg1[,arg2[,arg3[,...]]]])
-将obj1
设置为fun()
内部的this
的值,并调用fun()
,传递arg1,arg2,arg3,...
作为参数。fun.bind(obj1[,arg1[,arg2[,arg3[,...]]])
-返回对函数fun
的引用,其中this
inside fun绑定到obj1
,fun
的参数绑定到指定的参数arg1,arg2,arg3,...
。应用
,调用
和绑定
之间的区别肯定已经很明显了。 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
时,它将引用该元素。此
间接通过事件处理函数或事件属性内部调用的其他函数解析为全局对象窗口
。attachEvent
将函数附加到事件处理程序时,也会实现上述相同的行为。 它不是将函数分配给事件处理程序(从而生成元素的function方法),而是在事件上调用函数(有效地在全局上下文中调用它)。我建议最好在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>
8.ES6中的this
箭头函数
在箭头函数中,this
的行为将与普通变量类似:它将从其词法范围继承。 定义arrow函数的函数的this
将是arrow函数的this
。
所以,这是相同的行为:
(function(){}).bind(this)
请参阅以下代码:
const globalArrowFunction = () => {
return this;
};
console.log(globalArrowFunction()); //window
const contextObject = {
method1: () => {return this},
method2: function(){
return () => {return this};
}
};
console.log(contextObject.method1()); //window
const contextLessFunction = contextObject.method1;
console.log(contextLessFunction()); //window
console.log(contextObject.method2()()) //contextObject
const innerArrowFunction = contextObject.method2();
console.log(innerArrowFunction()); //contextObject
考虑以下函数:
function foo() {
console.log("bar");
console.log(this);
}
foo(); // calling the function
注意,我们是在普通模式下运行这个,即不使用严格模式。
在浏览器中运行时,this
的值将记录为window
。 这是因为window
是web浏览器作用域中的全局变量。
如果在Node.js这样的环境中运行这段代码,this
将引用应用程序中的全局变量。
现在,如果我们通过在函数声明的开头添加语句“use strict”;
来在严格模式下运行,则this
将不再引用两种环境中的全局变量。 这样做是为了避免严格模式下的混淆。 在本例中,这个
将只记录未定义
,因为它就是这样,所以它没有被定义。
在以下情况下,我们将看到如何操作this
的值。
有不同的方法可以做到这一点。 如果您在Javascript中调用过本机方法,比如foreach
和slice
,那么您应该已经知道,在这种情况下,this
变量引用了您调用该函数时所使用的对象
(注意,在Javascript中,几乎所有东西都是一个对象
,包括数组
和函数
)。 以下面的代码为例。
var myObj = {key: "Obj"};
myObj.logThis = function () {
// I am a method
console.log(this);
}
myObj.logThis(); // myObj is logged
如果对象
包含保存函数
的属性,则该属性称为方法。 调用此方法时,将始终将其此
变量设置为与之关联的对象
。 无论是严格模式还是非严格模式都是如此。
注意,如果一个方法存储(或者更确切地说,复制)在另一个变量中,对this
的引用将不再保留在新变量中。 例如:
// continuing with the previous code snippet
var myVar = myObj.logThis;
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
考虑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
类型的对象
。对象
的构造函数将其构造函数设置为Person
。 另外,请注意typeofAWA
将只返回object
。对象
将被分配为Person.prototype
的原型。 这意味着Person
原型中的任何方法或属性都可用于Person
的所有实例,包括AWA
。person
本身; 此
是对新构造的对象Awal
的引用。很直截了当吧?
请注意,官方ECMAScript规范中没有任何地方说明此类函数是实际的constructor
函数。 它们只是普通函数,new
可以用于任何函数。 只是我们把它们当作这样来使用,所以我们只把它们当作这样来称呼。
所以是的,因为函数
也是对象
(实际上是Javascript中的第一类变量),所以即使函数也有方法,这些方法是。。。 好吧,功能本身。
所有函数都继承自全局函数
,其众多方法中有两个是call
和apply
,这两个方法都可用于操作调用它们的函数中this
的值。
function foo () { console.log (this, arguments); }
var thisArg = {myObj: "is cool"};
foo.call(thisArg, 1, 2, 3);
这是使用调用
的一个典型例子。 它基本上采用第一个参数,并将函数foo
中的thisarg
设置为对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
的值。 很简单:)
绑定
是调用
和应用
的兄弟。 它也是所有函数从JavaScript中的全局函数
构造函数继承的方法。 绑定
和调用
/应用
之间的区别在于,调用
和应用
实际上都将调用函数。 另一方面,bind
返回一个新函数,该函数预先设置了thisarg
和argument
。 让我们举个例子来更好地理解这一点:
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
的值。
还要注意,这三个函数都没有对原始函数做任何更改。 call
和apply
将从新构造的函数返回值,而bind
将返回新构造的函数本身,准备调用。
有时,您不喜欢这个
随作用域(尤其是嵌套作用域)而变化的事实。 看一看下面的例子。
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
。 聪明吧?
索引:
此
中包含什么?new
关键字会怎样?调用
和应用
操作此
?绑定
。此
以解决嵌套范围问题。