“this”关键字如何工作?

我注意到,似乎并没有清楚地解释this关键字是什么以及它是如何在堆栈溢出网站的JavaScript中正确(和错误地)使用的。

我目睹了一些非常奇怪的行为,并没有理解为什么会发生。

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


我建议先阅读Mike West的文章Scope in JavaScript(镜像)。 这是对JavaScript中this和范围链的概念的一个很好的,友好的介绍。

一旦你开始习惯this ,规则其实很简单。 ECMAScript标准this定义为一个关键字:

评估为当前执行上下文的ThisBinding的值;

(§11.1.1)。 ThisBinding是JavaScript解释器在评估JavaScript代码时维护的东西,就像一个特殊的CPU寄存器,它持有对象的引用。 每当建立执行上下文时,解释器都会更新ThisBinding,只有三种情况之一:

  • 初始全局执行上下文

    对于在遇到<script>元素时评估的JavaScript代码就是这种情况:

    <script type="text/javascript">//<![CDATA[
    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()

    ThisBinding被设置为全局对象,就像在初始全局执行上下文(§10.4.2(1))中执行一样。

  • §15.1.2.1.1定义了对eval()的直接调用是什么。 基本上, eval(...)是直接调用,而类似(0, eval)(...)var indirectEval = eval; indirectEval(...); var indirectEval = eval; indirectEval(...); 是对eval()的间接调用。 在JavaScript中查看chuckj对(1,eval)('this')和eval('this')的回答? 以及Dmitry Soshnikov撰写的博客文章,您可以使用间接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);
    

    测验只是为了好玩,用下面的例子来测试你的理解。

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

  • 什么是价值this在A线? 为什么?

    <script type="text/javascript">
    if (true) {
        // Line A
    }
    </script>
    

    window

    行A在初始全局执行上下文中进行评估。

  • 什么是价值this当B线obj.staticFunction()执行? 为什么?

    <script type="text/javascript">
    var obj = {
        someData: "a string"
    };
    
    function myFun() {
        // Line B
    }
    
    obj.staticFunction = myFun;
    
    obj.staticFunction();
    </script>
    

    obj

    在对象上调用函数时,ThisBinding被设置为该对象。

  • 什么是价值this在C线? 为什么?

    <script type="text/javascript">
    var obj = {
        myMethod : function () {
            // Line C
        }
    };
    var myFun = obj.myMethod;
    myFun();
    </script>
    

    window

    在这个例子中,JavaScript解释器输入函数代码,但是因为myFun / obj.myMethod没有在对象上调用,所以ThisBinding被设置为window

    这与Python不同,在Python中访问方法( obj.myMethod )会创建一个绑定的方法对象。

  • 什么是价值this在行d? 为什么?

    <script type="text/javascript">
    function myFun() {
        // Line D
    }
    var obj = {
        myMethod : function () {
            eval("myFun()");
        }
    };
    obj.myMethod();
    </script>
    

    window

    这个很棘手。 在评估eval代码时, thisobj 。 但是,在eval代码中, myFun不会在对象上调用,因此将ThisBinding设置为window以进行调用。

  • 什么是价值this在E线?

    <script type="text/javascript">
    function myFun() {
        // Line E
    }
    var obj = {
        someData: "a string"
    };
    myFun.call(obj);
    </script>
    

    obj

    该行myFun.call(obj); 正在调用特殊的内置函数Function.prototype.call() ,它接受thisArg作为第一个参数。


  • 与其他语言相比, 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并将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";在严格模式下运行此操作, 到函数声明的开头, this将不再引用任何环境中的全局变量。 这是为了避免在严格模式下的混淆。 this会,在这种情况下,只要登录undefined ,因为那是它是什么,没有定义它。

    在下列情况下,我们将看到如何操作的值this

    在对象上调用函数

    有不同的方法来做到这一点。 如果你已经在Javascript中调用了native方法,比如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关键字时会发生什么。

  • new关键字调用函数会立即初始化Person类型的Object
  • Object的构造函数将其构造函数设置为Person 。 另请注意, typeof awal只会返回Object
  • 这个新Object将被分配Person.prototype的原型。 这意味着Person原型中的任何方法或属性都可用于Person所有实例,包括awal
  • 函数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在任何功能。

    applycall几乎相同,只接受两个参数: thisArg和一个包含要传递给函数的参数的数组。 所以上面的call可以被翻译成这样apply

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

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

    呈现.... 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集合的值。

    还要注意,这三个函数都没有对原函数做任何改变。 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 。 聪明,呃?

    指数:

  • 什么是举办this默认?
  • 如果我们将该函数作为一种使用对象点符号的方法调用,该怎么办?
  • 如果我们使用new关键字怎么办?
  • 我们如何处理thiscall ,并apply
  • 使用bind
  • 复制this来解决嵌套范围的问题。
  • 链接地址: http://www.djcxy.com/p/19423.html

    上一篇: How does the "this" keyword work?

    下一篇: JavaScript: undefined !== undefined?