面试常见-this指向问题

185 阅读3分钟

前言

this指向也是面试当中必不可少的问题了,相信有些同学还是和我一样在面试中会被问倒。借此机会回顾一下this指向的相关知识,攻克这个难关,以后面试不会再被这个问题困扰。

this指向

首先我们要知道this绑定的五种方式

  • new绑定
  • 箭头函数绑定(本身没有,同上级作用域)
  • 默认绑定
  • 显示绑定(通过callapplybind方法直接指定this)
  • 隐示绑定(当函数引用有上下文对象时, 如 obj.foo()的调用方式, foo内的this指向obj

1. new绑定

通过new创建的构造函数this会指向构造函数本身,如果使用call之类更改了this指向,相应的实例this同样会更改

function Person (name, id) {
  this.name = name
  this.id = id
}
Person.prototype.foo = function () {
  console.log(this.name, this.id);
  return function () {
    console.log(this);
  }
}
window.name = 'xiaowang'

let p = new Person('xiaoli', 2)
let p2 = new Person('xiaozhao', 3)
p.foo()()
p.foo.call(p2)()
p.foo().call(p2)

答案

image.png 2. 箭头函数绑定

箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。箭头函数作用域在创建的时候就已经决定了,不会改变。

也就是说箭头函数中的this是由外层作用域来决定的,同样的一个嵌套结构的箭头函数它的this实际是最外层的那个this,并不会因为层级深入改变。

Person.prototype.arrFoo = () => {
  console.log(this.name, this.id);
  return function () {
    console.log(this);
  }
}
p.arrFoo('arrow', 5)()

image.png

const obj1 = {
  name: 'obj1',
  foo: function () {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}

const obj2 = {
  name: 'obj2',
  foo: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}

const obj3 = {
  name: 'obj3',
  foo: () => {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}

const obj4 = {
  name: 'obj4',
  foo: () => {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}

obj1.foo()()
obj2.foo()()
obj3.foo()()
obj4.foo()()

答案 image.png obj1.foo()()输出了obj1和xiaowang 是因为obj1.foo()返回了一个匿名函数,它的执行环境是在全局下。obj2.foo()()则是因为return的箭头函数在创建时它的作用域就是obj2

3. 默认绑定

在非严格模式下this指向的是全局对象window,而在严格模式下会绑定到undefined,class中默认开启了严格模式

var a = 1
function foo () {
  var a = 2
  function inner () {
    console.log(this.a)
  }
  inner()
}

foo()

注意: 这里的答案是1并不是2,因为inner在函数内部调用,所以他的this指向实际还是window

4.显示绑定

通过call()、apply()或者bind()方法直接指定this的绑定对象, 如foo.call(obj)。使用.call()或者.apply()的函数是会直接执行的;bind需要手动调用;call接收若干个参数,apply是一个数组。

var obj = {
  a: 'obj',
  foo: function () {
    console.log('foo:', this.a)
    return function () {
      console.log('inner:', this.a)
    }
  }
}
var a = 'window'
var obj2 = { a: 'obj2' }

obj.foo()()
obj.foo.call(obj2)()
obj.foo().call(obj2)

答案

image.png

5. 隐式绑定

function foo () {
  console.log(this.a)
};
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo };
var a = 2;
var obj2 = { a: 3, doFoo }
var foo2 = obj.foo;

obj.foo();
foo2();
obj2.doFoo(obj.foo)

答案

image.png 可能有同学问foo2()为什么是2,这就涉及到隐式丢失的问题。因为foo2虽然等于obj.foo,但是最后调用实际是window,所以答案是2而不是1。doFoo中为什么也输出2呢?

因为把一个函数当成参数传递到另一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数的this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。

结语

写的不好,有错误的地方还请各位大佬多多指教,感恩家人🙏