6道面试题搞定this问题

268 阅读3分钟

题目一:全局环境下的 this

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

这种情况相对简单直接,函数在浏览器全局环境中被简单调用,非严格模式下 this 指向 window;在 use strict 指明严格模式的情况下就是 undefined

题目二:上下文对象调用中的 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() {
        var fn = o1.fn
        return fn()
    }
}

console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn())

最终将会返回:o1o1undefined

我们一一分析:

  • 第一个 console 最简单,o1 没有问题。难点在第二个和第三个上面,难点在第二个和第三个上面,关键还是看调用 this 的那个对象。
  • 第二个 consoleo2.fn(),最终还是调用 o1.fn(),因此还是返回 o1
  • 最后一个,在执行 var fn = o1.fn 赋值之后,变量提升,因此这里的 this 指向 window,答案当然是 undefined

题目三:bind/call/apply 改变 this 指向

const foo = {
    name: '蛙哇',
    logName: function() {
        console.log(this.name)
    }
}
const bar = {
    name: '蛙哇1'
}
console.log(foo.logName.call(bar))

将会输出 蛙哇1,这不难理解。

三者区别一句话总结:他们都是用来改变相关函数 this 指向的,但是 call/apply 是直接进行相关函数调用;bind 不会执行相关函数,而是返回一个新的函数,这个新的函数已经自动绑定了新的 this 指向,开发者需要手动调用即可。

// call 用法
const target = {}
fn.call(target, 'arg1', 'arg2')

// apply 用法
fn.apply(target, ['arg1', 'arg2'])

// bind 用法
fn.bind(target, 'arg1', 'arg2')()

题目四:构造函数和 this

function Foo() {
    this.bar = "lucas"
}
const instance = new Foo()
console.log(instance.bar)

答案将会输出 蛙哇。但是这样的场景往往伴随着下一个问题:new 操作符调用构造函数,具体做了什么?

  • 创建一个新的对象;
  • 将构造函数的 this 指向这个新对象;
  • 为这个对象添加属性、方法等;
  • 最终返回新对象。

需要注意的是,如果在构造函数中出现了显式 return 的情况,那么需要注意分为两种场景,如果构造函数中显式返回一个值,且返回的是一个对象,那么 this 就指向这个返回的对象;如果返回的不是一个对象,那么 this 仍然指向实例:

function Foo() {
    this.bar = "蛙哇"
    const o = {}
    return o
}
const instance = new Foo()
console.log(instance.bar)

将会输出 undefined,此时 instance 是返回的空对象 o

function Foo() {
    this.bar = "蛙哇"
    return 1
}
const instance = new Foo()
console.log(instance.bar)

将会输出 蛙哇,也就是说此时 instance 是返回的目标对象实例 this

题目五:箭头函数中的 this 指向

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

// {fn: ƒ}

单纯箭头函数中的 this 非常简单,嵌套函数中的 this 不会继承外层函数的 this 值,而箭头函数没有自己的执行上下文,所以箭头函数的 this 就是它外层函数的 this

题目6:this 优先级

function foo (a) {
    console.log(this.a)
}

const obj1 = {
    a: 1,
    foo: foo
}

const obj2 = {
    a: 2,
    foo: foo
}

obj1.foo.call(obj2)
obj2.foo.call(obj1)

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

需要知道的是:我们常常把通过 callapplybindnewthis 绑定的情况称为显式绑定;根据调用关系确定的 this 指向称为隐式绑定。

function foo (a) {
    this.a = a
}

const obj1 = {}

var bar = foo.bind(obj1)
var baz = new bar(3)
console.log(baz.a)

将会输出 3。我们看 bar 函数本身是通过 bind 方法构造的函数,其内部已经对将 this 绑定为 obj1,它再作为构造函数,通过 new 调用时,返回的实例已经与 obj1 解绑。 也就是说:

new 绑定修改了 bind 绑定中的 this,因此 new 绑定的优先级比显式 bind 绑定更高。