this 提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计得更加简洁并且易于复用。
this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。 this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。 this 就是记录的其中一个属性,会在函数执行的过程中用到。

绑定规则

默认绑定

最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其它规则时的默认规则。this 指向全局对象。

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

在代码中, foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
如果使用严格模式,那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined。

隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象
对象属性引用链中只有最顶层或者说最后一层会影响调用位置。

  1. function foo() {
  2. console.log(this.a);
  3. }
  4. var obj2 = {
  5. a: 42,
  6. foo: foo
  7. };
  8. var obj1 = {
  9. a: 2,
  10. obj2: obj2
  11. };
  12. obj1.obj2.foo(); // 42

隐式丢失
一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。

  1. function foo() {
  2. console.log(this.a);
  3. }
  4. var obj = {
  5. a: 2,
  6. foo: foo
  7. };
  8. var bar = obj.foo; // 函数别名
  9. var a = "oops, global"; // a 是全局对象的属性
  10. bar(); // "oops, global"

虽然 barobj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
参数传递其实就是一种隐式赋值。

显式绑定

使用 callapply 方法,它们的第一个参数是一个对象,它们会把这个对象绑定到 this ,接着在调用函数时指定这个 this 。因为可以直接指定 this 的绑定对象,因此我们称之为显式绑定。

  1. function foo() {
  2. console.log(this.a);
  3. }
  4. var obj = {
  5. a: 2
  6. };
  7. foo.call(obj); // 2

通过 foo.call(...) ,我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。
关于 call、apply 以及 bind 的详解可参考:apply、call 和 bind 的详解

硬绑定

  1. function foo() {
  2. console.log(this.a);
  3. }
  4. var obj = {
  5. a: 2
  6. };
  7. var bar = function() {
  8. foo.call(obj);
  9. };
  10. bar(); // 2
  11. setTimeout(bar, 100); // 2
  12. //硬绑定的 bar 不可能再修改它的 this
  13. bar.call(window); // 2

我们创建了函数 bar() ,并在它的内部手动调用了 foo.call(obj),因此强制把 foothis 绑定到了 obj 。无论之后如何调用函数 bar ,它总会手动在 obj 上调用 foo 。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值。另一种使用方法就是创建一个可以重复使用的辅助函数。

  1. function foo(something) {
  2. console.log(this.a, something);
  3. return this.a + something;
  4. }
  5. // 简单的辅助绑定函数
  6. function bind(fn, obj) {
  7. return function() {
  8. return fn.apply(obj, arguments);
  9. }
  10. }
  11. var obj = { a: 2 };
  12. var bar = bind(foo, obj);
  13. var b = bar(3); // 2 3
  14. console.log(b); // 5

由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 Function.prototype.bind,它的用法如下:

  1. function foo(something) {
  2. console.log(this.a, something);
  3. return this.a + something;
  4. }
  5. var obj = { a: 2 };
  6. var bar = foo.bind(obj);
  7. var b = bar(3); // 2 3
  8. console.log(b); // 5

bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。

new绑定

在 JavaScript 中,构造函数只是一些使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this 。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

优先级

  • 默认绑定优先级最低
  • 显示绑定高于隐式绑定
  • new 绑定高于隐式绑定
  • new 绑定高于显式绑定

    判断this

  1. 函数是否在 new 中调用(new绑定)?如果是的话 this 绑定的是新创建的对象。var bar = new foo()
  2. 函数是否通过 call 、apply(显式绑定)或者硬绑定调用?如果是的话, this 绑定的是指定的对象。 var bar = foo.call(obj2)
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。 var bar = obj1.foo()
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined ,否则绑定到全局对象。 var bar = foo()

    普通函数中的this

    普通函数中的 this 永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的。

例1:

  1. function a() {
  2. console.log(this); // Window
  3. }
  4. a();

例2:

  1. function a() {
  2. console.log(this); // {b: 1}
  3. }
  4. a.call({b:1});

例3:

  1. var a = 2;
  2. var o = {
  3. a: 1,
  4. b: function() {
  5. console.log(this.a); // 1
  6. }
  7. }
  8. o.b();

例4:

  1. var a = 2;
  2. var o = {
  3. a: 1,
  4. b: function() {
  5. console.log(this.a); // 2
  6. }
  7. };
  8. var d = o.b;
  9. d();

构造函数中的this

如果返回值是一个对象,那么 this 指向的就是那个返回的对象,如果返回值不是一个对象那么 this 还是指向函数的实例。还有一点就是虽然 null 也是对象,但是在这里 this 还是指向函数的实例,因为 null 比较特殊。

箭头函数中的this

函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。