高频——如何准确判断this指向的是什么?

807 阅读7分钟

this对象是在运行时基于函数的执行环境绑定的,也就是说,this 是和执行上下文绑定的。如果函数没有调用,那么this指向就没有确定下来。

什么是this?

作为前端面试几乎可以说必考的this,你真的了解吗?

this对象是在运行时基于函数的执行环境绑定的,也就是说,this 是和执行上下文绑定的。如果函数没有调用,那么this指向就没有确定下来。

说白了,this就是一个指针,它总会指向,最后调用函数的那个对象

为什么要用this?

正所谓知其然知其所以然,我一直都觉得学习一个新的知识点一定要知道它为什么为人们所使用,才能更好的理解它并使用它。

其实this机制不算是很简单,但是它可以让我们的代码更加的优雅,使得代码可读性变得更高,并且提升代码的可复用性

举个例子,我们看下面这段代码:

var myName = {
        name: 'nankou',
        age: 21,
        sayName: function() {
            return myName.name
        }
 }

看上去没有什么问题,sayName里面的函数返回的就是myName对象的name属性,但是如果我们需要改动对象名称的时候呢,就要把函数体里面的myName也跟着修改。如果我们用this机制,可以改写这段代码:

var myName = {
    name: 'nankou',
    age: 21,
    sayName: function() {
        return this.name
    }
}

那么我们修改对象名的时候就不必关心里面的函数返回值,因为调用函数时这里的this指向就是外层的对象。

没有this机制的情况下,我们可以明确的把这个对象传给函数,但是当模式越来越复杂,逻辑越来越多的时候,就会显得很混乱,使用this会使代码变得更加优雅,使得代码可复用并易于阅读。

如何使用this?

执行上下文

上面我们提过this是和执行上下文绑定的,那我们就先从执行上下文的角度看this,

1、全局执行上下文中的this

在全局执行上下文中,this是指向全局对象的,在浏览器中就是window对象。

所以当我们想知道全局执行上下文的this指向时,只需要在控制台console.log(this)就可以打印出来。

2、函数执行上下文中的this

函数执行上下文是重头戏,传说中的this的几大绑定规则几乎都是基于此。我们可以从这几大绑定规则中理解this的机制。

this的五种绑定规则

以下总结常见的this五种绑定规则,让你从绑定规则深刻理解this的指向~~

一、默认绑定方式

this默认绑定指向window对象,直接上代码来看:

// 1、默认绑定规则:this默认指向window
function foo() {
        console.log(this === window)
    }
foo()  //打印出true

从这里可以看出,如果是一个全局函数,默认是绑定指向window对象的。

二、隐式绑定规则:谁调用就指向谁

函数被作为某个对象的方法被调用时,this指向那个对象。

    // 2、隐式绑定规则:谁调用就指向谁
    var obj = {
        foo: function() {
            console.log(this) // this指向obj 
        }
    }
    obj.foo() // 对象的方法

此处还涉及到一个隐式丢失的概念。

什么是隐式丢失呢,顾名思义,隐式绑定规则无效了,也就是隐式绑定的函数丢失绑定对象,从而默认绑定到全局或者udnefinded.

我们分为四种情况来看隐式丢失。

1、第一种情况:为函数调用创建别名, 即创建变量放置结果

    function foo() {
        console.log(this.a)
    }
    var obj = {
        a: 2,
        foo: foo
    }
 
    var bar = obj.foo;  // 创建别名
    var a = 1;
    bar() // 打印出1,即this指向window

2、第二种情况:传入回调函数,函数作为参数传递,也就是常说的隐式赋值

    function foo() {
        console.log(this.a)
    }
 
    function doFoo(fn) { // fn作为参数传递
        fn();   
    }
    var obj = {
        a: 2,
        foo: foo
    }
    var a = 1
    doFoo(obj.foo) // 打印出1,即this指向window

3、第三种情况:出现在内置函数中,比如setTimeout这些

  function foo() {
        console.log(this.a)
    }
    var obj = {
        a: 2,
        foo: foo
    }
    var a = 1
    setTimeout(obj.foo, 100) // 打印出1,即this指向window

4、第四种情况:函数的赋值

    function foo() {
        console.log(this.a)
    }
    var obj = {
        a: 2,
        foo: foo
    }
    var obj2 = {
        a: 3
    }
    var a = 1;
    (obj2.foo = obj.foo)(); //打印出1,即this指向window

三、显式绑定规则:call、apply、bind

显式绑定规则,为什么叫显式呢,我自己理解的话认为是因为通过这三个函数传入的参数就是绑定的对象,所以看起来比较明显hhhhh

    // 3、显式绑定规则
    var obj = {
        a: 2,
        foo() {
            console.log(this.a)
        }
    }
 
    var obj2 = {
        a: 3
    }
 
    obj.foo.call(obj2) // 打印出3,this指向obj2  obj.foo.call(obj2,1,2)  传参方式:列出参数
    obj.foo.apply(obj2) // 打印出3,this指向obj2  obj.foo.apply(obj2,[1,2]) 传参方式:传数组
    var fn = obj.foo.bind(obj2)
    fn() // 打印出3,this指向obj2

这里涉及到手写call、apply、bind的方法,我认为手写这几个方法可以让自己对this指向理解的更加深刻,而不是直接调用那么傻瓜式!!

四、new绑定

    // 4、new绑定规则
    function foo() {
        console.log(this)
    }
    foo() // 默认指向window
    new foo() // 指向foo这个new出来的对象

五、箭头函数

     // 4、箭头函数
    var name = "1"
    const obj = {
        name: 2,
        foo: () => {
            console.log(this) // 指向window,指向外部函数,也就是window
            console.log(this.name) // 打印出1
        }
    }
    obj.foo()

这五种规则其实都不难理解,认真的动手实践一遍就能理解啦。另外,这里的代码都是分开规则讲的,有没有想过,如果一段代码里面用到了多种规则,那如何判定呢?

其实规则是有优先级的:箭头函数 > new > 显式 > 隐式 > 默认绑定

听别人说千遍万遍都不如自己动手一遍,具体的还需要自己进行实践并理解。

题目分享与解析

接下来我们看看面试的经典题目来检测一下学习成果。 (如果大家有遇到比较好玩的题目也可以分享出来吖)

1、如何准确判断this指向的是什么?

当我们知道了this的几大绑定规则以及这些规则的优先级,其实就很好回答了。只要从优先级较高的开始判断。

(1)函数是否是箭头函数:如果是的话,this继承的就是外层代码块的this。

(2)函数是否在new中调用:如果是的话,this绑定的就是新创建的对象。

(3)函数是否通过call、apply、bind调用:如果是的话,this绑定的就是传入的对象。(注意,如果传入的为null或者undefined或者未传值,使用的是默认绑定规则,即指向window)

(4)函数是否在某个上下文对象中调用:如果是的话,this绑定的就是那个上下文对象。

(5)如果以上都不是,那么使用默认绑定 :非严格模式下this指向window,严格模式下绑定为undefined。

2、输出打印的值

window.val = 1;
    var obj = {
        val: 2,
        dbl: function() {
            this.val *= 2;
            val *= 2;
            console.log('val:', val);
            console.log('this.val:', this.val);
        }
    

    obj.dbl(); 
    var func = obj.dbl;
    func(); 

结果我就不贴了,不影响大家思考,只要运行一下控制台就能看到答案。 这段代码比较简单,需要注意的点是,记得全局的对象值改变了之后要代入计算。

3、输出打印的值

    var number = 5;
    var obj = {
        number: 3,
        fn: (function() {
            var number;
            this.number *= 2;
            number = number * 2;
            number = 3;
            return function() {
                var num = this.number;
                this.number *= 2;
                console.log(num); 
                number *= 3;
                console.log(number); 
            }
        })()
    }
    var myFun = obj.fn;
    myFun.call(null); 
    obj.fn(); 
    console.log(window.number); 

4、输出打印的值

    var length = 10;

    function fn() {
        console.log(this.length);
    }

    var obj = {
        length: 5,
        method: function(fn) {
            fn();
            arguments[0](); 
        }
    };

    obj.method(fn, 1, 2, 4);

这次我从经典的是什么为什么怎么用三个方面来梳理this,希望对你能有一点点帮助~