this指向问题call、apply、bind超详细

his问题,是很多前端同学初学JS的拦路虎,甚至一些两三年工作经验的同学对this的理解还是模棱两可或是有一些误解,一个常见的误区就是:this指向函数本身。本文尝试总结了一下this的使用,并在最后总结了this指向的确定规则。

其实this很简单,你早该熟悉它了!

一、普通函数:谁调用指向谁


这是确定this指向最普遍的规则,普通函数是指由构造函数Function创建或者使用关键字function声明定义的函数。通常情况下,普通函数的this并不天生指向其定义所在的对象,而是指向它的调用者。

而如果没有指定调用者,严格模式下this会是undefined,非严格模式下则是指向全局对象(Node环境中是global,浏览器环境中则是window)。

且看示例,obj2和obj共享了func函数:

let obj ={
     a:'obj',
    func() {
        console.log(this.a)
    }
}
let obj2 = {
     a:'obj2',
     func: obj.func
}

obj.func()// this指向调用者obj,输出:obj1
obj2.func()// this指向调用者obj2,输出:obj2let func = obj.func
window.a ='window'func()// 未指定调用者,this指向全局对象,输出:window

示例中,this指向了给定的调用者或者全局对象window。如果是严格模式,最后的代码会报错,因为this指向了undefined:

"use strict"
// 省略函数定义
let func = obj.func
window.a ='window'
func()// 报错:Uncaught TypeError: Cannot read property 'a' of undefined

二、箭头函数中的this


箭头函数的this指向,和箭头函数定义所在上下文的this相同。对于普通函数,this在函数调用时才确定;而对于箭头函数,this在箭头函数定义时就已经确定了,并且不能再被修改。

且看示例:

let obj = {
    A() {
        return () =>{ 
            return this
        }
    },
    B() {
        return function(){
            return this
        }
    }
}
let func = obj.A()
console.log(func() === obj)// true

func = obj.B()
console.log(func() === obj)// false
console.log(func() === window)// true

例子中,obj的A/B函数都是返回一个内部定义的函数,A、B内部定义的函数函数返回值都是this,只是在A中,内部函数是箭头函数,而B中则是普通函数。将A、B函数创建的内部函数分别赋值给变量func进行调用,未指定调用者,可以发现箭头函数的返回值this能够指向obj,而普通函数的this则因为未指定调用者而指向了全局对象。

总而言之一句话,箭头函数this在定义时就已确定,指向此时外层函数的this,如果没有外层函数则指向全局对象。严格模式下箭头函数没有外层函数的情况this的指向会不会不同呢?并不会,依然指向全局对象而不是undefined,这点需要稍微注意:

"use strict"
let func = () => {
    return this
}
let func2 = function(){ 
    return this
}
console.log(func()=== window)// true
console.log(func2()=== window)// false
console.log(func2()===undefined)// true

三、构造函数中的this


JS里的普通函数可以使用new操作符来创建一个对象,此时该函数就是一个构造函数,箭头函数不能作为构造函数。执行new操作符,其实JS内部完成了以下事情:

  1. 创建一个空的简单JavaScript对象(即{});

  1. 将构造函数的prototype绑定为新对象的原型对象 ;

  1. 将步骤1新创建的对象作为this的上下文并执行函数 ;

  1. 如果该函数没有返回对象,则返回this。

"use strict"
function A(){
    this.a =1 
    this.func = () => {
        return this
    }
}
let obj = newA()
console.log(obj.a)// 1
console.log(obj.func()=== obj)// true

四、使用apply和call动态改变函数this的指向


使用apply可以在调用函数时改变this的指向,当然,箭头的this已经不可改变,所以不能通过该方法修改this指向。apply是函数原型上的方法,所以通过function创建的函数都有这个属性,函数的apply属性也是一个函数,它接收两个参数,第一个参数为指定的this对象,第二个参数为函数的参数列表:

func.apply(thisArg, [argsArray])
functiondir(x, y){
    this.x = x
    this.y = y
}
let obj ={
  a:1,
  b:2
}
dir.apply(obj,[3,4])

console.log(JSON.stringify(obj))// {"a":1,"b":2,"x":3,"y":4}

可以看到,通过apply函数指定了函数dir调用的this对象为obj,对this对象的修改就是对obj的修改。call函数和apply的作用都是更改this的指向,只是使用上稍微不同,apply方法中,第二个参数为一个数组,原函数所有的参数都放入该数组中,而call函数接收多个参数,第二个及之后的所有参数都被作为参数来调用原函数,对于上例,改为call方法调用如下:

dir.call(obj,3,4)
console.log(JSON.stringify(obj))// {"a":1,"b":2,"x":3,"y":4}

call和apply的第一个参数用于指定this对象。注意非严格模式下,如果该参数为null或undefined,则会指向全局对象,严格模式下则this依然指向给定的null或undefined:

let obj ={
    test() {
        return this === window
    }
}
console.log(obj.test())// false
console.log(obj.test.call(undefined))// 非严格模式输出:true,严格模式输出:false

对箭头函数使用call或者apply将不能指定this,因为箭头函数的this在定义时已经确定:

letdir = (x, y) => {
    this.x = x
     this.y = y
}
let obj ={
     a:1,
     b:2
}
dir.apply(obj,[3,4])

console.log(JSON.stringify(obj)) // {"a":1,"b":2,"x":3,"y":4}
console.log(window.x, window.y) // 3, 4

dir该为箭头函数后,定义时this指向window,通过apply调用时,对this的操作还是在window上生效。

五、使用bind创建新函数并绑定this


bind() 方法会创建一个新函数,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。bind方法有以下特点:

  • 新生成一个函数,包装了原函数,这意味着原函数的功能不被影响,但是调用新函数会调用原函数

  • 对箭头函数无效

  • 新生成的函数绑定了固定的this,再次bind生成的函数不会重新绑定this,很类似箭头函数,不过二者存在很大不同。

  • 非严格模式指定this为undefined,结果会指向全局对象

  • bind函数的第二个以及之后的参数,会作为原函数的参数并占用参数位置,绑定函数接收的参数会拼接在后面一并传给原函数。

lettest = function (b, c) {
  console.log(this.a, b, c)
}
let obj = { 
    a: 'obj1-a'
}
let obj2 = { 
    a: 'obj2-a'
}
let bindFunc = test.bind(obj,'b')
bindFunc('c')// obj1-a, b, c
bindFunc.call(obj2,'c') // 指定this无效,输出:obj1-a, b, c
test.call(obj2,'b','c') // 原函数不被影响,输出:obj2-a, b, c
let bindFunc2 = bindFunc.bind(obj2,'c-rebind')
bindFunc2()// 不能再次重新绑定,输出:obj1-a, b, c-rebind
let bindFunc3 =test.bind(undefined,'b')
window.a ='window-a'
bindFunc3('c')// 非严格模式输出window-a, b, c

以上是bind函数的常规用法,bind函数生成的函数与箭头函数不同,它仍然可以用于new操作符,不过这种情况下,bind的this对象会被忽略,this还是指向新生成的对象:

function A(b) {
    this.a ='a'
    this.b ='b'
 }
let obj ={
     a:'obj-a'
}
let B = A.bind(obj)
let newObj =newB()
console.log(newObj.a, obj.a)// a, obj-a

六、总结


以上就是this指向的大部分内容,在这里对this指向的规则做一个简单总结:

  1. 绝大多数情况下,非箭头函数内部的this由调用者确定,即谁调用指向谁

  1. 箭头函数的this取决于函数定义所在的上下文中this,即函数定义外部this是什么,箭头函数内部的this就是什么,相当于固化了当前执行环境中的this,注意不是函数定义所在的对象!

  1. 构造函数中的this指向新创建的对象

  1. apply/call方式调用的函数,其this可以在调用时指定

  1. bind函数可以将根据一个函数生成一个新的函数,并且该函数的this绑定到指定的对象上

其实最基本的规则就是第1条,其它的规则则是补充一些特殊情况下this的指向和使用方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李宏伟~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值