我希望找到一个“this”关键字的明确解释,以及如何正确使用它。

它的行为似乎很奇怪,我不完全明白为什么。

这是如何工作的,何时使用?


当前回答

关于这一点,最详细和最全面的文章可能是以下内容:

JavaScript中“this”关键字的温和解释

这背后的想法是理解函数调用类型对设置该值具有重要意义。


当难以识别时,不要问自己:

这是从哪里来的?

但一定要问问自己:

如何调用函数?

对于箭头函数(上下文透明的特殊情况),问问自己:

在定义箭头函数的地方有什么值?

在处理这个问题时,这种心态是正确的,会让你免于头痛。

其他回答

Javascript就是这个

简单函数调用

考虑以下功能:

function foo() {
    console.log("bar");
    console.log(this);
}
foo(); // calling the function

请注意,我们在正常模式下运行,即不使用严格模式。

在浏览器中运行时,此值将作为窗口记录。这是因为window是web浏览器范围内的全局变量。

如果在node.js这样的环境中运行相同的代码,这将引用应用程序中的全局变量。

现在,如果我们通过添加语句“usestrict”在严格模式下运行此操作;在函数声明的开头,这将不再引用这两个环境中的全局变量。这样做是为了避免严格模式中的混淆。在这种情况下,这将只是log undefined,因为这就是它,它没有被定义。

在下面的例子中,我们将看到如何操纵这个值。

对对象调用函数

有不同的方法可以做到这一点。如果您在Javascript中调用了本机方法,如forEach和slice,那么您应该已经知道,在这种情况下,this变量指的是调用该函数的Object(注意,在Javascript中,几乎所有的东西都是Object,包括数组和函数)。以以下代码为例。

var myObj = {key: "Obj"};
myObj.logThis = function () {
    // I am a method
    console.log(this);
}
myObj.logThis(); // myObj is logged

如果对象包含包含函数的属性,则该属性称为方法。调用此方法时,将始终将此变量设置为与其关联的对象。对于严格模式和非严格模式都是如此。

请注意,如果一个方法存储(或复制)在另一个变量中,则对该方法的引用将不再保留在新变量中。例如:

// 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关键字调用函数将立即初始化Person类型的Object。此对象的构造函数将其构造函数设置为Person。此外,请注意,typeof awal将仅返回Object。这个新对象将被分配Person.prototype的原型。这意味着Person原型中的任何方法或属性都可用于Person的所有实例,包括awal。现在调用函数Person本身;这是对新构建的对象awal的引用。

很简单,嗯?

请注意,官方的ECMAScript规范中没有说明此类函数是实际的构造函数。它们只是普通函数,新函数可以用于任何函数。只不过我们是这样使用它们的,所以我们只是这样称呼它们。

在函数上调用函数:调用和应用

所以,是的,因为函数也是Object(实际上是Javascript中的第一类变量),所以即使函数也有方法。。。嗯,功能本身。

所有函数都继承自全局函数,它的许多方法中有两个是调用和应用,这两个方法都可以用于在调用它们的函数中操纵此值。

function foo () { console.log (this, arguments); }
var thisArg = {myObj: "is cool"};
foo.call(thisArg, 1, 2, 3);

这是使用call的典型示例。它基本上接受第一个参数,并在函数foo中将其设置为对thisArg的引用。传递给调用的所有其他参数都作为参数传递给函数foo。因此,上面的代码将在控制台中记录{myObj:“is cool”},[1,2,3]。在任何函数中更改此值的非常好的方法。

apply与callaccept几乎相同,它只接受两个参数:thisArg和一个包含要传递给函数的参数的数组。因此,可以将上述调用转换为如下应用:

foo.apply(thisArg, [1,2,3])

请注意,调用和应用可以覆盖我们在第二个项目中讨论的点方法调用集的值。足够简单:)

呈现。。。。绑定

bind是应召而来的兄弟。它也是Javascript中所有函数从全局函数构造函数继承的方法。绑定和调用/应用之间的区别在于,调用和应用实际上都会调用函数。另一方面,bind会返回一个预设了thisArg和参数的新函数。让我们举个例子来更好地理解这一点:

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]`

看到三者之间的区别了吗?这很微妙,但它们的用法不同。与调用和应用一样,bind也将通过点方法调用覆盖此集合的值。

还要注意,这三个函数都没有对原始函数进行任何更改。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"
        });
    }
  };

在上面的代码中,我们看到这个值随嵌套范围而改变,但我们希望从原始范围中获得这个值。所以我们将这个“复制”到那个,并使用副本代替这个。聪明,嗯?

索引:

默认情况下,此中包含什么?如果我们将函数作为一个带有Object点符号的方法调用呢?如果我们使用新关键字呢?我们如何通过调用和应用来处理这一点?使用bind。复制此以解决嵌套范围问题。

与其他语言相比,this关键字在JavaScript中的行为有所不同。在面向对象语言中,this关键字指的是类的当前实例。在JavaScript中,这个值由函数的调用上下文(context.function())及其调用位置决定。

1.在全局上下文中使用时

当您在全局上下文中使用它时,它将绑定到全局对象(浏览器中的窗口)

document.write(this);  //[object Window]

当您在全局上下文中定义的函数中使用此函数时,它仍然绑定到全局对象,因为该函数实际上是全局上下文的方法。

function f1()
{
   return this;
}
document.write(f1());  //[object Window]

f1以上是全局对象的方法。因此,我们也可以在window对象上调用它,如下所示:

function f()
{
    return this;
}

document.write(window.f()); //[object Window]

2.在对象方法内部使用时

当您在对象方法中使用此关键字时,它将绑定到“立即”封闭对象。

var obj = {
    name: "obj",
    f: function () {
        return this + ":" + this.name;
    }
};
document.write(obj.f());  //[object Object]:obj

上面我把单词immediate用双引号括起来。这是为了指出,如果您将对象嵌套在另一个对象中,那么这将绑定到直接父对象。

var obj = {
    name: "obj1",
    nestedobj: {
        name:"nestedobj",
        f: function () {
            return this + ":" + this.name;
        }
    }            
}

document.write(obj.nestedobj.f()); //[object Object]:nestedobj

即使您将函数作为方法显式添加到对象中,它仍然遵循上述规则,即这仍然指向直接父对象。

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 

使用函数尝试所有功能

我们也可以用函数来尝试以上几点。然而,存在一些差异。

上面我们使用对象文字表示法向对象添加了成员。我们可以通过使用这个向函数添加成员。以指定它们。对象文字表示法创建了一个我们可以立即使用的对象实例。对于函数,我们可能需要首先使用新运算符创建其实例。同样,在对象文字方法中,我们可以使用点运算符将成员显式添加到已定义的对象中。这将仅添加到特定实例。然而,我已经向函数原型添加了变量,以便它在函数的所有实例中得到反映。

下面,我尝试了我们使用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关键字调用时),此内部函数体指向正在构造的新对象。

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.在原型链上定义的函数内部使用时

如果该方法位于对象的原型链上,则此类方法中的这个引用该方法所调用的对象,就像该方法是在对象上定义的一样。

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()内部的值,并调用fun(()传递argsArray元素作为其参数。fun.call(obj1[,arg1[,arg2[,arg3[,…]]])-将obj1设置为fun()内部的值,并调用fun(,传递arg1,arg2,arg3,…)。。。作为其论点。fun.bind(obj1[,arg1[,arg2[,arg3[,…]]])-返回对函数fun的引用,此函数内部fun绑定到obj1,fun的参数绑定到指定的参数arg1,arg2,arg3,。。。。到目前为止,apply、call和bind之间的区别肯定已经很明显了。apply允许将参数指定为类似数组的对象,即具有数字长度属性和相应的非负整数财产的对象。而调用允许直接指定函数的参数。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.此内部事件处理程序

当您将函数直接分配给元素的事件处理程序时,直接在事件处理函数内部使用该函数将引用相应的元素。这种直接函数分配可以使用addeventListener方法或通过传统的事件注册方法(如onclick)完成。类似地,当您在元素的事件属性(如<button onclick=“…this…”>)中直接使用它时,它引用的是元素。但是,通过在事件处理函数或事件属性内调用的其他函数间接地使用该函数将解析为全局对象窗口。当我们使用Microsoft的event Registration模型方法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>

8.这在ES6箭头函数中

在箭头函数中,它的行为类似于公共变量:它将从其词法范围继承。函数的this,其中定义了箭头函数,将是箭头函数的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 

关于这一点,最详细和最全面的文章可能是以下内容:

JavaScript中“this”关键字的温和解释

这背后的想法是理解函数调用类型对设置该值具有重要意义。


当难以识别时,不要问自己:

这是从哪里来的?

但一定要问问自己:

如何调用函数?

对于箭头函数(上下文透明的特殊情况),问问自己:

在定义箭头函数的地方有什么值?

在处理这个问题时,这种心态是正确的,会让你免于头痛。

“这”是关于范围的。每个函数都有自己的作用域,因为JS中的所有内容都是一个对象,所以即使是一个函数也可以使用“this”将一些值存储到自身中。OOP 101教导“这”仅适用于对象的实例。因此,每次函数执行时,该函数的新“实例”都具有“this”的新含义。

大多数人在尝试在匿名闭包函数中使用“this”时会感到困惑,例如:

(function(value) {
    this.value = value;
    $('.some-elements').each(function(elt){
        elt.innerHTML = this.value;        // uh oh!! possibly undefined
    });
})(2);

所以这里,在each()内部,“this”不包含您期望的“value”(从this.value=value;在其上方)。因此,为了克服这个(并非双关语)问题,开发人员可以:

(function(value) {
    var self = this;            // small change
    self.value = value;
    $('.some-elements').each(function(elt){
        elt.innerHTML = self.value;        // phew!! == 2 
    });
})(2);

试试看;你会开始喜欢这种编程模式

这里有一个很好的JavaScript源代码。

以下是总结:

全局this在浏览器中,在全局范围内,这是windowobject<script type=“text/javascript”>console.log(此==窗口);//真的var foo=“bar”;console.log(this.foo);//“条形图”console.log(window.foo);//“条形图”在使用repl的节点中,这是顶级命名空间。您可以将其称为全局。>这个{ArrayBuffer:[Function:ArrayBuffer],Int8Array:{[Function:Int8Array]BYTES_PER_ELEMENT:1},Uint8Array:{[Function:Uint8Array]BYTES_PER_ELEMENT:1},...>全局==此真的在从脚本执行的节点中,全局范围中的这个对象以空对象开始。它与全球不同\\测试.jsconsole.log(此);\\{}console.log(this==全局);\\时尚函数this

除了在DOM事件处理程序的情况下,或者当提供了thisArg时(请参见下文),在节点和浏览器中都会在不使用新引用调用全局范围的函数中使用此函数…

<script type="text/javascript">
    foo = "bar";

    function testThis() {
      this.foo = "foo";
    }

    console.log(this.foo); //logs "bar"
    testThis();
    console.log(this.foo); //logs "foo"
</script>

如果使用use strict;,在这种情况下,这将是未定义的

<script type="text/javascript">
    foo = "bar";

    function testThis() {
      "use strict";
      this.foo = "foo";
    }

    console.log(this.foo); //logs "bar"
    testThis();  //Uncaught TypeError: Cannot set property 'foo' of undefined 
</script>

若使用new调用函数,这将是一个新上下文,它将不会引用全局this。

<script type="text/javascript">
    foo = "bar";

    function testThis() {
      this.foo = "foo";
    }

    console.log(this.foo); //logs "bar"
    new testThis();
    console.log(this.foo); //logs "bar"

    console.log(new testThis().foo); //logs "foo"
</script>

原型

您创建的函数将成为函数对象。它们会自动获得一个特殊的原型属性,这是您可以为其赋值的属性。当您使用new调用函数来创建实例时,您可以访问分配给原型属性的值。您可以使用此访问这些值。

function Thing() {
  console.log(this.foo);
}

Thing.prototype.foo = "bar";

var thing = new Thing(); //logs "bar"
console.log(thing.foo);  //logs "bar"

在原型上分配数组或对象通常是错误的。如果希望每个实例都有自己的数组,请在函数中创建它们,而不是在原型中创建它们。

function Thing() {
    this.things = [];
}

var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");
console.log(thing1.things); //logs ["foo"]
console.log(thing2.things); //logs []

反对这个

您可以在对象的任何函数中使用它来引用该对象的其他财产。这与使用new创建的实例不同。

var obj = {
    foo: "bar",
    logFoo: function () {
        console.log(this.foo);
    }
};

obj.logFoo(); //logs "bar"

DOM事件this

在HTML DOM事件处理程序中,这始终是对事件附加到的DOM元素的引用

function Listener() {
    document.getElementById("foo").addEventListener("click",
       this.handleClick);
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs "<div id="foo"></div>"
}

var listener = new Listener();
document.getElementById("foo").click();

除非您绑定上下文

function Listener() {
    document.getElementById("foo").addEventListener("click", 
        this.handleClick.bind(this));
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs Listener {handleClick: function}
}

var listener = new Listener();
document.getElementById("foo").click();

HTML此

在可以放入JavaScript的HTML属性中,这是对元素的引用。

<div id="foo" onclick="console.log(this);"></div>
<script type="text/javascript">
document.getElementById("foo").click(); //logs <div id="foo"...
</script>

评估此

您可以使用eval访问此。

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    eval("console.log(this.foo)"); //logs "bar"
}

var thing = new Thing();
thing.logFoo();

用这个

您可以使用with将其添加到当前范围中,以读取和写入其值,而无需显式引用它。

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    with (this) {
        console.log(foo);
        foo = "foo";
    }
}

var thing = new Thing();
thing.logFoo(); // logs "bar"
console.log(thing.foo); // logs "foo"

jQuery this

jQuery在很多地方都会引用DOM元素。

<div class="foo bar1"></div>
<div class="foo bar2"></div>
<script type="text/javascript">
$(".foo").each(function () {
    console.log(this); //logs <div class="foo...
});
$(".foo").on("click", function () {
    console.log(this); //logs <div class="foo...
});
$(".foo").each(function () {
    this.click();
});
</script>