做题学知识(5)之 this

322 阅读5分钟

问题

第一题

请问控制台会打印出来什么?

window.a = 2
function fn () {
    console.log(this.a)
}

var obj = {
    a: 3,
    fn: fn,
    b: {
        a: 4,
        fn: fn
    }
}

var b = obj.b.fn

fn()
obj.fn()
obj.b.fn()
b()
new fn()
fn.call(obj)

第二题

请问控制台会打印出来什么?

window.a = 2
function fn() {
    setTimeout(function () {
        console.log(this.a)
    }, 0)
}
var obj = {
    a: 3,
    fn: fn
}
fn()
obj.fn()
fn.call(obj)
new fn()

第三题

请问控制台会打印出来什么?

window.a = 2
function fn() {
    setTimeout(() => {
        console.log(this.a)
    }, 0)
}
var obj = {
    a: 3,
    fn: fn
}
fn()
obj.fn()
fn.call(obj)
new fn()

第四题

请问控制台会打印出来什么?

window.a = 1
let obj = {
    a: 2,
    fn: () => {
        console.log(this.a)
    }
}
obj.fn()

答案

简介 this

this 只能在函数内部使用, this 的值在定义的时候是不能确定的,只有在调用这个函数的时候才能确定。而函数的调用一共有 4 种方式,分别是:

  1. 对象调用方式
  2. 函数调用方式
  3. apply 调用方式
  4. 构造函数调用方式

对象调用方式

对象调用方式又称为方法调用方式

function fn () {}
var obj = {
    fn1: fn,
    fn2: function () {},
    fn3 () {}
}

上面的这三种写法都是对象里面定义函数的方式,通过 obj 调用的时候函数中的 this 都指向 obj。但是有一种写法需要注意:

function fn () {}
var obj = {
    fn1: fn,
}
var a = obj.fn1
a()

这种方法不是对象调用方式,而是函数调用方式。

函数调用方式

function fn () {}
fn()
let a = function () {}
a()

这种都是函数调用方式,这种写法的 this 指向全局变量。在浏览器环境下就是指向 window。这种写法需要注意:

window.a = 1
function fn () {
    function fn1 () {
        console.log(this.a)
    }
    fn1()
}
let obj = { a: 2 }
fn.call(obj) // 打印出来 this.a = 1

函数里面嵌套了函数是一种混淆操作,里面的 fn1 还是函数调用方式,因此 this 指向 window

apply 调用方式

apply, call, bind 这三种统称为 apply 调用方式。

function fn() {}
fn.apply(window)
fn.call(window)
fn.bind(window)

this 指向他们的第一个参数

构造函数调用方式

如果一个函数是通过 new 关键字调用的,他就是构造函数调用方式

function fn () {}
new fn()

构造函数的 this 指向构造函数本身

补充箭头函数

箭头函数是 es6 新加入的一种缩写函数的语法糖,箭头函数使用的时候需要注意:

  • 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。

这里可以结合普通函数做个对比:

  • 普通函数体内的 this 是使用时所在的对象
  • 箭头函数体内的 this 是定义时所在的对象

所以普通函数根据调用方式的不同 this 指向的位置就不同,箭头函数不管调用方式如何,他都指向外部普通函数的 this,如果没有外部函数在浏览器环境下指向 window

答案公布

第一题

window.a = 2
function fn () {
    console.log(this.a)
}

var obj = {
    a: 3,
    fn: fn,
    b: {
        a: 4,
        fn: fn
    }
}

var b = obj.b.fn

fn()
obj.fn()
obj.b.fn()
b()
new fn()
fn.call(obj)

这题主要考察了普通函数是使用哪种方式调用的:

  • fn() 是使用函数调用的方式,因此 this 指向 window,打印 2
  • obj.fn() 是使用对象调用的方式,因此指向调用的对象 obj,打印 3
  • obj.b.fn() 是使用对象调用的方式,因此指向调用的对象 obj.b,打印 4
  • b() 是使用函数调用的方式,因此指向 window,打印 2
  • new fn() 是构造函数调用的方式,因此指向构造函数,构造函数没有定义 a 的值,因此打印 undefined
  • fn.call(obj) 是 apply 调用的方式,因此 this 指向方法的第一个参数 obj,打印 3

综上所述答案为: 2, 3, 4, 2, undefined , 3

第二题

请问控制台会打印出来什么?

window.a = 2
function fn() {
    setTimeout(function () {
        console.log(this.a)
    }, 0)
}
var obj = {
    a: 3,
    fn: fn
}
fn()
obj.fn()
fn.call(obj)
new fn()

这题跟第一题大同小异,问题是这个函数作为了 setTimeout 的参数,需要简单知道 setTimeout 使用这个参数的方式,这里他是用函数式调用的,因此答案是 2, 2, 2, 2

自己主动调用的函数很容易看出来是哪种方式,但是如果使用函数作为参数的这种 API 就不清楚 API 的处理方式,因此答案就不容易得出来。所以这种就需要自己多总结了,当然要想避免这种问题也可以采用箭头函数

第三题

请问控制台会打印出来什么?

window.a = 2
function fn() {
    setTimeout(() => {
        console.log(this.a)
    }, 0)
}
var obj = {
    a: 3,
    fn: fn
}
fn()
obj.fn()
fn.call(obj)
new fn()

这个题长得跟第二题几乎一个样,只是 setTimeout 里面的函数不是普通函数而是箭头函数。因此是定义时就确定的,或者这个题的 fn 可以写成下面这个样子:

function fn () {
    let _this = this
    setTimeout(function () {
        _this.a
    })
}

这样就可以看出来 _this 的值就是 this 的值。而 this 的值又是取决于函数如何调用的。因此答案是: 2, 3, 3, undefined

第四题

请问控制台会打印出来什么?

window.a = 1
let obj = {
    a: 2,
    fn: () => {
        console.log(this.a)
    }
}
obj.fn()

这个题箭头函数没有被某个普通函数包裹,这种情况下在浏览器环境箭头函数式的 this 是执行 window 的。因此答案是 1

总结

this 只能在函数内部使用,而声明函数的方式有俩种:普通函数和箭头函数。

  • 普通函数可以根据是哪种调用方式来看 this 是指向谁。需要注意的是某些接受函数作为参数的 API 不知道内部调用方式需要自己去总结。
  • 箭头函数的 this 总是指向嵌套他的普通函数的 this,如果没有函数嵌套在浏览器环境下 this 指向 window

觉得看文章还不够过瘾,我创建了一个交流群,欢迎大家扫码关注公众号进行获取。