完全理解javascript中的this指针

http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/javascript

https://www.bennadel.com/blog/2265-changing-the-execution-context-of-javascript-functions-using-call-and-apply.htmhtml

this关键字对于javascript初学者,即使是老手可能都是一个比较容易搞晕的东西。本文试图理顺这个问题。java

this和天然语言的类比

实际上js中的this和咱们天然语言中的代词有相似性。好比英语中咱们写"John is running fast because he is trying to catch the train"node

注意上面的代词"he",咱们固然能够这样写:"John is running fast because John is trying to catch the train" ,这种状况下咱们没有使用this去重用代替John。jquery

在js中this关键字就是一个引用的shortcut,他指代一个object,是执行的代码body的context环境。看下面的代码:web

var person = {
    firstName: "Penelope",
    lastName: "Barrymore",
    fullName: function () {
        ​// Notice we use "this" just as we used "he" in the example sentence earlier?:
        console.log(this.firstName + " " + this.lastName);
    ​// We could have also written this:​
        console.log(person.firstName + " " + person.lastName);
    }
}

若是咱们使用person.firstName,person.lastName的话,咱们的代码可能会产生歧义。好比,若是有一个全局变量,名字就是person,那么person.firstName可能试图从全局的person变量来访问其属性,这可能致使错误,而且难以调试。所以,咱们使用this关键字,不只为了代码更美(做为以个referent),并且为了更加精确而不会产生歧义。相似于刚刚举例的天然语言中,由于代词"he"使得句意更清晰,由于更加清晰地代表咱们正在引用一个特定的John这我的。ajax

正如代词"he"用于引用存于context中无歧义的名词同样,this关键字用于引用一个对象,而这个对象就是function(在该函数中,使用了this指针)所绑定的对象.(the this keyword is similarly used to refer to an object that the function(where this is used) is bound to.) tjson

this基础

什么是执行上下文(execution context)?

在js中,全部的函数体(function body)都能访问this关键字. this keyword就是函数执行的context.默认状况下,this引用着调用该函数的那个对象(也就是thisObj.thisFunction中的thisObj)(this is a reference to the object on which a particular function is called),在js中,全部的函数都会被绑定到一个object.然而,咱们可使用call(), apply()来在运行时变动这种binding关系。浏览器

首先,咱们须要澄清的是:js中全部的函数实际上都是"methods".也就是说全部的function都是某个对象的属性。虽然咱们能够定义看起来像是独立自由的函数,可是实际上这时候,这些含糊被隐式地被建立为window object的属性properties(在node环境中是其余的object,多是process)。这也就意味着,即便咱们建立一个自由独立的function而没有显然指定其context,咱们也能够以window属性的方式来调用该函数。闭包

// Define the free-floating function.
function someMethod(){ ... }

// Access it as a property of Window.
window.someMethod();

既然咱们知道了js的全部函数度做为一个对象的属性而存在,咱们能够具体探讨下执行上下文的默认绑定这个概念了。默认状况下,一个js函数在该函数所属的对象的上下文中执行(js function is executed in the context of the object for which it is a property).也就是说,在函数body中,this关键词就是对父亲对象的引用,看看下面的代码:

//  "this" keyword within the sayHello() method is a reference to the sarah object
sarah.sayHello();
// "this" keyword within the getScreenResolution() function is a reference to the window object (since unbound functions are implicitly bound to the global scope)
getScreenResolution();

以上是默认的状况,除此以外,js提供了一种变动任何一个method的执行上下文的机制: call()或者apply().这两个函数都能用于绑定"this"关键字到一个明确的context(object)上去。

method.call( newThisContext, Param1, ..., Param N )
method.apply( newThisContext, [ Param1, ..., Param N ] );

再看一个复杂一点的代码案例:

<!DOCTYPE html>
<html>
<head>
    <title>Changing Execution Context In JavaScript</title>

    <script type="text/javascript">
        // Create a global variable for context (this lives in the
        // global scope - window).
        var context = "Global (ie. window)";
        // Create an object.
        var objectA = {
            context: "Object A"
        };
        // Create another object.
        var objectB = {
            context: "Object B"
        };
        // -------------------------------------------------- //
        // -------------------------------------------------- //
        // Define a function that uses an argument AND a reference
        // to this THIS scope. We will be invoking this function
        // using a variety of approaches.
        function testContext( approach ){
            console.log( approach, "==> THIS ==>", this.context );
        }
        // -------------------------------------------------- //
        // -------------------------------------------------- //
        // Invoke the unbound method with standard invocation.
        testContext( "testContext()" );
        // Invoke it in the context of Object A using call().
        testContext.call(
            objectA,
            ".call( objectA )"
        );
        // Invoke it in the context of Object B using apply().
        testContext.apply(
            objectB,
            [ ".apply( objectB )" ]
        );
        // -------------------------------------------------- //
        // -------------------------------------------------- //
        // Now, let's set the test method as an actual property
        // of the object A.
        objectA.testContext = testContext;
        // -------------------------------------------------- //
        // -------------------------------------------------- //
        // Invoke it as a property of object A.
        objectA.testContext( "objectA.testContext()" );
        // Invoke it in the context of Object B using call.
        objectA.testContext.call(
            objectB,
            "objectA.testContext.call( objectB )"
        );
        // Invoke it in the context of Window using apply.
        objectA.testContext.apply(
            window,
            [ "objectA.testContext.apply( window )" ]
        );
    </script>
</head>
<body>
    <!-- Left intentionally blank. -->
</body>
</html>

 以上代码的输出以下:

testContext() ==> THIS ==> Global (ie. window)
.call( objectA ) ==> THIS ==> Object A
.apply( objectB ) ==> THIS ==> Object B
objectA.testContext() ==> THIS ==> Object A
objectA.testContext.call( objectB ) ==> THIS ==> Object B
objectA.testContext.apply( window ) ==> THIS ==> Global (ie. window)

 

为何this是undefined?

(function () {
    "use strict";

    this.foo = "bar"; // *this* is undefined, why?
}());
function myConstructor() {
    this.a = 'foo';
    this.b = 'bar';
}

myInstance     = new myConstructor(); // all cool, all fine. a and b were created in a new local object
// 若是是strict mode, 则显示 "TypeError: this is undefined"
myBadInstance  = myConstructor(); // oh my gosh, we just created a, and b on the window object

在js中有一种所谓沙盒模型"boxing" mechanism. 这个盒子在进入被调用函数执行上下文以前将包裹或者变动this object.在匿名函数中,因为在strict mode下并未以obj.method方式来调用匿名函数,所以this就为undefined(缘由是匿名函数就是一个闭包,其做用就是隔离了global scope,所以不会默认到window.method上去).而在非strict mode下则this指向window.

function Request(destination, stay_open) {
    this.state = "ready";
    this.xhr = null;
    this.destination = destination;
    this.stay_open = stay_open;

    this.open = function(data) {
        this.xhr = $.ajax({
            url: destination,
            success: this.handle_response,
            error: this.handle_failure,
            timeout: 100000000,
            data: data,
            dataType: 'json',
        });
    };

    /* snip... */

}

Request.prototype.start = function() {
    if( this.stay_open == true ) {
        this.open({msg: 'listen'});
    } else {

    }
};
var o = new Request(destination, stay_open);
o.start()

this object is not set based on declaration, but by invocation. What it means is that if you assign the function property to a variable like x = o.start and call x()this inside start no longer refers to o

var o = new Request(...);
 setTimeout(function() { o.start(); }, 1000);

 

另外一个角度来看this-functionObj.this

首先,须要知道的是js中的全部函数都有peroperties,就像objects都有properties同样,由于function自己也是一个object对象。this能够看做是this-functionObj的属性,而且只有当该函数执行时,该函数对象的this属性将会被赋值,"it gets the this property ---- a variable with the value of the object that invokes the function where this is used"

this老是指向或者说引用(并包含了对应的value)一个对象,而且this每每在一个function或者说method中来使用。注意:虽然在global scope中咱们能够不在function body中使用this,而是直接在global scope中使用this(实际上指向了window),可是若是咱们在strict mode的话,在global function中,或者说没有绑定任何object的匿名函数中,若是使用this, 那么这个this将是undefined值.

假设this在一个function A中被使用,那么this就将引用着调用 function A的那个对象。咱们须要这个this来访问调用function A对象的method和property.特别地,有些状况下咱们不知道调用者对象的名称,甚至有时候调用者对象根本没有名字,这时就必须用this关键字了!

    var person = {
    firstName   :"Penelope",
    lastName    :"Barrymore",
    // Since the "this" keyword is used inside the showFullName method below, and the showFullName method is defined on the person object,
    // "this" will have the value of the person object because the person object will invoke showFullName ()
    showFullName:function () {
    console.log (this.firstName + " " + this.lastName);
    }

    }
    person.showFullName (); // Penelope Barrymore

再看一个jquery事件处理函数中使用this关键字的常见例子:

    // A very common piece of jQuery code

    $ ("button").click (function (event) {
    // $(this) will have the value of the button ($("button")) object
// because the button object invokes the click () method, this指向button
    console.log ($ (this).prop ("name"));
    });

 The use of $(this), which is jQuery’s syntax for the this keyword in JavaScript, is used inside an anonymous function, and the anonymous function is executed in the button’s click () method. The reason $(this) is bound to the button object is because the jQuery library binds$(this) to the object that invokes the click method. Therefore, $(this) will have the value of the jQuery button ($(“button”)) object, even though $(this) is defined inside an anonymous function that cannot itself access the “this” variable on the outer function.

深刻一步理解this

咱们先抛出一个心法: this不会有value,直到一个object invoke了这个函数(this在这个函数中使用).为了行文方便,咱们将使用this关键字的函数称为thisFunction.

虽然默认状况下,this都会引用定义了this(也就是有this引用)的对象,可是只到一个对象调用了thisFunction,这个this指针才会被赋值。而这个this value只决定于调用了thisFunction的对象。尽管默认状况下this的值就是invoking ojbect(xxObj.thisFunction),可是咱们也能够经过xxObj.thisFunction.call(yyObj,parameters), apply()等方式来修改this的默认值!~

在global scope中使用this

在global scope中,当代码在浏览器中执行时,全部的全局variable和function都被定义在window object上,所以,在一个全局函数中当使用this时,this是引用了全局的window对象的(注意必须是非stric mode哦),而window对象则是整个js应用或者说web page的容器

最让人头疼和误解的this使用场景

有如下几个场景,this会变得很是易于使人误解:

1.当咱们借用一个使用了this的方法method;

2.当咱们将使用了this的method给到一个变量时;

3.当一个使用了this的函数被做为回调函数参数时;

4.当在一个闭包closure里面的函数中使用this时

下面咱们将一一探讨这些状况下this的正确取值是什么

继续下文前,再聊一聊"context"

javascript中context的概念和天然语言中的主语有相似性。“John is the winner who returned the money”.本句的主语是John, 咱们能够说本剧的context上下文就是John,由于本句此时的焦点就在他身上,甚至who这个代词指代的也是前面的这个主语John.正如咱们能够经过使用一个分号   "  ; " 来更改句子的主语同样,咱们也能够经过使用另一个object来调用这个function,从而改变context.

var person = {
   firstName   :"Penelope",
   lastName    :"Barrymore",
   showFullName:function () {
// The "context"
console.log (this.firstName + " " + this.lastName);
 }
}

// The "context", when invoking showFullName, is the person object, when we invoke the showFullName () method on the person object.
// And the use of "this" inside the showFullName() method has the value of the person object,
person.showFullName (); // Penelope Barrymore

// If we invoke showFullName with a different object:
var anotherPerson = {
firstName   :"Rohit",
lastName    :"Khan"
};

// We can use the apply method to set the "this" value explicitly—more on the apply () method later.
// "this" gets the value of whichever object invokes the "this" Function, hence:
person.showFullName.apply (anotherPerson); // Rohit Khan

// So the context is now anotherPerson because anotherPerson invoked the person.showFullName ()  method by virtue of using the apply () method

1. 当方法做为callback方式的传入时,如何fix住里面的this值

    // We have a simple object with a clickHandler method that we want to use when a button on the page is clicked
    var user = {
    data:[
    {name:"T. Woods", age:37},
    {name:"P. Mickelson", age:43}
    ],
    clickHandler:function (event) {
    var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1

    // This line is printing a random person's name and age from the data array
    console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
    }
    }

    // The button is wrapped inside a jQuery $ wrapper, so it is now a jQuery object
    // And the output will be undefined because there is no data property on the button object
    $ ("button").click (user.clickHandler); // Cannot read property '0' of undefined

在上面的代码中,($('button'))本身是一个对象,咱们将user.clickHandler method方法做为callback函数参数传入该jquery对象的click()方法中,咱们知道user.clickHandler()中的this再也不指向user对象了。this将指向user.clickMethod运行地所在的对象--由于this在user.clickHandler方法中定义。而invoking这个user.Handler方法的对象则是button object,---user.clickHandler将在button对象的click方法中被执行。

须要说明的是即便咱们经过user.clickHandler()方式来调用(实际上咱们也必须这么作,由于clickHandler自己就做为user的一个method来定义的,所以必须这么去调用), clickHandler()方法也将以button对象做为上下文去执行,也就是说this如今将指向这个button context对象($('button')).

到这里,咱们能够下一个结论:

At this point, it should be apparent that when the context changes—when we execute a method on some other object than where the object was originally defined, the this keyword no longer refers to the original object where “this” was originally defined, but it now refers to the object that invokes the method where this was defined.

如何能解决这类问题,而且fix住this指向呢?

在上面的例子中,既然咱们老是但愿this.data就是指向到user object的data属性,咱们可使用Bind(),Apply()或者Call()方法去特别设定this的value.

 

 $ ("button").click (user.clickHandler); // 这个clickHandler this指向button jquery对象
$("button").click (user.clickHandler.bind (user)); // P. Mickelson 43 经过bind指定这个clickHandler中的this就是指user

 

2. 如何在一个闭包的inner function中(或匿名函数)fix住this的值

正如上面说起,当咱们使用一个inner method(a closure)时,this也是很是容易搞混淆的。很是重要一点是:closures闭包不能访问外部函数(outer function)的this值,由于this变量只能由函数自己来访问,而不是inner function(的this)

var user = {
    tournament:"The Masters",
    data      :[
    {name:"T. Woods", age:37},
    {name:"P. Mickelson", age:43}
    ],

    clickHandler:function () {
    // the use of this.data here is fine, because "this" refers to the user object, and data is a property on the user object.

    this.data.forEach (function (person) {
    // But here inside the anonymous function (that we pass to the forEach method), "this" no longer refers to the user object.
    // This inner function cannot access the outer function's "this"
   
    console.log ("What is This referring to? " + this); //[object Window]
 
    console.log (person.name + " is playing at " + this.tournament);
    // T. Woods is playing at undefined
    // P. Mickelson is playing at undefined
    })
    }

    }
    user.clickHandler(); // What is "this" referring to? [object Window]

上面代码中,因为匿名函数中的this不能访问外部函数的this,所以当在非strict mode时,this将绑定到global window对象。

维护在匿名函数中使用的this值方案:

var user = {
    tournament:"The Masters",
    data      :[
    {name:"T. Woods", age:37},
    {name:"P. Mickelson", age:43}
    ],
    clickHandler:function (event) {
    // To capture the value of "this" when it refers to the user object, we have to set it to another variable here:
    // We set the value of "this" to theUserObj variable, so we can use it later
    var that = theUserObj = this;
    this.data.forEach (function (person) {
    // Instead of using this.tournament, we now use theUserObj.tournament
    console.log (person.name + " is playing at " + theUserObj.tournament);
    })
    }
    }
    user.clickHandler();
    // T. Woods is playing at The Masters
    //  P. Mickelson is playing at The Masters

3. 当method被赋值给一个变量时,如何fix住this指向

// This data variable is a global variable
    var data = [
    {name:"Samantha", age:12},
    {name:"Alexis", age:14}
    ];

    var user = {
    // this data variable is a property on the user object
    data    :[
    {name:"T. Woods", age:37},
    {name:"P. Mickelson", age:43}
    ],
    showData:function (event) {
    var randomNum = ((Math.random () * 2 | 0) + 1) - 1; // random number between 0 and 1

    // This line is adding a random person from the data array to the text field
    console.log (this.data[randomNum].name + " " + this.data[randomNum].age);
    }

    }

    // Assign the user.showData to a variable
    var showUserData = user.showData;

    // When we execute the showUserData function, the values printed to the console are from the global data array, not from the data array in the user object
    //
    showUserData (); // Samantha 12 (from the global data array)

解决方案是特别地经过使用bind方法来指定this值

 // Bind the showData method to the user object
    var showUserData = user.showData.bind (user);

    // Now we get the value from the user object, because the this keyword is bound to the user object
    showUserData (); // P. Mickelson 43

4. 当借用一个定义了this的method方法时

在js开发中,借用方法是一个很是常见的实例,做为js开发者,咱们必定会常常遇到。

// We have two objects. One of them has a method called avg () that the other doesn't have
    // So we will borrow the (avg()) method
    var gameController = {
    scores  :[20, 34, 55, 46, 77],
    avgScore:null,
    players :[
    {name:"Tommy", playerID:987, age:23},
    {name:"Pau", playerID:87, age:33}
    ]
    }

    var appController = {
    scores  :[900, 845, 809, 950],
    avgScore:null,
    avg     :function () {

    var sumOfScores = this.scores.reduce (function (prev, cur, index, array) {
    return prev + cur;
    });

    this.avgScore = sumOfScores / this.scores.length;
    }
    }
    //If we run the code below,
    // the gameController.avgScore property will be set to the average score from the appController object "scores" array
    // Don't run this code, for it is just for illustration; we want the appController.avgScore to remain null
    gameController.avgScore = appController.avg();

avg方法的"this"再也不指向gameController object,它将指向到appController对象,由于该avg()方法是在appController对象上执行的。

解决方法:

// Note that we are using the apply () method, so the 2nd argument has to be an array—the arguments to pass to the appController.avg () method.
    appController.avg.apply (gameController, gameController.scores);

    // The avgScore property was successfully set on the gameController object, even though we borrowed the avg () method from the appController object
    console.log (gameController.avgScore); // 46.4

    // appController.avgScore is still null; it was not updated, only gameController.avgScore was updated
    console.log (appController.avgScore); // null

gameController对象借用了appController's avg()方法,在appController.avg()中的this value会被设定为gameContrller对象,由于咱们使用了apply()方法。

最后,须要牢记:

Always remember that this is assigned the value of the object that invoked the this Function

相关文章
相关标签/搜索