this指向问题汇总

普通函数:

1、this总是代表着他的直接调用者,例如obj.fun(),那么fun()中的this就是obj;

2、调用的时候,没有任何前缀,则指向window,new的时候,指向new出来的对象;

3、在默认情况下(非严格模式),没找到直接调动者,则this指向window;

4、在严格模式下,没有直接调用者的函数中的this是undefined;

5、使用call、apply、bind绑定的,this指向绑定的对象。

6、在 Node 中,指向 Global

箭头函数:

1、this对象,就是定义时所在的对象,而不是使用时所在的对象;!!!

2、不可以当做构造函数,即不能使用new;

3、不可以使用arguments对象,该对象在函数体内不存在,如果要用可以rest参数替代;

4、不可以使用yield命令,因此箭头函数不能使用Generator函数;

5、返回对象时必须在对象外面加上括号。

上面四点,第一个尤为注意。普通函数this指向可变,但是箭头函数是固定的。


特殊情境下的 this 指向

在三种特殊情境下,this 会 100% 指向 window

  • 立即执行函数(IIFE)
  • setTimeout 中传入的函数
  • setInterval 中传入的函数

结合一下例子理解一下:

例子:

先看几个例子理解一下,看一个字面量对象中的this

var num = 1
var obj = {
    num: 2,
    fun1() { return this.num*2 },
    fun2: ()=> { return this.num*5 }
}
obj.fun1() //4,this指向fun1的调用者obj
obj.fun2() // 5,fun2定义的环境是window

obj.fun2() // 5,fun2定义的环境是window
上图,看结果
在这里插入图片描述
在看一个

var a = 1
var obj = {
  a: 2,
  func2: () => {
    console.log(this.a)
  },
  
  func3: function() {
    console.log(this.a)
  }
}

// func1
var func1  = () => {
  console.log(this.a)
}

// func2
var func2 = obj.func2
// func3
var func3 = obj.func3

func1()
func2()
func3()
obj.func2()
obj.func3()

一口气看完吧

var a = 1,
obj = {
	a: 2,
	b1:function(){
		console.log(this.a);
    },
    b2:()=>{
        console.log(this.a);
    },
	c1:function(){
		return function(){
			console.log(this.a);
		}
    },
    c2:()=>{
        return function () {
            console.log(this.a)
        }
    },
    c3:()=>{
        return ()=>{
            console.log(this.a)
        }
    }
}

obj.b1(); // 2;
obj.b2(); // 1;
obj.b1.call(window); // 1;
obj.c1()(); // 1;
obj.c2()(); // 1
obj.c3()(); // 1



var name = 'win',
obj = {
    name: 'o',
    fun1:()=>{
        console.log(this.name)
    },
    fun2:function () {
        console.log(this.name)
    }
}

obj.fun1();// win
obj.fun2();// o



var name = 'win';
function Obj() {
    this.name = 'o';
    this.fun1 = function () {
        console.log(this.name)
    }
    this.fun2 = () => {
        console.log(this.name)
    }
}

var obj = new Obj();
obj.name; // o;
obj.fun1(); // o;
obj.fun2(); // o;

var _fun1 = obj.fun1;
_fun1(); // win
var _fun2 = obj.fun2;
_fun2(); // o

解析一下:

了解的同学就直接过滤,往下翻
区分 “声明位置” 与 “调用位置”
js 是词法作用域模型,无论我是一个对象也好,一个方法也好,它的生命周期只和我们声明它的位置有关。我把它写在哪个位置,它就活在哪个位置。

我们来看这样一个例子:

// 声明位置
var me = {
  name: 'xiaohong',
  hello: function() {
    console.log(`你好,我是${this.name}`)
  }
}

var you = {
  name: 'xiaoming',
  hello: me.hello
}

// 调用位置
me.hello() // xiaohong
you.hello() // xiaoming

各位看到 hello 在代码中分别被 me 和 you 调用了,因此两次调用的 this 也就分别指向了 me 和 you,这没毛病。

我们稍微把这个例子改一下:

// 声明位置
var me = {
  name: 'xiaohong',
  hello: function() {
    console.log(`你好,我是${this.name}`)
  }
}

var name = 'zhangsan'
var hello = me.hello 

// 调用位置
me.hello() // 你好,我是xiaohong
hello() // 你好, 我是zhangsan

这里我们直接调用 hello 的时候,输出了全局的 name 变量。我们可以理解为是因为 name 和 hello 都挂在在全局对象 window 上,所以 hello () 其实等价于 window.hello (),此时 hello 方法内部的 this 自然指向 window,于是 this.name 就等价于 window.name。这也没毛病。

我们再改一下:

// 声明位置
var me = {
  name: 'xiaohong',
  hello: function() {
    console.log(`你好,我是${this.name}`)
  }
}

var you = {
  name: 'xiaoming',
  hello: function() {
    var targetFunc = me.hello
    targetFunc()
  }
}

var name = 'zhangsan'

// 调用位置
you.hello()

上面这段代码,大家先给自己 1 分钟的时间,在脑子里面跑一下。

OK,现在我默认你心里已经有了一个自己的答案了(还没有跑完的同学不要急着往下看哈,自觉暂停一下,先有一个自己的结论再来看我们的解析,收获会更大)。

调用位置输出的结果是 zhangsan—— 竟然不是 xiaoming?的确,我们打眼看过去,直觉上肯定会认为是 you 这个对象在调用 hello 方法、进而调用 targetFunc,所以此时 this 肯定指向 you 对象啊!为啥会输出一个 window 上的 name 呢?

我们再复习一下我们开头那句话 ——“this 指向调用它所在方法的那个对象”。

回头看我们例题中的 targetFunc 这个方法,大家之所以第一直觉会认为它的 this 应该指向 you 这个对象,其实还是因为把 “声明位置” 和 “调用位置” 混淆了。我们看到虽然 targetFunc 是在 you 对象的 hello 方法里声明的,但是在调用它的时候,我们是不是没有给 targetFunc 指明任何一个对象作为它前缀? 所以 you 对象的 this 并不会神奇地自动传入 targetFunc 里,js 引擎仍然会认为 targetFunc 是一个挂载在 window 上的方法,进而把 this 指向 window 对象。

在面试命题过程中,this 指向问题如果想往难了出, 就会像楼上这样把声明位置和调用位置故意揉在一起,考验你对两者的区分能力。 但只要各位能记住,“不管方法被书写在哪个位置,它的 this 只会跟着它的调用方走” 这个核心原则,就一定不会出错。

特殊情况下的this:

立即执行函数
所谓立即执行函数,就是定义后立刻调用的匿名函数(参见下面这道例题里 hello 方法的函数体里这种写法)。

var name = 'BigBear'

var me = {
  name: 'xiaohong',
  // 声明位置
  sayHello: function() {
    console.log(`你好,我是${this.name}`)
  },
  hello: function() {
    (function(cb) {
      // 调用位置
      cb()
    })(this.sayHello)
  }
}

me.hello() // 大家再猜下输出啥了?

经过我们楼上的提点,相信这里大家可以想都不想就说出 me.hello 的执行结果,没错,就是 BigBear , 是 window.name 的值。

但其实,即便不考虑立即执行的匿名函数这种所谓的“特殊情况”,大家按照我们上面的指向原则来分析,结果也是一样一样的。 立即执行函数作为一个匿名函数,在被调用的时候,我们往往就是直接调用,而不会(也无法)通过属性访问器( 即 xx.xxx) 这样的形式来给它指定一个所在对象,所以它的 this 是非常确定的,就是默认的全局对象 window。

setTimeout 和 setInterval 中传入的函数
考虑到 setTimeout 和 setInterval 中函数的 this 指向机制其实是一样的,咱们这里拿 setTimeout 来开刀就够了:

var name = 'BigBear'

var me = {
  name: 'xiaohong',
  hello: function() {
    setTimeout(function() {
      console.log(`你好,我是${this.name}`)
    })
  }
}

me.hello() // 你好,我是BigBear

是不是觉得好神奇?我们的 this.name 明明看起来是在 me.hello () 里被调用的,结果却输出了 window.name。 setTimeout 到底对函数做了什么?

其实,我们所看到的延时效果(setTimeout)和定时效果(setInterval),都是在全局作用域下实现的。无论是 setTimeout 还是 setInterval 里传入的函数,都会首先被交付到全局对象手上。因此,函数中 this 的值,会被自动指向 window。

那么如何改变this指向呢

看下一篇吧《call、apply 和 bind 能做什么?如何使用?之间的区别?模拟一个?

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值