协慌网

登录 贡献 社区

“this” 关键字如何运作?

我注意到,似乎没有明确解释this关键字是什么以及如何在 Stack Overflow 站点上的 JavaScript 中正确(和错误地)使用它。

我亲眼目睹了一些非常奇怪的行为,并且无法理解为什么会发生这种行为。

怎么this时候它应该被用来工作?

答案

我建议先阅读Mike West的文章Scope in JavaScriptmirror )。它是对 JavaScript 中this和范围链的概念的一个出色,友好的介绍。

一旦你开始习惯this ,规则实际上非常简单。 ECMAScript 5.1 标准定义了this

§11.1.1this关键字

this关键字的计算结果为当前执行上下文的 ThisBinding 的值

ThisBinding 是 JavaScript 解释器在评估 JavaScript 代码时维护的东西,比如一个特殊的 CPU 寄存器,它保存对象的引用。每当在三种不同情况之一中建立执行上下文时,解释器就会更新 ThisBinding:

1. 初始全局执行上下文

这是在顶层评估的 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 代码

  • ... 通过直接调用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在环境中的箭头函数是在所定义。

只是为了好玩,用一些例子来测试你的理解

要显示答案,请将鼠标悬停在浅黄色框上。

  1. 什么是值this在标线?为什么?

    window - 标记的行在初始全局执行上下文中计算。

    if (true) {
        // What is `this` here?
    }
  2. 什么是价值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);

  3. 什么是值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);

  4. 什么是值this在标线?为什么?

    window

    这个很棘手。在评估 eval 代码时, thisobj 。然而,在 EVAL 代码, myFun不是在对象上调用,所以 ThisBinding 设置为window的呼叫。

    function myFun() {
        return this; // What is `this` here?
    }
    var obj = {
        myMethod: function () {
            eval("myFun()");
        }
    };
  5. 什么是值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]

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

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,...
  • 到目前为止, applycallbind之间的区别必须明显。 apply允许指定用作类数组对象的参数,即具有数字length属性的对象和相应的非负整数属性。而call允许直接指定函数的参数。 applycall立即调用指定上下文中的函数并使用指定的参数。另一方面, 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
  • 当我们使用 Microsoft 的事件注册模型方法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>

Javascript 就是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 中调用了本机方法,比如forEachslice ,你应该已经知道在这种情况下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关键字时会发生什么。

  1. 使用new关键字调用该函数将立即初始化Person类型的Object
  2. Object的构造函数的构造函数设置为Person 。另请注意, typeof awal仅返回Object
  3. 这个新的Object将被赋予Person.prototype的原型。这意味着Person原型中的任何方法或属性都可用于Person所有实例,包括awal
  4. 现在调用函数Person本身; this是对新构造的对象的awal

很简单,嗯?

请注意,官方的 ECMAScript 规范没有说明这类函数是实际的constructor函数。它们只是普通函数, new可以用于任何函数。只是我们这样使用它们,所以我们只将它们称为它们。

在函数上调用函数: callapply

所以是的,因为function s 也是Objects (实际上是 Javascript 中的第一类变量),所以即使函数也有...... 好吧,函数本身。

所有的功能从全球继承Function ,及其两个多方法callapply ,都可以用来操纵的值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])

请注意, callapply可以通过我们在第二个项目符号中讨论的点方法调用来覆盖this set 的值。很简单:)

提出...... bind

bindcallapply的兄弟。它也是 Javascript 中全局Function构造函数的所有函数继承的方法。 bindcall / apply之间的区别在于callapply都会实际调用该函数。另一方面, bind使用thisArgarguments预设返回一个新函数。让我们举个例子来更好地理解这个:

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

看到三者之间的区别?它很微妙,但它们的使用方式不同。与callapply一样, bind也会通过点方法调用覆盖this set 的值。

另请注意,这三个函数都不会对原始函数进行任何更改。 callapply将从新构造的函数返回值,而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从原来的范围。因此,我们 “复制” thisthat和所使用的副本,而不是this 。聪明,是吗?

指数:

  1. 什么是举办this默认?
  2. 如果我们将该函数称为具有 Object-dot 表示法的方法,该怎么办?
  3. 如果我们使用new关键字怎么办?
  4. 我们如何通过callapply操纵this
  5. 使用bind
  6. 复制this以解决嵌套范围问题。