这就是 this

29 阅读5分钟

深入掌握 JavaScript 中的 this 关键字

this 的概念

this 是什么

thisJavaScript 中一个很重要的关键字,它表示函数执行时,自动生成的一个内部对象,只能在函数内部使用。

this 的指向决定

this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象。

this 各种情况下的指向

全局环境中 this

很多时候都是在 window 下这个对象进行调用的,

const a = 1;
function b() {
  console.log(this.a);  // 1
}

当使用 setTimeout 等函数时,this 指向的是 window 对象。

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

函数内部的 this

    var name = "windowsName";
    function a() {
        var name = "Hit";

        console.log(this.name);          // windowsName

        console.log("inner:" + this);    // inner: Window
    }
    a();
    console.log("outer:" + this)         // outer: Window

对象方法调用中的 this

    var name = "windowsName";
    var a = {
        name: "Hit",
        fn : function () {
            console.log(this.name);      // Hit
        }
    }
    a.fn();

构造函数中的 this

当使用 new 关键字来调用函数时,this 指向的是新创建的对象。

class createImageBitmap {
    constructor() {
        this.name = 'createImageBitmap';
    }
}
var obj = new createImageBitmap();

console.log(obj.name);  //  createImageBitmap

事件回调中的 this

当使用 addEventListener 等函数时,this 指向的是元素对象。

 document.getElementById('xxx').addEventListener('click', function() {
    console.log(this) // 这里的 this 指向的是元素对象
})

强制设置 this 指向的方法

使用 call、apply、bind

当使用 apply()call() 函数时,this 指向的是指定的对象。

    var a = {
        name : "Hit",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.apply(a),100);
        }

    };

    a.func2()            // Hit
    var a = {
        name : "Hit",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.call(a),100);
        }

    };
    a.func2()            // Hit

当使用 bind() 函数时,this 指向的是 bind() 函数的第一个参数。

    var a = {
        name : "Hit",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.bind(a)(),100);
        }

    };

    a.func2()            // Hit

使用箭头函数

当使用箭头函数时,this 指向的是定义它的那个对象。

    var name = "windowsName";

    var a = {
        name : "Hit",

        func1: function () {
            console.log(this.name)     
        },

        func2: function () {
            setTimeout( () => {
                this.func1()
            },100);
        }

    };

    a.func2()     // Hit

使用 new.target

function Person(name, age) {
    // 检测是否被 new 调用
    if (!new.target) { 
      return new Person(name, age); 
    }
  
    this.name = name;
    this.age = age;
  }
  
  // 不使用 new 调用
  let p1 = Person('Jack', 20); 
  console.log(p1.name); // Jack
  
  // 使用 new 调用
  let p2 = new Person('John', 18);
  console.log(p2.name); // John

在 Person 构造函数中检查了 new.target,如果检测到不是通过 new 关键字调用的,就使用 new 重新调用了一次,这样就修正了 this 的指向问题。

这样我们在使用 Person 的时候,不管有没有使用 new,最后都可以正确地获取到实例上的属性,而不需要关心 this 指向的问题。

前端框架中的 this

React 中的 this 指向

类组件

在 React 组件中,如果出现下面这些情况,this 很可能会指向全局对象或者 undefined:

  1. 在组件类中定义的事件处理函数中:
class Foo extends React.Component {

  handleClick() {
    console.log(this); // this 为 undefined
  }

  render() {
    return <button onClick={this.handleClick}>Click</button>
  }
}
  1. 在组件类中定义的定时器或异步请求回调函数中:
class Foo extends React.Component {

  componentDidMount() {
    setTimeout(function() {
      console.log(this); // this 为 window 
    }, 1000);
  }

  render() {
    return <div>Foo</div>
  }  
}
  1. 将组件的方法作为参数传递给子组件时:
class Parent extends React.Component {
  btnClick() {
    // ...
  }

  render() {
    return <Child onClick={this.btnClick} />
  }
}

这些情况下,回调函数或传递的方法会失去组件实例的 this 绑定,进而指向全局对象或 undefined。

原因在于 JavaScript 中的 this 是动态绑定的,当函数被作为回调或者传参调用时,this 绑定情况就可能会出错。

所以为了避免上述情况,需要注意使用箭头函数或者 bind 方法来正确绑定 this。

解决方案:

为了确保在React组件方法中正确地使用this,有几种常见的方式:

  1. 使用箭头函数: 箭头函数在定义时会捕获所在上下文的this值,因此它们在React组件中经常用于处理事件回调。例如:

    class MyComponent extends React.Component {
        handleClick = () => {
            // 在这里,this 指向 MyComponent 的实例
            console.log(this.props);
        }
    
        render() {
            return (
                <button onClick={this.handleClick}>Click me</button>
            );
        }
    }
    
  2. 在构造函数中绑定方法: 可以在组件的构造函数中使用bind方法显式地绑定方法的this。这通常在类的构造函数中执行,确保方法在整个组件实例的生命周期中都有正确的this值。

    class MyComponent extends React.Component {
        constructor(props) {
            super(props);
            this.handleClick = this.handleClick.bind(this);
        }
    
        handleClick() {
            // 在这里,this 指向 MyComponent 的实例
            console.log(this.props);
        }
    
        render() {
            return (
                <button onClick={this.handleClick}>Click me</button>
            );
        }
    }
    
  3. 使用公共类字段语法(class field syntax): 这是在类中定义方法的一种新的语法,它可以确保方法在声明时被绑定到实例。

    class MyComponent extends React.Component {
        handleClick = () => {
            // 在这里,this 指向 MyComponent 的实例
            console.log(this.props);
        }
    
        render() {
            return (
                <button onClick={this.handleClick}>Click me</button>
            );
        }
    }
    

函数式组件

函数式组件没有实例,所以 this 的指向都是 undefined

Vue 中的 this 指向

Vue 中,一个很常见的操作场景是这样的。

methods: {
  handleClick() {
    const _this = this
    document.addEventListener('click', function() {
      // 这里使用 _this 来访问和调用组件的属性和方法
      _this.someMethod() 
    })
  }  
}

因为正常场景下在 document.addEventListener 回调函数中,this 指向的是 window 对象,而在 Vue 中,this 指向的是组件实例,所以需要使用 _this 来访问和调用组件的属性和方法。

如果不想重新声明个 this 变量来存储,那么可以使用如下方法。

  1. 使用箭头函数:
methods: {
  handleClick() {
    document.addEventListener('click', () => {
      this.someMethod(); // this 仍然指向当前组件
    });
  }
}

箭头函数会继承外围函数的 this 指向,所以回调函数中的 this 也会是组件实例。

在回调函数中直接绑定 this:

methods: {
  handleClick() {
    document.addEventListener('click', function() {
      this.someMethod();  
    }.bind(this));
  }
}

使用 .bind(this) 来手动绑定 this 的指向。

call apply bind 之间的区别

call

fun.call(thisArg[, arg1[, arg2[, ...]]])

apply

fun.apply(thisArg, [argsArray])

所以 apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

    var a ={
        name : "Hit",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.apply(a,[1,2])     // 3

    var a ={
        name : "Hit",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.call(a,1,2)       // 3

bind

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

    var a ={
        name : "Hit",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.bind(a,1,2)()           // 3

小结

this 的指向性问题与它的调用方式相关,在某些场景下为了 this 的正确指向,我们可以使用 箭头函数call,apply, bindnew 关键字等方法去强制绑定 this