this及call、apply和bind的详解

118 阅读4分钟

一、this 指向

在 JavaScript 中,this 的指向是一个重要且有时令人困惑的概念。它的值在不同的上下文中可能不同,主要取决于函数的调用方式。下面详细介绍普通函数和箭头函数中 this 的指向。

1. 普通函数中的 this

普通函数中的 this 指向取决于函数的调用方式:

  • 全局上下文

    • 在浏览器中,如果你在全局作用域中调用一个函数,this 指向 window 对象(在严格模式下,thisundefined)。

    • 例如:

      function show() {
          console.log(this); // 浏览器中输出 window
      }
      show();
      
  • 对象方法

    • 当函数作为对象的方法被调用时,this 指向调用该方法的对象。

    • 例如:

      const person = {
          name: 'Alice',
          greet: function() {
              console.log(this.name); // 输出 "Alice"
          }
      };
      person.greet();
      
  • 构造函数

    • 当函数被用作构造函数(即通过 new 关键字调用时),this 指向新创建的对象实例。

    • 例如:

      function Person(name) {
          this.name = name;
      }
      const alice = new Person('Alice');
      console.log(alice.name); // 输出 "Alice"
      
  • 事件处理函数

    • 在事件处理函数中,this 指向触发事件的 DOM 元素。

    • 例如:

      document.getElementById('myButton').addEventListener('click', function() {
          console.log(this); // 输出触发事件的 DOM 元素
      });
      

2. 箭头函数中的 this

箭头函数的 this 行为与普通函数不同:

  • 词法绑定

    • 箭头函数不会创建自己的 this,它的 this 绑定来自于它定义时的上下文(词法作用域)。因此,箭头函数中的 this 值由外部上下文决定。

    • 例如:

      const person = {
          name: 'Alice',
          greet: function() {
              setTimeout(() => {
                  console.log(this.name); // 输出 "Alice",因为箭头函数中的 `this` 继承自 `greet` 方法的 `this`
              }, 1000);
          }
      };
      person.greet();
      

【备注:箭头函数的其他特性】

  1. 箭头函数没有constructor,是匿名函数,不能作为构造函数,不能通过 new 调用;
  2. 没有new.target 属性。在通过 new 运算符被初始化的函数或构造方法中,new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是 undefined
  3. 箭头函数不绑定Arguments 对象。取而代之用 rest 参数...解决。由于 箭头函数没有自己的 this 指针,通过 call() 或 apply() 方法调用一个函数时,只能传递参数(不能绑定 this),他们的第一个参数会被忽略。(这种现象对于 bind 方法同样成立)
  4. 箭头函数没有原型属性 Fn.prototype 值为 undefined
  5. 箭头函数不能当做 Generator 函数,不能使用 yield 关键字

注意:箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。

参考:箭头函数与普通函数的区别

二、callapplybind 的作用和区别

callapplybind 是 Function 对象上的方法,用于改变函数的 this 指向。

1. call

  • 作用

    • call 方法调用函数时,明确指定 this 的值,并且可以立即执行该函数。

    • 语法

      function func(arg1, arg2, ...) {
          // 函数体
      }
      func.call(thisArg, arg1, arg2, ...);
      
    • 示例

      function greet(greeting) {
          console.log(`${greeting}, ${this.name}`);
      }
      const person = { name: 'Alice' };
      greet.call(person, 'Hello'); // 输出 "Hello, Alice"
      

2. apply

  • 作用

    • apply 方法调用函数时,明确指定 this 的值,并且可以传入一个数组(或类数组对象)作为参数。

    • 语法

      function func(arg1, arg2, ...) {
          // 函数体
      }
      func.apply(thisArg, [arg1, arg2, ...]);
      
    • 示例

      function greet(greeting, punctuation) {
          console.log(`${greeting}, ${this.name}${punctuation}`);
      }
      const person = { name: 'Alice' };
      greet.apply(person, ['Hello', '!']); // 输出 "Hello, Alice!"
      

3. bind

  • 作用

    • bind 方法创建一个新的函数,调用时 this 被绑定到指定的 thisArg,并且可以预设参数。

    • 语法

      function func(arg1, arg2, ...) {
          // 函数体
      }
      const boundFunc = func.bind(thisArg, arg1, arg2, ...);
      
    • 示例

      function greet(greeting, punctuation) {
          console.log(`${greeting}, ${this.name}${punctuation}`);
      }
      const person = { name: 'Alice' };
      const boundGreet = greet.bind(person, 'Hello');
      boundGreet('!'); // 输出 "Hello, Alice!"
      

区别总结

相同:

  1. 都是用来改变函数的 this 对象的指向的。
  2. 第一个参数都是 this 要指向的对象。
  3. 都可以利用后续参数传参。

不同:

  1. apply 和 call 传入的参数列表形式不同。apply 接收 arguments,call 接收一串参数列表
  2. bind:语法和 call 一模一样,区别在于立即执行还是等待执行,bind 不兼容 IE6~8
  3. bind 主要就是将函数绑定到某个对象,bind()会创建一个函数,返回对应函数便于稍后调用;而 apply、call 则是立即调用。

基于 Function.prototype 上的 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表, bind方法通过传入一个对象,返回一个this 绑定了传入对象的新函数。这个函数的 this指向除了使用new 时会被改变,其他情况下都不会改变。若为空默认是指向全局对象 window。