this可以在哪些场景下使用

在常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中。也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象。
但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义。

在对象中的使用

  1. var obj = {
  2. name: "abc",
  3. say: function () {
  4. console.log(this.name + ": hello!");
  5. },
  6. };
  7. obj.say(); // abc: hello!

在全局中的使用

浏览器环境下:

  1. console.log(this); // window
  2. var msg = "hi";
  3. console.log(this.msg); // hi

Node环境下:

  1. console.log(this); // {}
  2. var msg = "hi";
  3. console.log(this.msg); // undefined

:::info 在开发过程中,很少会在全局下使用this,一般都是函数调用时使用this。 :::

直接调用函数

  1. var msg = "hi";
  2. function foo() {
  3. console.log(this); // window (Node:Object [global])
  4. console.log(this.msg); //hi (Node:undefined)
  5. }
  6. foo();

this绑定的过程

  • 在所有函数被调用时,都会创建一个执行上下文,通常是在函数中使用。
  • 所有上下文记录这函数的调用栈、AO对象等。
  • this会被绑定到上下文环境中。 :::info this是在函数创建执行上下文中被绑定的。也就是在函数调用时绑定的。

  • 函数在调用时,JavaScript会默认给this绑定一个值

  • this的绑定和定义的位置(编写的位置,无论是对象内部还是全局下等)没有关系。
  • this的绑定只和调用方式以及调用的位置有关系;
  • this是在运行时被绑定的;
    :::

    this的绑定规则

    this绑定规则分为四种:

  • 默认绑定

  • 隐身绑定
  • 显示绑定
  • new绑定

    默认绑定

    默认绑定就是函数的一次独立调用。 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用(在严格模式下会指向undefined,而非严格模式下一般会绑定GO对象)。

    案例1——直接调用函数

    ```javascript function foo() { console.log(this); }

foo();

  1. 运行结果:window
  2. ---
  3. <a name="a80mq"></a>
  4. #### 案例2——函数内部调用其他函数
  5. ```javascript
  6. function f1() {
  7. console.log("f1", this);
  8. f2();
  9. }
  10. function f2() {
  11. console.log("f2", this);
  12. f3();
  13. }
  14. function f3() {
  15. console.log("f3", this);
  16. }
  17. f1();

运行结果:
image.png


案例3——函数调用与定义位置的关系

var msg = "hello";

var obj = {
  msg: "obj",
  say: function () {
    console.log(this.msg);
  },
};

var say2 = obj.say;

say2();

运行结果:
image.png :::info 该案例充分说明了this的绑定只和调用方式以及调用的位置有关系和定义位置没有关系。 :::

隐式绑定

隐式绑定就是通过某个对象进行调用,也就是它的调用位置是通过某个对象发起的。

案例1——通过对象调用

var msg = "hello";

var obj = {
  msg: "obj",
  say: function () {
    console.log(this.msg);
  },
};

obj.say(); // obj

案例2——对象调用另一个对象的方法

var msg = "hello";

var obj = {
  msg: "obj",
  say: function () {
    console.log(this.msg);
  },
};

var obj2 = {
  msg: "obj",
  obj1: obj,
};

obj2.obj1.say(); // obj

:::info 隐式绑定有一个前提条件: 必须在调用的对象内部有一个对函数的引用(比如一个属性);如果没有这样的引用,在进行调用时,会报找不到该函数的错误;正是通过这个引用,间接的将this绑定到了这个对象上;
:::

显示绑定

JavaScript所有的函数都可以使用callapply方法(这个和prototype有关)。这两个函数的第一个参数都要求是一个对象,是给this准备的。在调用这个函数时,会将this绑定到这个传入的对象上。


通过call、apply绑定this对象

function foo() {
  console.log(this);
}

foo.call(window); // window
foo.call({ msg: "hello" }); // { msg: "hello" }
foo.call(true); // true

:::info apply和call基本没有什么区别

  • apply函数参数是一个数组
  • call函数参数是参数列表(剩余参数)。 :::

    通过bind总是将一个变量绑定到this上

    ```javascript function foo() { console.log(this); }

var foo2 = foo.bind({ msg: “abc” });

foo(); // window foo2(); // { msg: “abc” }

:::info
bind绑定会返回一个新的函数。
:::
<a name="S86ju"></a>
### new绑定
 JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。<br /> 使用new关键字来调用函数是,会执行如下的操作: 

1. 创建一个全新的对象;
1. 这个新对象会所执行的函数的`prototype`关联;
1. 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
1. 如果函数没有返回其他对象,表达式会返回这个新对象  
```javascript
function Person(name) {
  this.name = name;
  console.log(this);
}

const p = new Person("abc"); // { name: "abc" }

规则绑定优先级

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定 :::warning new绑定是不能和call、apply不允许同时使用。所以不存在谁优先级高
new和bind一起使用时,new优先级更高 :::

this绑定其他规则:

忽略绑定null和undefined

function foo() {
  console.log(this);
}

foo.call(null);
foo.call(undefined);

var foo2 = foo.bind(null);
foo2();

运行结果:
image.png :::info 如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则
:::


间接函数引用

function foo() {
  console.log(this);
}

var obj1 = {
  msg: "obj1",
  foo: foo,
};

var obj2 = {
  msg: "obj2",
};

obj2.foo = obj1.foo;
obj2.foo();

(obj2.foo = obj1.foo)();

image.png

ES6的箭头函数

箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层词法环境(编写位置最近的函数作用域)来决定this的指向。

var msg = "hello";

var obj = {
  msg: "obj",
  foo: () => {
    console.log(this.msg);
  },
};

obj.foo(); // hello

结合setTimeout

var msg = "window msg";

var obj = {
  msg: "obj msg",
  foo: () => {
    setTimeout(() => {
      console.log(this.msg);
    }, 100);
  },
};

obj.foo(); // window msg
var msg = "window msg";

var obj = {
  msg: "obj msg",
  foo: function () {
    setTimeout(() => {
      console.log(this.msg);
    }, 100);
  },
};

obj.foo(); // obj msg

函数参数要求是一个函数

function foo(fn) {
  fn.call("hello");
}

foo(() => {
  console.log(this);
});
// 结果:window,因为foo函数指向的就是window
function foo(fn) {
  fn.call("hello");
}

foo(function() {
  console.log(this);
});
// 结果:hello

JS内置函数的绑定:

内置函数的this绑定取决于内部的执行原理。

案例1——setTimeout

setTimeout(function() 
  console.log(this); // window
}, 100);

默认调用

案例2——数组的forEach()

image.png

thisArg绑定到callbackfn上

案例3——DOM的事件绑定

const btn = document.querySelector("#btn");
btn.onclick = function () {
  console.log(this); 
};

image.png

总结

  1. this的绑定只和函数的调用方式、调用位置有关系,和定义位置没有关系。
  2. 箭头函数不会绑定this,会从编写位置的最近的函数作用域找。