this指向与call,apply,bind
this问题对于每个前端同学来说相信都不陌生,在平时开发中也经常能碰到,有时候因为this还踩过不少坑,并且this问题在面试题中出现的概率也非常高,我们一起来了解一下
this
的指向与call
,apply
,bind
this的指向
ES5中的this
在ES5中,this一般指向函数调用时所在的执行环境,与函数定义的位置无关。也可以理解成this永远指向最后调用它的对象
- 在普通函数中的this总是指向它的直接调用者,默认情况下指向全局对象(浏览器为window)
- 在严格模式下,没有直接调用者的函数中的
this
为undefined
- Call, apply,bind函数,this指向的是绑定的对象
- 对象函数调用,this指向调用它的那个对象
- 构造函数中的this,指向该构造函数new出来的实例对象
1 | var obj = { |
ES6中的this
在ES6中新增了一种箭头函数,箭头函数的this始终指向它定义时的this,而非执行时
- 箭头函数没有自己的this,它的this是继承来的,默认指向它定义时所在的对象,即
箭头函数中的this指向外层代码的this
- 不可以当作构造函数,也就是说,不可以用
new
命令调用,否则会抛出一个错误 - 箭头函数内没有
arguments
对象,可以用rest
参数代替 - 不可以使用
yield
命令,因此箭头函数不能用作generator
函数 - 箭头函数没有自己的
this
,所以不能用call
,apply
,bind
这些方法改变this指向
1 | var obj = { |
输出结果依次为obj对象,obj对象,window,window,window
解析:
1.第一个obj.hi()很好理解,hi为普通函数,this指向调用它的那个对象,即obj
2.第二个执行hi(),它其实是上一个执行后返回的函数,并且是箭头函数,箭头函数本身没有this,我们往他的上一级去查找,我们刚刚得出上一级的this为obj,所以这里的this也指向obj
3.第三个执行obj.sayHi(),这里没有打印this,而是返回了一个普通函数
4.第四个执行sayHi(),其实执行的是刚刚返回的那个普通函数,这里的this则指向调用它的那个对象,没有则指向window
5.第五个执行sa yHiBack(),指向的是刚刚第四次执行返回的箭头函数,OK,箭头函数我们往上一层找,也是window
5.第六个执行obj.say(),这里这个say()是一个箭头函数,当前代码块obj不存在this,只能往上一层查找,指向window
call, apply,bind的区别
我们都知道call,apply,bind
都可以用来改变this
指向,但这三个函数稍稍有些不同。
- call与apply唯一的区别就是它们的传参方式不同,call从第二个参数开始都是传给函数的,apply只有两个参数,第二个参数是一个数组,传给函数的参数都写在这个数组里面
- call与apply改变了函数的this指向后会立即执行,而bind是改变函数的this指向并返回这个这个函数,不会立即执行
- call与apply的返回值是函数的执行结果,bind的返回值是改变了this指向的函数的拷贝
call
call()
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数。(来自MDN)
语法: fun.call(thisArg,arg1[,arg2,arg3...])
解释: call方法用来为一个函数指定this对象,第一个参数是你想要指定的那个对象,后面都是传给该函数的参数,之间用逗号隔开
1 | var person = { |
apply
apply()
方法调用一个具有给定this
值的函数,以及以一个数组(或类数组对象)的形式提供的参数。(来自MDN)
语法: fun.apply(thisArg,[arg1,arg2,arg3...])
解释: apply方法与call方法基本类似,不同的是,两者的参数形式,apply方法传递的是一个由若干个参数组成的数组。
1 | var person = { |
#####bind
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
语法: fun.bind(thisArg,arg1[,arg2,arg3...])()
解释: bind方法用来为方法指定this对象并返回一个新的函数,它的参数与call函数一样。它本身是不会调用的,需要自己手动调用。
1 | var person = { |
注意这里需要自己再调用一次,因为bind只会返回这个改变了this指向的函数,并不会自己执行
call,apply该用哪个?
- 参数数量,顺序确定就用call,参数数量,顺序不确定就用apply
- 参数数量少用call,参数数量多用apply
- 参数集合已经是一个数组的情况,最好用apply
bind的应用场景
1.保存参数
我们先来看一道经典面试题
1 | for(var i=1;i<6;i++){ |
相信大家都知道这里会打印出五个6,因为在执行settimeout回调函数时,i已经变成了6
那么如何让它打印出1,2,3,4,5呢?
当然方法有很多,比如闭包、将var改成let使它形成块级作用域,这里先不讲,后面单独讲闭包会提出来
我们也可以用bind来解决
1 | for(var i=1;i<6;i++){ |
2.回调函数this丢失问题
1 | var student = { |
想一想这里settimeout的回调如果不用bind绑定this,结果会怎样?
结果是报错,因为不给settimeout回调函数绑定this的话,那它的this应该指向的是全局window,全局没有subject,调用join会报错
模拟call
思路:
- 根据call的规则设置上下文对象,也就是
this
的指向。 - 通过设置
context
的属性,将函数的this指向到context上 - 通过隐式绑定执行函数并传递参数。
- 删除临时属性,返回函数执行结果
1 | Function.prototype.myCall = function(context){ |
模拟apply
思路:
- 与call类似,主要区别是参数的处理
1 | /* |
模拟bind
1 | Function.prototype.myBind = function(context){ |