前言

最近准备把5月份面试的时候被问到的问题整理一下,本来是想写在面试总结篇里面的,但是有些知识点还是值得我单独写一篇来记录的。于是便有了 XXX面试官:小勾同学来说说XXX 系列文章。这个系列文章涉及的内容含有本人经历的面试笔试题,也有网上看的面经,觉得可以深挖的问题,便记录了下来。

正文

  1. 回想起之前的面试和看过的面经,几乎都有 说说这道题的输出 这种类型的题,这种类型的题,进行细分的话,无非就3种:1、this指向 2、event loop 3、原型原型链。这篇文章主要写的是this指向的问题。对于this指向的问题的话,再进行细分无非也就下面这几种情况:

    image-20200710153727328

    在正式介绍之前,你需要知道 this 的5种绑定方式:

    • 默认绑定(非严格模式下this指向全局对象,严格模式下this会绑定在 undefined
    • 隐式绑定(当函数引用有上下文对象时,如 obj.foo() 的调用方式,foo 内的this指向 obj
    • 显示绑定(通过 call() 或者 apply() 方法直接指定 this 的绑定对象,如 foo.call(obj)
    • new 绑定
    • 将头函数绑定(this 的指向由外层作用域决定的)
  2. 默认绑定

    在非严格模式下 this 指向的是全局对象 window ,而在严格模式下会绑定到 undefined

    非严格模式下:

    var a = 10
    function foo(){
        console.log(this.a) //10
    }
    foo()

    严格模式下:

    "use strict";
    var a = 10;
    function foo () {
      console.log('this1', this) // undefined
      console.log(window.a) //10
      console.log(this.a) // Uncaught TypeError: Cannot read property 'a' of undefined
    }
    console.log(window.foo) //function foo
    console.log('this2', this) // this2 window
    foo();

    通过 letconst 定义的变量不会绑定在window上。

    let a = 10
    const b = 20
    
    function foo () {
      console.log(this.a) //undefined
      console.log(this.b) //undefined
    }
    foo();
    console.log(window.a) //undefined
  3. 隐式绑定

    隐式绑定其实很简单,总结出来就一句话:this永远指向最后调用它的那个对象(不考虑箭头函数)

    例如:

    function foo () {
      console.log(this.a) // 1
    }
    var obj = { a: 1, foo }
    var a = 2
    obj.foo()

    上面这段代码,虽然foo 是定义在 window 下的,但是在 obj 对象种引用了它,并将它重新赋值到 obj.foo 上,就相当于是下面这样:

    var obj = {
      a: 1,
      foo: function () {
        console.log(this.a)
      }
    }
    var a = 2
    obj.foo()
  4. 隐式绑定的隐式丢失问题

    隐式丢失其实就是被隐式绑定的函数在特定的情况下会丢失绑定对象。

    由两种情况容易发生隐式丢失问题:

    • 使用另一个变量来给函数取别名
    • 将函数作为参数传递时会被隐式赋值,回调丢失this绑定
    function foo () {
      console.log(this.a)
    };
    var obj = { a: 1, foo };
    var a = 2;
    var foo2 = obj.foo;
    
    obj.foo(); // 1
    foo2(); //2

    把函数当成参数传递时,也会被隐式赋值

    function foo () {
      console.log(this.a) // 2
    }
    function doFoo (fn) {
      console.log(this) // window
      fn()
    }
    var obj = { a: 1, foo }
    var a = 2
    doFoo(obj.foo)
  5. 显示绑定

    功能如其名,就是强行使用某些方法,改变函数内的 this 指向。

    通过 call()apply()bind() 方法直接指定 this 的绑定对象,如 foo.call(obj)

    这里还有一个知识点:就是这三种绑定方式的区别,详情可见我写的前端面试总结篇

    最简单的使用方法:

    function foo () {
      console.log(this.a)
    }
    var obj = { a: 1 }
    var a = 2
    
    foo() // 2
    foo.call(obj) //1
    foo.apply(obj) //1
    foo.bind(obj)

    值得一提的是:如果这些方法接收的第一个参数是空或者 nullundefined 的话,会忽略这个参数。

    稍微复杂一点的题目:

    var obj1 = {
      a: 1
    }
    var obj2 = {
      a: 2,
      foo1: function () {
        console.log(this.a)
      },
      foo2: function () {
        setTimeout(function () {
          console.log(this)
          console.log(this.a)
        }, 0)
      }
    }
    var a = 3
    
    obj2.foo1() // 2
    obj2.foo2() // window 3

    面对上面这种情况,我们就可以使用显示绑定的方法来操作

    var obj1 = {
      a: 1
    }
    var obj2 = {
      a: 2,
      foo1: function () {
        console.log(this.a)
      },
      foo2: function () {
        setTimeout(function () {
          console.log(this)
          console.log(this.a)
        }.call(obj1), 0)
      }
    }
    var a = 3
    
    obj2.foo1() // 2
    obj2.foo2() // {a:1} 1

    看上面的代码,是将 call 运用到setTimeout 里面的回调函数桑,并不是运用到obj2.foo2()

    注意⚠️:如果是这种写法的话,我改变的就是foo2函数内的this的指向了,但是我们知道,foo2函数内this的指向和setTimeout里函数的this是没有关系的,因为调用定时器的始终是window

    上面这种情况,也可以换一种写法:

    var obj1 = {
      a: 1
    }
    var obj2 = {
      a: 2,
      foo1: function () {
        console.log(this.a)
      },
      foo2: function () {
        function inner(){
            console.log(this.a)
        }
          inner()
      }
    }
    var a = 3
    
    obj2.foo1() // 2
    obj2.foo2() // window 
  6. new 绑定

    使用new来调用Person,构造了一个新对象person1并把它(person1)绑定到Person调用中的this

    function Person (name) {
      this.name = name
    }
    var name = 'window'
    var person1 = new Person('kweku')
    console.log(person1.name) //kweku
  7. 箭头函数绑定

    箭头函数绑定有一个诀窍:它里面的this是由外层作用域决定,且指向函数定义时的this而非执行时的。

    var obj = {
      name: 'obj',
      foo1: () => {
        console.log(this.name) // window
      },
      foo2: function () {
        console.log(this.name) //obj
        return () => {
          console.log(this.name) //obj
        }
      }
    }
    var name = 'window'
    obj.foo1()
    obj.foo2()()
    

总结

其实在实际面试笔试中,类似说输出的题目大多数情况都是结合promise和事件轮询进行综合的考察,所以我在后面的文章中再进行扩展。

最后更新: 2020年09月21日 15:47

原始链接: http://www.kweku.top/2020/07/10/08.this指向/