携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情
this初探
首先,我们得知道一个蛋疼的事实,js的this是所有语言里面比较变态的知识点,其它语言如c,java的this都是十分简单的,是直接动态的绑定到类上。对于js的this,我们首先牢记几个点
1. 普通函数的this是动态绑定的,谁调用this指向谁
栗子1
function fn() {
console.log(this)
}
fn()//window
相比初学js的小盆友们都被这个答案震惊过,这不是是函数fn里面的?为啥this却指向了window。嗯仔细看口诀1,this在普通函数上是动态绑定的,谁调用这个函数,this就是谁的。fn是window调用的,所以是window
栗子2
function fn1() {
console.log(this)
}
let obj = {
fn1:fn1
}
obj.fn1()//obj
看了栗子1,你就很容易清楚此处为什么是obj了。因为fn1是obj调用的,所以this是obj对象的
栗子3
function fn1() {
console.log(this)
}
let obj = {
fn1:fn1
}
let fn2 = obj.fn1
fn2()//window
此处可能大家会骂娘,说能不能说说this的隐式和默认调用?嗯,说实话,我感觉那个不好描述,大家也看的头疼,就直接说口诀吧。此处用口诀,直接看fn2()的执行,它是window调用的,所以是window
栗子4
function func(){
console.log(this.a)
}
const obj ={
a:1,
fn:function(){
console.log(this)
func()
}
}
var a = 2
obj.fn()//obj 2
这个题目,大家第一个this基本都可以回答正确,obj调用的fn所以this肯定是obj。但是第二个大家就容易失误,注意func()是谁调用的?不是obj调用的哦,它前面没有obj.func。因此它只能是window的,this.a就输出2
2.this的丢失问题
经过上面的练习大家基本可以解决相当部分的this指向,但是有一部分需要单独说,这部分靠口诀一是无法辨别的,因为this在传递的过程中发生了改变。
2.1 定时器setTimeout系列
定时器里面的this十分容易出错,本质原因是我们忘了定时器它其实也是一个函数.看下面的栗子
栗子5
const obj = {
a:1,
fn1:function(){
console.log(this.a)
},
fn2:function(){
setTimeout(function(){
console.log(this.a)
}, 1000);
}
}
var a = 2
obj.fn1()//1
obj.fn2()//2
首先第一个很简单,obj.fn1的调用直接是通过obj。因此this的指向就是obj,第一个输出就是1 第二个obj.fn2(),此时fn2里面的this确实是obj,但是定时器里面是一个函数,这个函数是直接调用的,不是obj调用的,因此this是window
栗子6
定时器的简写是一个坑,它的this永远是window
const obj = {
fn:function(){
console.log(this)
}
}
setTimeout(obj.fn,1000)//window
大家看到这个可能很容易说this输出的是obj。此时就错了,上了面试官的当了。这个是定时器的简写它本质还原如下
//简写
setTimeout(obj.fn,1000)
//还原后,其实是将obj.fn传递给了参数fn。所以fn它是单独调用,fn函数内部永远指向window
setTimeout((fn) => {
fn()
}, timeout);
2.2 函数作为参数
function func() {
console.log(this.a)
}
var a = 1
const obj = {
a:2,
func:function(fn){
console.log(this.a)
fn()
}
}
obj.func(func)//2,1
首先执行obj的func函数,因此this.a输出的是obj的2。但是fn()的执行是不属于obj对象,因此fn的this是window,此处输出1
2.3 函数作为返回值
var a = 1
const obj = {
a:2,
func:function(){
console.log(this.a)
return function(){
console.log(this.a)
}
}
}
obj.func()()//2,1
这个和上面的分析基于一致,首先是obj.func()的执行,所以this.a的就是obj的a输出了2。 但是后面返回了一个函数,这个函数不是obj调用的,是属于window的,因此输出了1
3.多层this时:找最近的
const obj = {
a:1,
info:{
a:2,
func:function(){
console.log(this.a)//2
}
}
}
obj.info.func()
看这个栗子,对象出现了两层嵌套,this的输出就找最近的调用,就是info对象,因此输出2
4.new的this问题
new是比较特殊的函数,其内部的this指向的是函数本身。看下面的例子,无论是普通函数还是箭头函数,其this都是函数本身。(自己按上面的分析很好解决)
function Peo () {
this.age = 1
this.info = function() {
console.log(this.age)
}
this.other = () => {
console.log(this.age)
}
}
let p = new Peo()
p.info()
p.other()
this.age = 2
5.箭头函数
箭头函数的this和普通函数的this是不同的,普通函数的this是动态的,根据调用情况的不同,this的展现就不同。但是箭头函数的this可以理解成伪静态的(不是完全静态,还取决于它的上一层正常函数被谁调用了,箭头函数的this就是它)
箭头函数的this永远是当前它所处作用域的上一层
如果感觉这句话不好理解,就记住箭头函数的this永远看它的上一个正常函数的this指向,它指向了谁,箭头函数的this就是谁。
var name = 'tom'
const obj = {
name: 'zc',
intro:function () {
return () => {
console.log('My name is ' + this.name)
}
},
intro2:function () {
return function() {
console.log('My name is ' + this.name)
}
}
}
obj.intro2()()//my name is tom
obj.intro()()//my name is zc
首先第一个obj.intro2它返回一个函数,这个函数的调用是window,不是obj。第一个输出就是tom。 第二个是一个箭头函数,它的this要看上面第一个正常函数的this指向,也就是obj.intro因此输出的是zc
6.强绑定this(call,bind,apply)
普通函数和new
强绑定可以改变普通函数和new函数的this指向。 下面的例子大家自己分析,不会的可以评论区分享。
例子1
function Peo () {
this.age = 1
this.info = function() {
console.log(this.age)
}
this.other = () => {
console.log(this.age)
}
}
const obj = {
age:3
}
let p = new Peo()
p.info()//1
p.other()//1
p.info.call(obj)//3
p.other.call(obj)//1
例子2
var name = 'window'
var obj1 = {
name: 'obj1',
intro: function () {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
var obj2 = {
name: 'obj2'
}
obj1.intro.call(obj2)()//obj2,obj2
箭头函数
此处只需记住强绑定对箭头函数是无效的
总结
- 普通函数的this是动态的,谁调用这个函数,这个函数的内部this就指向了谁
- 当函数作为参数,函数作为返回值,定时器。要留意this丢失
- 箭头函数的this是取决于它的上一个正常函数的this指向
- 使用call,bind,apply无法改变箭头函数的this
- 只要细心,灵活运用上面的三条规则,基本this都可以解决了