js中的this指向问题

171 阅读5分钟

this到底指向谁

首先先记住以下特点,带你一起了解this的指向问题:

  • 在函数体中,非显式或隐式地简单调用函数时,严格模式下,函数内的this 会被绑定到undefined上,在非严格模式下则会被绑定到全局对象window上。
  • 一般使用new方法调用构造函数时,构造函数内的this会被绑定到新创建的对象上。
  • 一般通过call/apply/bind方法显式调用函数时,函数体内的 this会被绑定到指定参数的对象上。
  • 一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
  • 在箭头函数中,this 的指向是由外层(函数或全局)作用域来决定的。

全局环境中的this指向

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

        function f2() {
            'use strict'
            console.log(this);
        }
        f1() // window
        f2() // undefined

这到基础的题,可以理解我们的第一个this的特点,那么 我们再来看下面这两个:

        const foo = {
            bar: 10,
            fn: function () {
                console.log(this);
                console.log(this.bar);
            }
        }
        let fn1 = foo.fn
        fn1() // Window 和 undefined

其实这里也很好理解,函数在调用时才能确定它的this指向,fn赋值给了fn1,所以fn1调用时相当于window环境下全局执行,所以this指向还是window。 那么把上边的代码换成下边:

        const foo = {
            bar: 10,
            fn: function () {
                console.log(this);
                console.log(this.bar);
            }
        }
        foo.fn()


        输出结果:
        {bar: 10, fn: ƒ}
        10

这时,this指向最后调用它的对象,如果函数中的this,是被上一级所调用的,那么this就是指向它的上一级调用者。

上下文调用中的this

参考上边的代码,我们知道下边的代码输出为true

        const student = {
            name: 'Lucas',
            fn: function () {
                return this
            }
        }
        console.log(student.fn()===student); // true

当存在更复杂的关系时,this会指向它最后的调用者,最后的调用者为brother,因此输出为brother。

        const person = {
            name: 'person',
            brother:{
                name: 'brother',
                fn: function () {
                return this.name
            }
            }
        }
        console.log(person.brother.fn()); // brother

至此,this上下文对象的调用介绍的比较清楚了,我们再来看一个高阶题目:

        const o1 = {
            text:'o1',
            fn: function(){
                return this.text
            }
        }
        const o2 = {
            text:'o2',
            fn: function(){
                return o1.fn()
            }
        }
        const o3 = {
            text:'o3',
            fn: function(){
                let fn = o1.fn
                return fn()
            }
        }
        console.log(o1.fn()); // o1
        console.log(o2.fn()); // o1
        console.log(o3.fn()); // undefined
  • 第一个输出不难理解,o1调用的函数fn所以返回的是o1;
  • 第二个o2.fn函数最终调用的还是o1.fn,所以运行的结果仍然是o1;
  • 第三个 let fn = o1.fn 进行了赋值操作,它的返回值为fn()相当于全局调用,所以o3为undefined

通过bind、call、apply改变this指向

bind、call、apply都是用来改变this指向的,但是call和apply是直接进行函数调用的,bind不会立即执行相关函数,其次就是参数设定上的不同,简单的来说就是以下代码是等价的。

        // call
        const target = {}
        fn.call(target,'arg1','arg2')
        // apply
        const target = {}
        fn.apply(target,['arg1','arg2'])
        // bind
        const target = {}
        fn.bind(target,'arg1','arg2')()

下面我们来看一道例题

        const foo = {
            name: 'Lucas',
            logName:function(){
                console.log(this.name);
            }
        }
        const bar = {
            name: 'mike'
        }
        foo.logName.call(bar) // mike

以上代码执行的结果为mike,通过call改变了this的指向,如果有不明白bind、call、apply的可以参考这篇文章 从原生的角度理解call、apply、bind的实现

构造函数中的this

        function Foo() {
            this.bar = 'Lucas'
        }
        const instance = new Foo()
        console.log(instance.bar); // Lucas

执行以上代码输出为Lucas,但是这样的场景往往伴随着另一个问题,new操作符在调用构造函数时具体做了什么?简单的来说可以分为以下四个步骤:

  • 创建一个新的对象;
  • 将构造函数的this指向这个新对象;
  • 为这个对象添加新的属性和方法等;
  • 最终返回新的对象
       // 场景一
       function Foo() {
            this.user = 'Lucas'
            const o = {}
            return o
        }
        const instance = new Foo()
        console.log(instance.user) // undefined
        
         // 场景二
        function Foo() {
            this.user = 'Lucas'
            return 1
        }
        const instance = new Foo()
        console.log(instance.user) // Lucas
        
         // 场景三
        function Foo() {
            this.user = 'Lucas'
            return this
        }
        const instance = new Foo()
        console.log(instance.user) // Lucas

以上三种情况可以看出,如果构造函数中显示的返回一个值,且返回的还是一个对象(复杂数据类型),那么this指向这个返回的对象;如果返回的是一个不是一个对象(基本类型),那么this仍然指向实例。

箭头函数中的this

在箭头函数中,this指向都是有外层作用域来决定的,指向它最近一级的作用域。

        const foo = {
            fn:function(){
                setTimeout(function(){
                    console.log(this);
                })
            }
        }
        foo.fn() // Window

上面代码,setTimeout在匿名函数中,所以this指向是Window;如果我们像改变这个this指向,可以巧妙的运用箭头函数。

        const foo = {
            fn:function(){
                setTimeout(()=>{
                    console.log(this);
                })
            }
        }
        foo.fn() //{fn: ƒ}

this的优先级

我们常常把通过call、apply bind、new对this进行绑定的情况称为显式绑定,而把根据调用关系确定this指向的情况称为隐式绑定。那么易式绑定和隐式绑定谁的优先缓更高呢?

        function foo(a) {
            console.log(this.a);
        }
        const obj1 = {
            a: '1',
            foo:foo
        }
        const obj2 = {
            a: '2',
            foo:foo
        }
        obj1.foo.call(obj2) // 2
        obj2.foo.call(obj1) // 1

输出分别为2、1,也就是说,call、apply的显式绑定一般来说优先级更高。

        function foo(a){
            this.a = a
        }
        const obj1 = {}
        let bar = foo.bind(obj1)
        bar(2)
        console.log(obj1.a); // 2

上述代码通过 bind 将 bar 函数中的this 绑定为obj1对象。执行bar(2)后,obj1.a值为2,即执行 bar(2)后,obj1 对象为{a: 2}。 当再使用bar作为构造函数时,例如执行以下代码,则会输出3。

        let bar2 = new foo(3)
        console.log(bar2.a); // 3

bar 函数本身是通过 bind 方法构造的函数,其内部已经将 this 绑定为 obj1,当它再次作为构造函数通过 new 被调用时,返回的实例就已经与 obj1 解绑了。也就是说,new 绑定修改了 bind 绑定中的 this 指向,因此new 绑定的优先级比显式 bind 绑定的更高。

再来看以下示例:

        function foo() {
            return a=>{
                console.log(this.a);
            }
        }
        const obj1 = {
            a: 1
        }
        const obj2 = {
            a: 2
        }
        const bar = foo.call(obj1)
        console.log(bar.call(obj2));

以上代码输出为1。由于foo中的this绑定到了obj1上,所以bar中的this也会绑定到obj1上,箭头函数中的this无法修改。

        var a  =123
        const foo = ()=>(a)=>{
            console.log(this.a);
        }
        const obj1 ={
            a:1
        }
        const obj2 ={
            a:2
        }
        let bar = foo.call(obj1) // 123
        console.log(bar.call(obj2));

上边代码输出的就是123。

        const a  =123
        const foo = ()=>(a)=>{
            console.log(this.a);
        }
        const obj1 ={
            a:1
        }
        const obj2 ={
            a:2
        }
        let bar = foo.call(obj1) // undefined
        console.log(bar.call(obj2));

把var换成了const输出就变成了undefined,因为const声明的变量不会挂着到window全局对象上,因此this指向window上时也就找不到哦啊变量a了。

以上就是this指向的问题,有不明白的欢迎提问。