this - 图1

  • this的绑定在创建执行上下文时确定

  • 大多数情况函数调用的方式决定this的值,this在执行时无法赋值

  • this的值为当前执行的环境对象,非严格下总是指向一个对象,严格下可以是任意值

  • 全局环境下this始终指向window,严格模式下函数的调用没有明确调用对象的情况下,函数内部this指向undefined,非严格下指向window

  • 箭头函数的this永远指向创建当前词法环境时的this

  • 作为构造函数时,函数中的this指向实例对象

  • this的绑定只受最靠近调用它的成员的引用

  • 执行上下文在被执行的时候才会创建,创建执行上下文时才会绑定this,所以this的指向永远是在执行时确定

详解

this 绑定规则

1.默认绑定

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

在这里this指向全局作用域,因为函数的调用位置实在全局作用域中,像foo()这种不带任何修饰的函数引用进行调用时,只能使用默认绑定,这也是最常见的d独立函数调用

2.隐式绑定

隐式绑定是当调用位置周围含有上下文对象时需要考虑的

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

当函数调用位置周围有上下文对象时,隐式绑定会把函数中的thisb绑定到这个上下文对象。换句话说,在这里我们在obj对象中调用了foo()函数,调用位置正好处于obj对象中,因此隐式绑定把函数中的this绑定到了obj对象上。

一连串的函数调用会有函数调用栈,那么函数包含在一连串的对象中时,属于对象调用链

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

这里是因为对象属性链中只有第一层在调用位置中起作用,换句话说,函数调用位置只在第一层对象中绑定。foo()最开始是在obj2被调用的,因此this绑定到了obj2上,接着obj2中的foo属性又在obj1中调用了,但是此时我们已经不再考虑h后面的this绑定了,因为this的绑定已经终结在了第一层。

ps:隐式丢失含义 实例

  1. function foo() {
  2. console.log(this.a);
  3. };
  4. var obj = {
  5. a: 1,
  6. foo: foo
  7. };
  8. var bar = obj.foo; //传递了函数,隐式绑定丢失了
  9. var a = 'hello';
  10. bar(); // 'hello'

首先在这里我们创建了一个bar全局变量,并且把obj中的foo属性传递给了它,由于foo属性对应一个函数,因此 var bar = obj.foo 相当于把foo(…)这个函数传给了bar,当我们再调用bar时,其实是一个不带任何修饰的函数调用,因此应用了默认绑定,这时隐式绑定就丢失了。

3.显式绑定

我们希望this绑定在哪个对象上我们就用方法绑定它,具体可以使用call()、apply()、bind()达到这个效果,需要注意的是一旦我们显式绑定过就无法再绑定了

  1. var obj1 = {
  2. a:1,
  3. say1: function() {
  4. console.log(this.a) //obj2没有call之前 this是指向obj1的
  5. }
  6. }
  7. var obj2 = {
  8. a:2,
  9. say2:function() {
  10. obj1.say.call(this) //call之后obj1的this指向了obj2
  11. }
  12. }
  13. obj2.say2() // 2
  14. obj1.say1() //1

4.new 绑定

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

首先,创建了一个新的对象bar,当使用new来调用foo(..)函数时,我们会把bar对象中的this绑定在foo(...)函数中,因此这里bar对象中的a指向foo(..)函数中的a,所以输出是2。

绑定优先级

  • 隐式绑定VS显式绑定
  1. function foo() {
  2. console.log(this.a);
  3. }
  4. var obj1 = {
  5. a: 2,
  6. foo: foo
  7. }
  8. var obj2 = {
  9. a: 3,
  10. foo: foo
  11. }
  12. obj1.foo(); // 2
  13. obj2.foo(); // 3
  14. obj1.foo.call(obj2); // 3 显示绑定
  15. obj2.foo.call(obj1); // 2 显示绑定

从上面这个例子我们可以看出显示绑定的优先级要高于隐式绑定,因为在隐式绑定之后我仍可以用显式绑定。

  • new绑定VS隐式绑定
  1. function foo(something) {
  2. this.a = something;
  3. }
  4. var obj1 = {
  5. foo: foo
  6. }
  7. var obj2 = {}
  8. obj1.foo(2);
  9. console.log(obj1.a); // 2
  10. var bar = new obj1.foo(4);
  11. console.log(obj1.a); // 2
  12. console.log(bar.a); // 4

从上面这个例子我们可以看出new绑定的优先级要高于隐式绑定,因为在隐式绑定之后我仍可以用new绑定。

  • new绑定VS显式绑定
  1. function foo(something) {
  2. this.a = something;
  3. }
  4. var obj1 = {};
  5. var bar = foo.bind(obj1);
  6. bar(2);
  7. console.log(obj1.a); // 2
  8. var baz = new bar(3);
  9. console.log(obj1.a); // 2 ??
  10. console.log(baz.a); // 3

当我们创建baz时,实际上是创建了一个新对象,新对象的this指向函数调用的this,因此虽然前面bar(..)被硬绑定到了obj1上,但是new绑定修改了bar(..)中的this,该this最终指向foo(..)函数,所以obj1中的a并没有被修改,同时在baz中创建了一个新的属性。从上面这个例子我们可以看出new绑定的优先级要高于显式绑定,因为在显式绑定之后我仍可以用new绑定。