彻底理解js中this的指向问题

习文

在平时写js、react等等这些代码的时候,经常会遇到使用this的情况,当自己信心满满的写完代码,点击运行的时候,发现报错 this.xxx is undefined 或者 this.xxx is not a function,但自己的代码里面明明定义了 xxx 的。
为什么会出现这些错误呢,其实就是 this 指向的对象里面没有定义的 xxx
所以搞懂 this 的指向是非常有必要的。

一、执行上下文

在开始说this之前,先简单说一下执行上下文
执行上下文有三个部分:

  • 变量对象(Variable Object,VO)
  • 作用域链
  • this

这里里面的this就是本文要说的 this

执行上下文又分为:

  • 全局执行上下文
  • 函数执行上下文

全局执行上下文是在代码执行时就创建了,函数执行上下文是在函数调用的时候创建的,同一个函数,在不同时刻调用,会创建不同的执行上下文

二、this的指向

由于this存在于执行上下文当中,而执行上下文又是在代码执行和函数调用的时候创建的,所以this的指向也是在代码执行和函数调用时确定的。

可以this使用的不同场景,将this的指向分为三种情况:

  1. 全局中的this
  2. 函数中的this
  3. 构造函数中的this

全局中的this

在浏览器中,全局中的this指向的是 window,node.js中全局的this指向 globel

var a = 1;
console.log(this);  // window
console.log(this.a);  // 1
console.log(window.a);  // 1
console.log(this === window);  // true

函数中的this

根据函数调用方式,又可以分为以下两种情况:

  • 当函数作为对象中的方法被调用时的this
  • 当函数被独立调用时的this

声明式函数中的this
1、对象方法中的this
当函数作为对象中的方法被调用时,函数中this指向的是,函数的拥有者。

var a = 111;
var obj = {
    a: 222,
    func: function() {
        console.log(this);  // obj
        console.log(this.a);  // 222
    }
}
obj.func();

在这里 func函数,作为 obj对象 里面的方法被调用,所以 func 里面的 this 指向的就是 func函数 的拥有者,在这里就是 obj

2、被独立调用时
当函数被独立调用时,函数里面的 this 就指向全局上下文里面的 this,即 window ;在 严格模式 下,this使 undefined

var a = 111;
function func1(){
    var a = 222;
    console.log(this);  // window
    console.log(this.a);  // 111
}
func1();

function func2(){
    "use strict"
    console.log(this);  // undefined
}
func2();

ES6箭头函数中的this
箭头函数中this指向是在函数定义时确定的,这一点和声明式函数定义有所区别。
在箭头函数定义时,函数this指向,定义时所在执行上下文里面的this

var func = () => {
    console.log(this);  // window
}
func();

func 函数是一个箭头函数,所以 func 函数里面的 this 是在其定义是确定的,定义时所在的执行上下文是全局执行上下文,上下文中的 this 指向的是 window,所以 func 函数的 this 指向的也是this。

var a = 111;
var obj = {
    a: 222,
    func: function() {
        var fun2 = () => {
            var a = 333;
            console.log(this.a);
        }
        fun2();
    }
}
obj.func();

在这段代码里,当 func函数 调用时,func 里面的 this 指向的是 obj,在 func函数里面定义了一个箭头函数,所以 func2函数 里面的 this 指向的是 func函数 执行上下文里面的 this,所以最后打印出来的是 obj对象里面的 a(222)

构造函数中的this

构造函数new创建对象的过程:

  1. 创建一个新对象;
  2. 将构造函数的 this 指向这个新对象;
  3. 执行构造函数内部代码;
  4. 返回这个新对象

构造函数中的this指向生成的对象

function Obj(a){
    this.a = a;
    this.func = function(){
        console.log(this);
        console.log(this.a);
    }
}
var obj = new Obj(2);
obj.func();

在这段代码当中,在使用 new 实例化对象之后,调用对象里面的func函数,这里的func函数就是 obj对象 里面的一个方法,所以func函数里面的 this,就指向函数的拥有者,所以这里打印的结果是 2

三、改变this指向的方法

可以使用call,apply,bind来改变函数里面的this的指向

var a = 111;
var obj1 = {
    a: 222,
    func: function() {
        console.log(this); // {a: 333}
        console.log(this.a); // 333
    }
}
        
var obj2 = {
    a: 333,
}
obj1.func.call(obj2,);

这里调用obj1里面的func方法的时候,在后面加了一个call,它会吧func里面的this改变为指向call里面指定的 obj2,所以最后打印的是 333 ,在这里调用call,函数会立即执行。

apply方法和call的作用是一样的,他们之间的区别就是,call在传递其他参数时,是一个一个传的,而apply是传递一个数组,这样当传递的数据个数增多的时候,就不用像call一样,挨个写道call后面的括号里了。
比如:

obj.func.call(obj2,param1,pram2,param3)
obj.func.aplly(obj2,[param1,pram2,param3])

bind也可以改变函数里面的this指向,它和call和apply的区别就是,它返回一个新的函数,并不会和call和apply一样立即执行。

四、其他情况下的this指向

当函数作为DOM事件处理函数

当函数被用作事件处理函数时,它的 this 指向触发事件的元素。

function func(){
    console.log(this);  // element
}
var button = document.getElementsByTagName('button');
button.addEventListener('click', func);

当使用 addEventListener 为DOM元素添加事件时,函数的this会指向该DOM元素。

当作为一个内联事件处理函数

<button onclick="alert(this.tagName.toLowerCase());">
    button
</button>

image.png

<button onclick="alert((function(){console.log(this)})())" >
    触发事件回调
</button>

这段代码会在控制台打印 window

所以这里需要注意的是,当函数作为内联事件时,函数外层的this是指向DOM元素的,但当函数调用时,它是属于被独立调用的函数,所以函数里面的this指向的是window。

React中的this

在React中使用 class 来定义一个组件,在 render 里面的 this 指向的还是实例化之后的对象(组件),但是将组件里面的函数作为 onClick 回调的时候,函数里面的this指向的是它的拥有者。

import React, { PureComponent } from 'react';

class App extends PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            ldate: new Date()
        }
        // this.startTick = this.startTick.bind(this);
        // this.stopTick = this.stopTick.bind(this);
        // this.tick = this.tick.bind(this);
    }

    componentDidMount() {
        // this.TimeID = setInterval(this.tick, 1000);
    }

    componentWillUnmount() {
        clearInterval(this.TimeID);
    }

    startTick(){
        console.log(this);
        this.TimeID = setInterval(this.tick, 1000);
    }

    stopTick() {
        clearInterval(this.TimeID);
    }

    tick() {
        console.log(this);
        this.setState({
            ldate: new Date()
        })
    }

    render() {
        const { ldate: localstate } = this.state;
        return (
            <div className='app'>
                <div>{localstate.toTimeString()}</div>
                <button onClick={this.startTick}>点击开始</button>
                <button onClick={this.stopTick}>点击结束</button>
            </div>
        )
    }
}
export default App;

这段代码,由于render是App组件里面的一个方法,所以render里面的this指向的是App实例化之后的对象(App),在render里面使用将App里面的方法作为点击事件的回调时,onClick={this.startTick} 中的this是指向的App,所以startTick函数可以被拿到;

当点击事件触发时,由于React在内部对 JSX 代码进行了处理,将回调函数的this进行了绑定,所以startTick触发时的this是 undefined (这里是作者的个人推测);

在startTick函数中,将 this.tick函数作为计时函数的回调,由于this是undefined,所以找不到tick函数,会报错。

解决React里面this的指向问题

  • 使用bind在constructor里面将组件中函数的this和组件绑定
  • 在函数定义时,使用箭头函数定义
constructor(props) {
    super(props);
    this.state = {
        ldate: new Date()
    }
    this.startTick = this.startTick.bind(this);
    this.stopTick = this.stopTick.bind(this);
    this.tick = this.tick.bind(this);
}
startTick = () => {
    this.TimeID = setInterval(this.tick, 1000);
}
参考资料
MDN-this
阅读 232
3 声望
6 粉丝
0 条评论
你知道吗?

3 声望
6 粉丝
宣传栏