this keyword

Meaning

The this keyword is a very important syntax point. It is no exaggeration to say that most development tasks cannot be completed without understanding its meaning.

As mentioned in the previous chapter, this can be used in constructors to represent instance objects. In addition, this can also be used in other occasions. But no matter what the occasion, this has one thing in common: it always returns an object.

Simply put, this is the object where the property or method is "currently".

this.property;

In the above code, this represents the object where the property property is currently located.

The following is a practical example.

var person = {
  name: "Zhang San",
  describe: function () {
    return "Name:" + this.name;
  },
};

person.describe();
// "Name: Zhang San"

In the above code, this.name represents the object where the name property is located. Since this.name is called in the describe method, and the current object where the describe method is located is person, so this points to person, and this.name is person.name.

Since the properties of an object can be assigned to another object, the current object where the properties are located is mutable, that is, the point of this is mutable.

var A = {
  name: "Zhang San",
  describe: function () {
    return "Name:" + this.name;
  },
};

var B = {
  name: "Li Si",
};

B.describe = A.describe;
B.describe();
// "Name: Li Si"

In the above code, the A.describe property is assigned to B, so B.describe means that the current object where the describe method is located is B, so this.name points to B.name .

Refactoring this example a bit, the dynamic direction of this can be seen more clearly.

function f() {
  return "Name:" + this.name;
}

var A = {
  name: "Zhang San",
  describe: f,
};

var B = {
  name: "Li Si",
  describe: f,
};

A.describe(); // "Name: Zhang San"
B.describe(); // "Name: Li Si"

In the above code, the this keyword is used inside the function f. As the object of f is different, the point of this is also different.

As long as the function is assigned to another variable, the direction of this will change.

var A = {
  name: "Zhang San",
  describe: function () {
    return "Name:" + this.name;
  },
};

var name = "Li Si";
var f = A.describe;
f(); // "Name: Li Si"

In the above code, A.describe is assigned to the variable f, and the internal this will point to the object where f is at runtime (this example is the top-level object).

Look at an example of web programming.

<input type="text" name="age" size="3" onChange="validate(this, 18, 99);" />

<script>
  function validate(obj, lowval, hival) {
    if (obj.value < lowval || obj.value > hival) console.log("Invalid Value!");
  }
</script>

The above code is a text input box. Whenever the user enters a value, the onChange callback function will be called to verify whether the value is within the specified range. The browser will pass in the current object to the callback function, so this means passing in the current object (ie text box), and then the user's input value can be read from this.value.

To sum up, in the JavaScript language, everything is an object, and the operating environment is also an object, so functions run in an object, and this is the object (environment) where the function runs. This does not confuse users, but JavaScript supports dynamic switching of the operating environment. That is to say, the direction of this is dynamic, and there is no way to determine in advance which object it points to. This is the most confusing place for beginners. .

Substance

The design of this in the JavaScript language is related to the data structure in the memory.

var obj = { foo: 5 };

The above code assigns an object to the variable obj. The JavaScript engine will first generate an object { foo: 5 } in memory, and then assign the memory address of this object to the variable obj. In other words, the variable obj is an address (reference). If you want to read obj.foo later, the engine first gets the memory address from obj, then reads the original object from that address, and returns its foo attribute.

The original object is stored in a dictionary structure, and each attribute name corresponds to an attribute description object. For example, the foo property in the above example is actually stored in the following form.

{
  foo: {
    [[value]]: 5
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}

Note that the value of the foo attribute is stored in the value attribute of the attribute description object.

This structure is very clear, the problem is that the value of the attribute may be a function.

var obj = { foo: function () {} };

At this time, the engine will save the function separately in memory, and then assign the address of the function to the value attribute of the foo attribute.

{
  foo: {
    [[value]]: the address of the function
    ...
  }
}

Since the function is a single value, it can be executed in different environments (contexts).

var f = function () {};
var obj = { f: f };

// execute separately
f();

// obj environment execution
obj.f();

JavaScript allows you to reference other variables in the current environment within the function body.

var f = function () {
  console.log(x);
};

In the above code, the variable x is used in the function body. This variable is provided by the operating environment.

Now the problem is coming. Since functions can be executed in different operating environments, a mechanism is needed to obtain the current operating environment (context) inside the function body. Therefore, this appears, and its design purpose is to refer to the current operating environment of the function within the function body.

var f = function () {
  console.log(this.x);
};

In the above code, this.x in the function body refers to x in the current operating environment.

var f = function () {
  console.log(this.x);
};

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// execute separately
f(); // 1

// obj environment execution
obj.f(); // 2

In the above code, the function f is executed in the global environment, and this.x points to the x in the global environment; it is executed in the obj environment, and this.x points to obj.x.

Use occasion

this is mainly used in the following situations.

(1) Global environment

The global environment uses this, which refers to the top-level object window.

this === window; // true

function f() {
  console.log(this === window);
}
f(); // true

The above code shows that, whether it is inside a function or not, as long as it is running in the global environment, this refers to the top-level object window.

(2) Constructor

The this in the constructor refers to the instance object.

var Obj = function (p) {
  this.p = p;
};

The above code defines a constructor Obj. Since this points to the instance object, defining this.p inside the constructor is equivalent to defining the instance object to have a p attribute.

var o = new Obj("Hello World!");
op; // "Hello World!"

(3) Object method

If the method of the object contains this, the point of this is the object where the method is running. Assigning this method to another object will change the direction of this.

However, this rule is not easy to grasp. Please see the code below.

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

obj.foo(); // obj

In the above code, when the obj.foo method is executed, its internal this points to obj.

However, the following usages will change the direction of this.

// case one
(obj.foo = obj.foo)()(
  // window
  // Situation two
  false || obj.foo
)()(
  // window
  // Situation three
  1,
  obj.foo
)(); // window

In the above code, obj.foo is a value. When this value is actually called, the operating environment is no longer obj, but the global environment, so this no longer points to obj.

It can be understood that, inside the JavaScript engine, obj and obj.foo are stored in two memory addresses, called address one and address two. When obj.foo() is called like this, address two is called from address one, so the operating environment of address two is address one, and this points to obj. However, in the above three cases, address two is directly taken out for calling. In this case, the operating environment is the global environment, so this points to the global environment. The above three cases are equivalent to the following code.

// case one
(obj.foo = function () {
  console.log(this);
})()(
  // Equivalent to
  function () {
    console.log(this);
  }
)()(
  // Situation two
  false ||
    function () {
      console.log(this);
    }
)()(
  // Situation three
  1,
  function () {
    console.log(this);
  }
)();

If the method of this is not in the first layer of the object, then this only points to the object of the current layer, and does not inherit the higher layer.

var a = {
  p: "Hello",
  b: {
    m: function () {
      console.log(this.p);
    },
  },
};

abm(); // undefined

In the above code, the abm method is in the second layer of the a object. The this inside the method does not point to a, but points to ab, because the following code is actually executed.

var b = {
  m: function () {
    console.log(this.p);
  },
};

var a = {
  p: "Hello",
  b: b,
};

ab.m(); // equivalent to bm()

If you want to achieve the desired effect, you can only write it as follows.

var a = {
  b: {
    m: function () {
      console.log(this.p);
    },
    p: "Hello",
  },
};

If the method inside the nested object is assigned to a variable at this time, this will still point to the global object.

var a = {
  b: {
    m: function () {
      console.log(this.p);
    },
    p: "Hello",
  },
};

var hello = abm;
hello(); // undefined

In the above code, m is a method inside the multi-layer object. For simplicity, assign it to the hello variable, and when the result is called, this points to the top-level object. To avoid this problem, you can only assign the object where m is located to hello, so that the point of this will not change when it is called.

var hello = ab;
hello.m(); // Hello

Use precautions

Avoid multiple layers of this

Since the direction of this is uncertain, do not include multiple layers of this in the function.

var o = {
  f1: function () {
    console.log(this);
    var f2 = (function () {
      console.log(this);
    })();
  },
};

o.f1();
// Object
// Window

The above code contains two layers of this. After the result is run, the first layer points to the object o, and the second layer points to the global object, because the following code is actually executed.

var temp = function () {
  console.log(this);
};

var o = {
  f1: function () {
    console.log(this);
    var f2 = temp();
  },
};

One solution is to use a variable pointing to the outer layer this in the second layer.

var o = {
  f1: function () {
    console.log(this);
    var that = this;
    var f2 = (function () {
      console.log(that);
    })();
  },
};

o.f1();
// Object
// Object

The above code defines the variable that, which is fixed to the outer this, and then uses that in the inner layer, so that the point of this will not change.

In fact, it is a very common practice to use a variable to fix the value of this, and then the inner function calls this variable, so please be sure to master it.

JavaScript provides a strict mode, and this problem can also be avoided rigidly. In strict mode, if the this inside the function points to the top-level object, an error will be reported.

var counter = {
  count: 0,
};
counter.inc = function () {
  "use strict";
  this.count++;
};
var f = counter.inc;
f();
// TypeError: Cannot read property'count' of undefined

In the above code, the inc method adopts the strict mode through the 'use strict' declaration. At this time, once the internal this points to the top-level object, an error will be reported.

Avoid this in the array processing method

The map and foreach methods of arrays allow you to provide a function as a parameter. this should not be used inside this function.

var o = {
  v: "hello",
  p: ["a1", "a2"],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + "" + item);
    });
  },
};

of();
// undefined a1
// undefined a2

In the above code, the this in the callback function of the foreach method actually points to the window object, so the value of ov cannot be obtained. The reason is the same as the multi-layer this in the previous paragraph, that is, the inner this does not point to the outside, but points to the top-level object.

One way to solve this problem is to use an intermediate variable to fix this as mentioned earlier.

var o = {
  v: "hello",
  p: ["a1", "a2"],
  f: function f() {
    var that = this;
    this.p.forEach(function (item) {
      console.log(that.v + "" + item);
    });
  },
};

of();
// hello a1
// hello a2

Another method is to use this as the second parameter of the foreach method to fix its operating environment.

var o = {
  v: "hello",
  p: ["a1", "a2"],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + "" + item);
    }, this);
  },
};

of();
// hello a1
// hello a2

Avoid this in the callback function

The this in the callback function tends to change its direction, so it is best to avoid using it.

var o = new Object();
of = function () {
  console.log(this === o);
};

// How to write jQuery
$("#button").on("click", of);

In the above code, after clicking the button, the console will display false. The reason is that at this time this no longer points to the o object, but to the button's DOM object, because the f method is called in the context of the button object. Such subtle differences can easily be overlooked in programming, leading to undetectable errors.

In order to solve this problem, you can use the following methods to bind this, that is, make this a fixed point to an object to reduce uncertainty.

Binding this method

The dynamic switching of this certainly creates great flexibility for JavaScript, but it also makes programming difficult and obscure. Sometimes, you need to fix this to avoid unexpected situations. JavaScript provides three methods of call, apply, and bind to switch/fix the point of this.

Function.prototype.call()

The call method of a function instance can specify the point of this inside the function (that is, the scope in which the function is executed), and then call the function in the specified scope.

var obj = {};

var f = function () {
  return this;
};

f() === window; // true
f.call(obj) === obj; // true

In the above code, when the function f is run in the global environment, this points to the global environment (the browser is the window object); the call method can change the point of this and specify that this points to the object obj , And then run the function f in the scope of the object obj.

The parameter of the call method should be an object. If the parameters are empty, null and undefined, the global object is passed in by default.

var n = 123;
var obj = { n: 456 };

function a() {
  console.log(this.n);
}

a.call(); // 123
a.call(null); // 123
a.call(undefined); // 123
a.call(window); // 123
a.call(obj); // 456

In the above code, the keyword this in the a function, if it points to the global object, the return result is 123. If the call method is used to point the this keyword to the obj object, the return result is 456. As you can see, if the call method has no parameters, or the parameters are null or undefined, it is equivalent to pointing to the global object.

If the parameter of the call method is a primitive value, then this primitive value will be automatically converted into the corresponding packaging object, and then passed to the call method.

var f = function () {
  return this;
};

f.call(5);
// Number {[[PrimitiveValue]]: 5}

In the above code, the parameter of call is 5, which is not an object. It will be automatically converted into a wrapper object (an instance of Number), bound to this inside f.

The call method can also accept multiple parameters.

func.call(thisValue, arg1, arg2, ...)

The first parameter of call is the object pointed to by this, and the following parameters are the parameters required for the function call.

function add(a, b) {
  return a + b;
}

add.call(this, 1, 2); // 3

In the above code, the call method specifies that the this inside the function add is bound to the current environment (object), and the parameters are 1 and 2, so the function add gets 3 after running.

One application of the call method is to call the native method of an object.

var obj = {};
obj.hasOwnProperty("toString"); // false

// Override the inherited hasOwnProperty method
obj.hasOwnProperty = function () {
  return true;
};
obj.hasOwnProperty("toString"); // true

Object.prototype.hasOwnProperty.call(obj, "toString"); // false

In the above code, hasOwnProperty is a method inherited by the obj object. If this method is overridden, the correct result will not be obtained. The call method can solve this problem. It puts the original definition of the hasOwnProperty method on the obj object for execution, so that no matter whether there is a method with the same name on the obj, the result will not be affected.

Function.prototype.apply()

The function of the apply method is similar to that of the call method, which is to change the point of this and then call the function. The only difference is that it receives an array as a parameter when the function is executed, and the format is as follows.

func.apply(thisValue, [arg1, arg2, ...])

The first parameter of the apply method is also the object pointed to by this. If it is set to null or undefined, it is equivalent to specifying the global object. The second parameter is an array, and all members of the array are passed into the original function as parameters in turn. The parameters of the original function must be added one by one in the call method, but in the apply method, they must be added in the form of an array.

function f(x, y) {
  console.log(x + y);
}

f.call(null, 1, 1); // 2
f.apply(null, [1, 1]); // 2

In the above code, the f function originally accepts two parameters. After using the apply method, it can accept an array as a parameter.

Using this, some interesting applications can be made.

(1) Find the largest element of the array

JavaScript does not provide a function to find the largest element of an array. Combining the apply method and the Math.max method, you can return the largest element of the array.

var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a); // 15

(2) Change the empty element of the array to undefined

Through the apply method, use the Array constructor to turn the empty elements of the array into undefined.

Array.apply(null, ["a", , "b"]);
// ['a', undefined,'b']

The difference between empty elements and undefined is that the forEach method of the array will skip empty elements, but not undefined. Therefore, when traversing internal elements, you will get different results.

var a = ["a", , "b"];

function print(i) {
  console.log(i);
}

a.forEach(print);
// a
// b

Array.apply(null, a).forEach(print);
// a
// undefined
// b

(3) Convert an array-like object

In addition, by using the slice method of the array object, an array-like object (such as the arguments object) can be converted into a real array.

Array.prototype.slice.apply({ 0: 1, length: 1 }); // [1]
Array.prototype.slice.apply({ 0: 1 }); // []
Array.prototype.slice.apply({ 0: 1, length: 2 }); // [1, undefined]
Array.prototype.slice.apply({ length: 1 }); // [undefined]

The parameters of the apply method of the above code are all objects, but the returned results are all arrays, which serves the purpose of converting objects into arrays. As you can see from the code above, the premise for this method to work is that the processed object must have the length property and the corresponding number key.

(4) Object to bind callback function

The previous button click event example can be rewritten as follows.

var o = new Object();

of = function () {
  console.log(this === o);
};

var f = function () {
  ofapply(o);
  // or ofcall(o);
};

// How to write jQuery
$("#button").on("click", f);

In the above code, after clicking the button, the console will display true. Because the apply() method (or the call() method) not only binds the object where the function is executed, but also executes the function immediately, so the binding statement has to be written in a function body. A more concise way of writing is to use the bind() method described below.

Function.prototype.bind()

The bind() method is used to bind the this in the function body to an object, and then return a new function.

var d = new Date();
d.getTime(); // 1481869925657

var print = d.getTime;
print(); // Uncaught TypeError: this is not a Date object.

In the above code, we assign the d.getTime() method to the variable print, and then call print() to report an error. This is because the this inside the getTime() method binds the instance of the Date object, and after assigning it to the variable print, the internal this no longer points to the instance of the Date object.

The bind() method can solve this problem.

var print = d.getTime.bind(d);
print(); // 1481869925657

In the above code, the bind() method binds the this inside the getTime() method to the d object, and then this method can be safely assigned to other variables.

The parameter of the bind method is the object to be bound to this. The following is a clearer example.

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  },
};

var func = counter.inc.bind(counter);
func();
counter.count; // 1

In the above code, the counter.inc() method is assigned to the variable func. At this time, the bind() method must be used to bind the this inside the inc() to the counter, otherwise an error will occur.

It is also possible to bind this to other objects.

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  },
};

var obj = {
  count: 100,
};
var func = counter.inc.bind(obj);
func();
obj.count; // 101

In the above code, the bind() method binds the this inside the inc() method to the obj object. As a result, after calling the func function, the property of count inside obj is incremented.

bind() can also accept more parameters, and bind these parameters to the parameters of the original function.

var add = function (x, y) {
  return x * this.m + y * this.n;
};

var obj = {
  m: 2,
  n: 2,
};

var newAdd = add.bind(obj, 5);
newAdd(5); // 20

In the above code, in addition to binding the this object, the bind() method also binds the first parameter x of the add() function to 5, and then returns a new function newAdd() , this function can run as long as it accepts another parameter y.

If the first parameter of the bind() method is null or undefined, it is equivalent to binding this to the global object. When the function runs, this points to the top-level object (the browser is window).

function add(x, y) {
  return x + y;
}

var plus5 = add.bind(null, 5);
plus5(10); // 15

In the above code, there is no this inside the function add(). The main purpose of using the bind() method is to bind the parameter x. Every time you run a new function plus5(), you only need It is enough to provide another parameter y. And because there is no this inside add(), the first parameter of bind() is null, but if it is another object, it has no effect.

The bind() method has some precautions.

(1) Return a new function every time

The bind() method returns a new function every time it runs, which can cause some problems. For example, when monitoring an event, it cannot be written as follows.

element.addEventListener("click", ombind(o));

In the above code, the click event is bound to an anonymous function generated by the bind() method. This will make it impossible to unbind, so the following code is invalid.

element.removeEventListener("click", ombind(o));

The correct way is to write the following:

var listener = ombind(o);
element.addEventListener("click", listener);
// ...
element.removeEventListener("click", listener);

(2) Use with callback function

Callback function is one of the most commonly used patterns in JavaScript, but a common mistake is to treat the method containing this directly as a callback function. The solution is to use the bind() method to bind counter.inc() to counter.

var counter = {
  count: 0,
  inc: function () {
    "use strict";
    this.count++;
  },
};

function callIt(callback) {
  callback();
}

callIt(counter.inc.bind(counter));
counter.count; // 1

In the above code, the callIt() method will call the callback function. At this time, if you pass in counter.inc directly, the this inside counter.inc() will point to the global object when called. After using the bind() method to bind counter.inc to counter, there will be no such problem, and this always points to counter.

There is also a more subtle situation, that is, some array methods can accept a function as a parameter. The internal this points of these functions may also be wrong.

var obj = {
  name: "Zhang San",
  times: [1, 2, 3],
  print: function () {
    this.times.forEach(function (n) {
      console.log(this.name);
    });
  },
};

obj.print();
// no output

In the above code, this of this.times inside obj.print points to obj, this is no problem. However, the this.name inside the callback function of the forEach() method points to the global object, so there is no way to get the value. With a slight modification, you can see more clearly.

obj.print = function () {
  this.times.forEach(function (n) {
    console.log(this === window);
  });
};

obj.print();
// true
// true
// true

To solve this problem, bind this through the bind() method.

obj.print = function () {
  this.times.forEach(
    function (n) {
      console.log(this.name);
    }.bind(this)
  );
};

obj.print();
// Zhang San
// Zhang San
// Zhang San

(3) Use with call() method

Using the bind() method, you can rewrite the usage of some JavaScript native methods. Take the slice() method of an array as an example.

[1, 2, 3].slice(0, 1); // [1]
// Equivalent to
Array.prototype.slice.call([1, 2, 3], 0, 1); // [1]

In the above code, the slice method of the array slices another array from [1, 2, 3] according to the specified start position and end position. The essence of this is to call the Array.prototype.slice() method on [1, 2, 3], so you can use the call method to express this process and get the same result.

The call() method essentially calls the Function.prototype.call() method, so the above expression can be rewritten with the bind() method.

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1); // [1]

The meaning of the above code is to turn Array.prototype.slice into the object where the Function.prototype.call method is located, and it becomes Array.prototype.slice.call when called. Similar writing can also be used for other array methods.

var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);

var a = [1, 2, 3];
push(a, 4);
a; // [1, 2, 3, 4]

pop(a);
a; // [1, 2, 3]

If you go further, bind the Function.prototype.call method to the Function.prototype.bind object, which means that the call form of bind can also be rewritten.

function f() {
  console.log(this.v);
}

var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)(); // 123

The meaning of the above code is to bind the Function.prototype.bind method to the Function.prototype.call, so the bind method can be used directly without using it on the function instance.