问题
第一题
请问控制台会打印出来什么?
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 种方式,分别是:
- 对象调用方式
- 函数调用方式
- apply 调用方式
- 构造函数调用方式
对象调用方式
对象调用方式又称为方法调用方式
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
觉得看文章还不够过瘾,我创建了一个交流群,欢迎大家扫码关注公众号进行获取。