关于this

关于this

任何足够先进技术和魔法无异

实际上,JavaScript中this的机制并没有那么先进,但是开发者往往把理解过程复杂化,毫无疑问,在缺乏清晰认识的情况下,this对你来说完全是一种魔法。

为什么要用到this:

如果不使用this,那就需要给函数传入一个上下文对象,而this提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以让API设计得更加简洁易于复用。

this是什么

太拘泥于“this”的字面意思就会产生一些误解。有两种常见的对于 this 的解释,但是它 们都是错误的。

学习 this 的第一步是明白 this 既不指向函数自身也不指向函数的词法作用域,你也许被 这样的解释误导过,但其实它们都是错误的。
this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

this全面解析

每个函数的 this 是在调用 时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。

调用位置

调用位置就是函数在代码中被调用的 位置(而不是声明的位置)。

绑定规则

默认绑定

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

函数调用时应用了 this 的默认绑定,因此 this 指向全局对象,注意在严格模式下,全局对象无法使用默认绑定,因此this会绑定到undefined

隐式绑定

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

隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。因为调 用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。

隐式丢失

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

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

虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

显式绑定

使用函数的 call(..) 和 apply(..) 方法

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

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

装箱:如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对 象,这个原始值会被转换成它的对象形式(也就是new String(..)、new Boolean(..)或者 new Number(..))。

硬绑定

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

Function.prototype. bind:

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

new绑定

在 JavaScript 中,构造函数只是一些 使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上, 它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。

使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

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

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

使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 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()

小结

  1. 由new调用?绑定到新创建的对象。

  2. 由call或者apply(或者bind)调用?绑定到指定的对象。

  1. 由上下文对象调用?绑定到那个上下文对象。
  1. 默认:在严格模式下绑定到undefined,否则绑定到全局对象。

一定要注意,有些调用可能在无意中使用默认绑定规则。如果想“更安全”地忽略 this 绑 定,你可以使用一个DMZ对象,比如ø = Object.create(null),以保护全局对象。

ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和ES6之前代码中的self = this机制一样。