JavaScript - This

来源:互联网 发布:蘑菇云刷机软件 编辑:程序博客网 时间:2024/06/05 00:10

One of most confusing mechanisms in JavaScript is the this identifier. It’s a special identifier keyword that’s automatically defined in the scope of every function.

1. How to use this

this refers to the context (or object) that calls a function. (which means it defines the “scope” of the function)

function identify() {    return this.name.toUpperCase();}function speak() {    var greeting = "Hello, I'm " + identify.call( this );    console.log( greeting );}var me = {    name: "Kyle"};var you = {    name: "Reader"};// call(x) is explicitly invoking a function by x.identify.call( me ); // KYLEidentify.call( you ); // READERspeak.call( me ); // Hello, I'm KYLEspeak.call( you ); // Hello, I'm READER

identify() and speak() functions to be re-used against multiple context (me and you) objects, rather than needing a separate version of the function for each object.


Instead of relying on this, you could have explicitly passed in a context object to both identify() and speak(). Like:

function identify(context) {    return context.name.toUpperCase();}function speak(context) {    var greeting = "Hello, I'm " + identify( context );    console.log( greeting );}identify( you ); // READERspeak( me ); // Hello, I'm KYLE

However, you’ll see that passing context around as an explicit parameter is often messier than passing around a this context.

When we explore objects and prototypes, you will see the helpfulness of a collection of functions being able to automatically reference the proper context object.


2. Eliminate the confusion of this

The name “this” creates confusion when developers try to think about it too literally. There are two meanings often assumed, but both are incorrect. this is not a reference to the function itself or its scope.

1. Itself

The first common temptation is to assume this refers to the function itself. That’s a reasonable grammatical inference, at least.

Many developers may believe that this refer to a function from inside itself. The most common reasons would be things like recursion (calling a function from inside itself) or having an event handler that can unbind itself when it’s first called. lets you store state (values in properties) between function calls.

But for just a moment, we’ll explore that pattern, to illustrate how this doesn’t let a function get a reference to itself like we might have assumed.

function foo(num) {    console.log( "foo: " + num );    // keep track of how many times `foo` is called    this.count++;}foo.count = 0;var i;for (i=0; i<10; i++) {    if (i > 5) {        foo( i );    }}// foo: 6// foo: 7// foo: 8// foo: 9// how many times was `foo` called?console.log( foo.count ); // 0 -- WTF?

foo.count is still 0, even though the four console.log statements clearly indicate foo(..) was in fact called four times. The frustration stems from a too literal interpretation of what this (in this.count++) means.
Note: the this.count++ in the code refers to a global variable count which is not declared yet. So when it runs, it will be declared automatically and its value it NaN. //NaN++ is also a NaN.

When the code executes foo.count = 0, indeed it’s adding a property count to the function object foo. But for the this.count reference inside of the function, this is not in fact pointing at all to that function object, and so even though the property names are the same, the root objects are different, and confusion ensues.


To reference a function object from inside itself, this by itself will typically be insufficient. You generally need a reference to the function object via a lexical identifier (variable) that points at it.

function foo() {    foo.count = 4; // `foo` refers to itself}setTimeout( function(){    // anonymous function (no name), cannot    // refer to itself}, 10 );

So another solution to our running example would have been to use the foo identifier as a function object reference in each place, and not use this at all, which works:

function foo(num) {    console.log( "foo: " + num );    // keep track of how many times `foo` is called    foo.count++;}foo.count = 0;var i;for (i=0; i<10; i++) {    if (i > 5) {        foo( i );    }}// foo: 6// foo: 7// foo: 8// foo: 9// how many times was `foo` called?console.log( foo.count ); // 4

Yet another way of approaching the issue is to force this to actually point at the foo function object:

function foo(num) {    console.log( "foo: " + num );    // keep track of how many times `foo` is called    // Note: `this` IS actually `foo` now, based on    // how `foo` is called (see below)    this.count++;}foo.count = 0;var i;for (i=0; i<10; i++) {    if (i > 5) {        // using `call(..)`, we ensure the `this`        // points at the function object (`foo`) itself        foo.call( foo, i );    }}// foo: 6// foo: 7// foo: 8// foo: 9// how many times was `foo` called?console.log( foo.count ); // 4

2 . Scope

The next most common misconception about the meaning of this is that it somehow refers to the function’s scope. It’s a tricky question, because in one sense there is some truth, but in the other sense, it’s quite misguided.

To be clear, this does not, in any way, refer to a function’s lexical scope.

Consider code which attempts (and fails!) to cross over the boundary and use this to implicitly refer to a function’s lexical scope:

function foo() {    var a = 2;    this.bar();}function bar() {    console.log("invoking bar");    console.log( this.a ); // failed to access "var a"}foo(); //undefined

However, the developer who writes such code is attempting to use this to create a bridge between the lexical scopes of foo() and bar(), so that bar() has access to the variable a in the inner scope of foo(). No such bridge is possible.

Every time you feel yourself trying to mix lexical scope look-ups with this, remind yourself: there is no bridge.


3. What is this?

We said earlier that this is not an author-time binding but a run-time binding. It is contextual based on the conditions of the function’s invocation (who call the function).

this binding has nothing to do with where a function is declared, but has instead everything to do with the manner in which the function is called.

When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference which will be used for the duration of that function’s execution.

For the previous example:

var a = 4;function foo() {    var a = 2;    this.bar();}function bar() {    console.log( this.a );}foo(); // 4

foo() is called by the window or global. So this refers to the global context. It will emit 4 (global variable) instead. There is no bridge between two function scopes.


4. Four rules determine where this will point

a. Default Binding

function foo() {    console.log( this.a );}var a = 2;foo(); // 2

The first thing to note, is that variables declared in the global scope, as var a = 2 is, are synonymous with global-object properties of the same name. They’re not copies of each other, they are each other. Think of it as two sides of the same coin.

Secondly, we see that when foo() is called, this.a resolves to our global variable a. Why? Because in this case, the default binding for this applies to the function call, and so points this at the global object.(window)

If strict mode is in effect, the global object is not eligible for the default binding, so the this is instead set to undefined.

function foo() {    "use strict";    console.log( this.a );}var a = 2;foo(); // TypeError: `this` is `undefined`

A subtle but important detail is: even though the overall this binding rules are entirely based on the call-site, the global object is only eligible for the default binding if the contents of foo() are not running in strict mode; the strict mode state of the call-site of foo() is irrelevant.

function foo() {    console.log( this.a );}var a = 2;(function(){    "use strict";//There is no strict mode in the content of foo() function    foo(); // 2})();

b. Implicit Binding

Another rule to consider is: does the call-site have a context object, also referred to as an owning or containing object, though these alternate terms could be slightly misleading.

function foo() {    console.log( this.a );}var obj = {    a: 2,    foo: foo};obj.foo(); // 2

Note: Only the top/last level of an object property reference chain matters to the call-site. For instance:

function foo() {    console.log( this.a );}var obj2 = {    a: 42,    foo: foo};var obj1 = {    a: 2,    obj2: obj2};obj1.obj2.foo(); // 42, not the obj1.a, but obj2.a

Implicitly Lost: One of the most common frustrations that this binding creates is when an implicitly bound function loses that binding, which usually means it falls back to the default binding, of either the global object or undefined, depending on strict mode.

function foo() {    console.log( this.a );}var obj = {    a: 2,    foo: foo};var bar = obj.foo; // function reference/alias!var a = "oops, global"; // `a` also property on global objectbar(); // "oops, global"

Even though bar appears to be a reference to obj.foo, in fact, it’s really just another reference to foo itself. Moreover, the call-site is what matters, and the call-site is bar(), which is a plain, un-decorated call and thus the default binding applies. (this points to global object)

Note: The more subtle, more common, and more unexpected way this occurs is when we consider passing a callback function:

function foo() {    console.log( this.a );}function doFoo(fn) {    // `fn` is just another reference to `foo`    fn(); // <-- call-site!}var obj = {    a: 2,    foo: foo};var a = "oops, global"; // `a` also property on global objectdoFoo( obj.foo ); // "oops, global"

Parameter passing is just an implicit assignment, and since we’re passing a function, it’s an implicit reference assignment, so the end result is the same as the previous snippet.


c. Explicit Binding

We had to mutate the object in question to include a reference on itself to the function, and use this property function reference to indirectly (implicitly) bind this to the object.

But, what if you want to force a function call to use a particular object for the this binding, without putting a property function reference on the object?

Specifically, functions have call(..) and apply(..) methods. They both take, as their first parameter, an object to use for the this, and then invoke the function with that this specified.

function foo() {    console.log( this.a );}var obj = {    a: 2    // no function reference here};foo.call( obj ); // 2

d. new Binding

JavaScript has a new operator, and the code pattern to use it looks basically identical to what we see in those class-oriented languages; most developers assume that JavaScript’s mechanism is doing something similar. However, there really is no connection to class-oriented functionality implied by new usage in JS.

In JS, constructors are just functions that happen to be called with the new operator in front of them. They are not attached to classes, nor are they instantiating a class.

When a function is invoked with new in front of it, otherwise known as a constructor call, the following things are done automatically:

  • a brand new object is created
  • he newly constructed object is [[Prototype]]-linked
  • the newly constructed object is set as the this binding for that function call
  • unless the function returns its own alternate object, the new-invoked function call will automatically return the newly constructed object.
function foo(a) {    this.a = a;}var bar = new foo( 2 );console.log( bar.a ); // 2

By calling foo(..) with new in front of it, we’ve constructed a new object and set that new object as the this for the call of foo(..).

So new is the final way that a function call’s this can be bound. We’ll call this new binding.


5. The order of binding rules

function foo() {    console.log( this.a );}var obj1 = {    a: 2,    foo: foo};var obj2 = {    a: 3,    foo: foo};obj1.foo(); // 2obj2.foo(); // 3obj1.foo.call( obj2 ); // 3obj2.foo.call( obj1 ); // 2

So, explicit binding takes precedence over implicit binding, which means you should ask first if explicit binding applies before checking for implicit binding.

function foo(something) {    this.a = something;}var obj1 = {    foo: foo};var obj2 = {};obj1.foo( 2 );console.log( obj1.a ); // 2obj1.foo.call( obj2, 3 );console.log( obj2.a ); // 3var bar = new obj1.foo( 4 );console.log( obj1.a ); // 2console.log( bar.a ); // 4

OK, new binding is more precedent than implicit binding. But do you think new binding is more or less precedent than explicit binding?

Note: new and call/apply cannot be used together, so new foo.call(obj1) is not allowed, to test new binding directly against explicit binding. But we can still use a hard binding to test the precedence of the two rules.

default binding is the lowest.

0 0
原创粉丝点击