This指向

89 阅读5分钟

前言

在我自学前端的几个月中,this指向在我曾经刷面试题中,给我看的晕头转向,所以我觉得有必要重新梳理一下在JS中this指向的知识点。

This指向

在ES5中,This指向始终坚持一个原则:This永远指向最后调用他的那个对象
接下来我们来看一个简单的例子:

    var name = 'zzz'
    function a() {
        var name = 'hhh';
        console.log(this.name);
        console.log('inner'+this);
    }
    a()
    console.log('outer'+this);

image.png 根据This指向的原则This永远指向最后调用他的那个对象,我们看函数a,最后调用它的地方为a(),在它前面没有调用对象,在一般情况下,前面没有调用对象的时候,它的调用对象就是全局对象window,这就相当于window.a(),函数a()的this的指向为window,这也是为啥this.name输出的值为zzz
注意:这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是undefined,那上述代码中的this.name就会报错。

接着我们再看一个例子:

    var name = 'zzz';
    var a = {
        name : 'hhh',
        fn : function () {
            console.log(this.name);
        }

    }
    a.fn()

image.png 在上述代码中,函数fn就是对象a调用的,函数fnthis指向为a,所以输出的namehhh

在这个代码的基础上,我们做一点小小的改动:

    var name = 'zzz';
    var a = {
        name : 'hhh',
        fn : function () {
            console.log(this.name);
        }

    }
    window.a.fn()

image.png 在这里,我们使用window.a.fn(),一开始使用了window来调用fn最后又使用a来调用,但因为This指向始终坚持一个原则:Thie始终指向最后调用它的那个对象,所以这里输出的结果为hhh

接下来我们再来看一个例子:

    var name = 'zzz';
    var a = {
        fn : function () {
            console.log(this.name);
        }

    }
    window.a.fn()

image.png 这里由于函数fn的指向还是a,但由于对象a中缺少对属性name的定义,所以最终输出的结果为undefined,在对象a外面还有一个name的变量,但由于两个处于不同的作用域,就算在对象a这个作用域中没有this.name,它也不会去上一个对象的作用域中去寻找name这个属性。

接着我们再来看一个比较离谱的例子:

    var name = 'zzz';
    var a = {
        name : 'hhh',
        fn : function () {
            console.log(this.name);
        }

    }
    var b = a.fn;
    b()

image.png 看到这里,我们可能会产生一些疑问,为啥输出不是hhh,而是zzz,我们先来看下谁调用了fn,在代码中,我们一开始只是将对象a中的函数fn赋值给变量b对象a并没有调用这个方法,接下来变量b执行了方法,也就是说变量b执行了函数fn,此时变量bthis指向为window,所以输出为zzz

通过上面的例子,我们可以看出This的执行不是在创建的时候确定的,而是在它执行的时候才确认的,在ES5中,This的指向永远指向最后调用它的对象

如何改变This指向

改变This指向

  1. 使用ES6的箭头函数
  2. 在函数内部使用_this = this
  3. 使用apply,call,bind
  4. new实例化一个对象

箭头函数

在介绍箭头函数之前,我们先来看一段代码:

    let name = 'zzz';
    let a = {
        name : 'hhh',

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

        fn2 : function () {
            setTimeout(function () {
                this.fn1()
            }, 1000);
        }
    }

    a.fn2()

image.png 上述代码,使用了setTimeout函数,这个函数在不改变他的This指向之前,它的This指向永远指向window,正因为这个原因,我们在调用这个函数时,会出现错误,在开发过程中,我们会经常使用延时函数,此时我们就需要改变setTimeout函数的This指向来使用它。
在ES6中,提出了箭头函数箭头函数的This始终指向函数定义时的This,而非执行时,箭头函数没有This绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数包含时,则This绑定的是最近一层非箭头函数的this,否则This就为undefined。

    let name = 'zzz';
    let a = {
        name : 'hhh',

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

        fn2 : function () {
            setTimeout( ()=> {
                this.fn1()
            }, 1000);
        }
    }

    a.fn2()

image.png 上述代码中,在函数fn2中,setTimeout函数中使用了箭头函数,箭头函数的this在它定义时就被确定了,它继承了它外层执行环境中的fn2的执行环境的this

在函数内部使用 _this = this

这个方法是我看别人使用才懂的,如果不使用ES6,这种方式是最简单的。这个方法主要是:将调用这个函数的对象保存在变量_this中,然后在函数中都使用这个_this,这样_this就不会改变。

    let name = 'zzz';
    let a = {
        name : 'hhh',
        fn1 : function fn1() {
            console.log(this.name);
        },
        fn2 : function fn2() {
            let _this = this
            setTimeout( function () {
                console.log(_this.name);
            },1000)
        } 
    }
    a.fn2()

image.pngfn2中,我们先设置_this = this,在调用时,fn2的this指向为a,在setTimeout中本来的this指向应该为window,但我们调用的是_this,此时_this的指向为a

使用apply,call,bind

call

call方法是使用一个指定的this和单独给出一个或多个函数来调用一个函数。

语法:

function.call(thisArg, arg1, arg2, ...)

调用call函数必须是一个函数,call函数接收两个参数,第一个参数接收一个参数名,可以使调用这个函数的this指向变为接收的函数。第二个参数为一个参数列表,表示的是调用这个函数所需要的参数。

    let person = {
        fullName : function () {
            return this.firstName + '' + this.lastName
        }
    }
    let person1 = {
        firstName : 'w',
        lastName : 'x'
    }
    let person2 = {
        firstName : 'a',
        lastName : 'g'
    }
    console.log(person.fullName.call(person1));

image.png

apply

apply函数的使用与call大致相同,区别是两个函数接收的第二个参数,call接收的是一个参数列表,而apply接收的是一个数组。

语法:

apply(thisArg, argsArray)

例子:

    let person = {
        fullName(city) {
            return this.firstName + ' ' + this.lastName + ' ' + city
        }
    }

    let person1 = {
        firstName : 'a',
        lastName : 'g'
    }
    console.log(person.fullName.apply(person1,['xm']));
    

image.png

bind

bind的使用与call的用法差不多,但bind返回的是一个函数。

语法:

function.bind(thisArg[, arg1[, arg2[, ...]]])

例子:

    let hello = function (a,b,c,d) {
        console.log(this.name);
        console.log(a,b,c,d);
    }

    let demo = {
        name: 'demo'
    }

    let h = hello.bind(demo,1,2)
    h()

image.png