• 前端人70%以上 不了解的this指向


    前言

    这周工作偶然发现了一个问题,我们同事因为this指向的问题卡住的bugvue2的this指向问题,使用了箭头函数,导致拿不到对应的props。当我给他介绍的时候他竟然不知道,随后也刻意的看了一下前端交流群,至今最起码还有70%以上的前端程序员搞不明白,今天给大家分享一下this指向,如果啥都没学会,请给我一个大嘴巴子。1. 调用位置


    • 作用域跟在哪里定义有关,与在哪里执行无关
    • this指向跟在哪里定义无关,跟如何调用,通过什么样的形式调用有关
    • this(这个) 这个函数如何被调用(方便记忆)
    • 为了方便理解,默认情况下不开启严格模式

    2. 绑定规则

    上面我们介绍了,this的指向主要跟通过什么样的形式调用有关。接下来我就给大家介绍一下调用规则,没有规矩不成方圆,大家把这几种调用规则牢记于心就行了,没有什么难的地方。

    • 你必须找到调用位置,然后判断是下面四种的哪一种绑定规则
    • 其次你要也要晓得,这四种绑定规则的优先顺序
    • 这两点你都知道了 知道this的指向对于你来说 易如反掌

    2.1 默认绑定

    函数最常用的调用方式,调用函数的类型:独立函数调用

    function bar() {console.log(this) // window
    } 
    
    • 1
    • 2
    • bar是不带任何修饰符的直接调用 所以为默认绑定 为window
    • 在严格模式下 这里的thisundefined

    2.2 隐式绑定

    用最通俗的话表示就是:对象拥有某个方法,通过这个对象访问方法且直接调用(注:箭头函数特殊,下面会讲解)

     const info = {fullName: 'ice',getName: function() {console.log(this.fullName)}
    }
    
    info.getName() // 'ice' 
    
    • 1
    • 2
    • 3
    • 4
    • 这个函数被info发起调用,进行了隐式绑定,所以当前的thisinfo,通过this.fullName毫无疑问的就访问值为ice

    隐式丢失 普通

    有些情况下会进行隐式丢失,被隐式绑定的函数会丢失绑定对象,也就是说它为变为默认绑定,默认绑定的this值,为window还是undefined取决于您当前所处的环境,是否为严格模式

     const info = {fullName: 'ice',getName: function() {console.log(this.fullName)}
    }
    
    const fn = info.getName
    
    fn() //undefined 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这种情况下就进行了隐式丢失,丢失了绑定的对象,为什么会产生这样的问题呢?如果熟悉内存的小伙伴,就会很容易理解。

    • 这里并没有直接调用,而是通过info找到了对应getName的内存地址,赋值给变量fn
    • 然后通过fn 直接进行了调用
    • 其实这里的本质 就是独立函数调用 也就是为window,从window中取出fullName属性,必定为undefined

    隐式丢失 进阶这里大家首先要理解什么是回调函数。其实可以这样理解,就是我现在不调用它,把他通过参数的形式传入到其他地方,在别的地方调用它。

     //申明变量关键字必须为var
    var fullName = 'panpan'
    
    const info = {fullName: 'ice',getName: function() {console.log(this.fullName)}
    }
    
    function bar(fn) {//fn = info.getNamefn() // panpan
    }
    
    bar(info.getName) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 首先bar中的fn为一个回调函数
    • fn = info.getName 参数传递就是一种隐式赋值,其实跟上面的隐式丢失是一个意思,他们都是指向的fn = info.getName引用,也就是它们的内存地址
    • 因为他们的this丢失,也就是函数独立调用,默认绑定规则,this为全局的window对象
    • 注意: 为什么申明必须为var呢?* 因为只有var申明的变量才会加入到全局window对象上* 如果采用let\const 则不是,具体的后续介绍一下这两个申明变量的关键字
    • 但是有些场景,我不想让隐式丢失怎么办,下面就来给大家介绍一下显示绑定,也就是固定调用。

    2.3 显示绑定

    但是在某些场景下,this的改变都是意想不到的,实际上我们无法控制回调函数的执行方式,因此没有办法控制调用位置已得到期望的绑定即this指向。

    接下来的显示绑定就可以用来解决这一隐式丢失问题。

    2.1 call/apply/bind

    js中的 ”所有“函数都有一些有用的特性,这个跟它的原型链有关系,后续我会在原型介绍,通过原型链js中变相实现继承的方法,其中call/apply/bind这三个方法就是函数原型链上的方法,可以在函数中调用它们。

    2.2 call
    • call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。* 第一个参数为固定绑定的this对象* 第二个参数以及二以后的参数,都是作为参数进行传递给所调用的函数
    • 备注* 该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组
    var fullName = 'panpan'
    
    const info = {fullName: 'ice',getName: function(age, height) {console.log(this.fullName, age, height)}
    }
    
    function bar(fn) {fn.call(info, 20, 1.88) //ice 20 1.88
    }
    
    bar(info.getName) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    2.3 apply
    • call的方法类似,只是参数列表有所不同* 参数* call 参数为单个传递* apply 参数为数组传递
    var fullName = 'panpan'
    
    const info = {fullName: 'ice',getName: function(age, height) {console.log(this.fullName, age, height)}
    }
    
    function bar(fn) {fn.apply(info, [20, 1.88]) //ice 20 1.88
    }
    
    bar(info.getName) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    2.4 bind
    • bindapply/call之间有所不同,bind传入this,则是返回一个this绑定后的函数,调用返回后的函数,就可以拿到期望的this。
    • 参数传递则是* 调用bind时,可以传入参数* 调用bind返回的参数也可以进行传参
    var fullName = 'panpan'
    
    const info = {fullName: 'ice',getName: function(age, height) {console.log(this.fullName, age, height) //ice 20 1.88}
    }
    
    function bar(fn) {let newFn = fn.bind(info, 20)newFn(1.88)
    }
    
    bar(info.getName) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.4 new绑定

    谈到new关键字,就不得不谈构造函数,也就是JS中的 “类”,后续原型篇章在跟大家继续探讨这个new关键字,首先要明白以下几点,new Fn()的时候发生了什么,有利于我们理解this的指向。

    1.创建了一个空对象
    2.将this指向所创建出来的对象
    3.把这个对象的[[prototype]] 指向了构造函数的prototype属性
    4.执行代码块代码
    5.如果没有明确返回一个非空对象,那么返回的对象就是这个创建出来的对象

    function Person(name, age) {this.name = namethis.age = age
    
    }
    
    const p1 = new Person('ice', 20)
    
    console.log(p1) // {name:'ice', age:20} 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 当我调用new Person()的时候,那个this所指向的其实就是p1对象

    3. 绑定优先级

    3.1 隐式绑定 > 默认绑定

     function bar() {console.log(this) //info
    }
    
    const info = {bar: bar
    }
    
    info.bar() 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 虽然这边比较有些勉强,有些开发者会认为这是默认绑定的规则不能直接的显示谁的优先级高
    • 但是从另外一个角度来看,隐式绑定,的this丢失以后this才会指向widonw或者undefined,变相的可以认为隐式绑定 > 默认绑定

    3.2 显示绑定 > 隐式绑定

     var fullName = 'global ice'
    const info = {fullName: 'ice',getName: function() {console.log(this.fullName) }
    }
    
    info.getName.call(this) //global ice
    info.getName.apply(this) //global ice
    info.getName.bind(this)() //global ice 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 通过隐式绑定和显示绑定的一起使用很明显 显示绑定 > 隐式绑定

    3.3 bind(硬绑定) > apply/call

    function bar() {console.log(this) //123
    }
    
    const newFn = bar.bind(123)
    newFn.call(456) 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.4 new绑定 > bind绑定

    首先我们来说一下,为什么是和bind比较,而不能对callapply比较,思考下面代码

    const info = {height: 1.88
    }
    
    function Person(name, age) {this.name = namethis.age = age
    }
    
    const p1 = new Person.call('ice', 20)
    
    //报错: Uncaught TypeError: Person.call is not a constructor 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    new绑定和bind绑定比较

     const info = {height: 1.88
    }
    
    function Person(name, age) {this.name = namethis.age = age
    }
    
    const hasBindPerson = Person.bind(info)
    
    const p1 = new hasBindPerson('ice', 20)
    
    console.log(info) //{height: 1.88} 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 我们通过bindPerson进行了一次劫持,硬绑定了this为info对象
    • new 返回的固定this的函数
    • 但是我们发现 并不能干预this的指向

    3.5 总结

    new关键字 > bind > apply/call > 隐式绑定 > 默认绑定

    4. 箭头函数 (arrow function)

    首先箭头函数是ES6新增的语法

    const foo = () => {} 
    
    • 1

    4.1 箭头函数this

    var fullName = 'global ice'
    
    const info = {fullName: 'ice',getName: () => {console.log(this.fullName)}
    }
    
    info.getName() //global ice 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 你会神奇的发现? 为什么不是默认绑定,打印结果为ice
    • 其实这是ES6的新特性,箭头函数不绑定this,它的this是上一层作用域,上一层作用域为window
    • 所以打印的结果是 global ice

    4.2 箭头函数的应用场景 进阶

    • 需求: 在getObjName通过this拿到info中的fullName (值为icefullName)
     const info = {fullName: 'ice',getName: function() {let _this = thisreturn {fullName: 'panpan',getObjName: function() {console.log(this) // objconsole.log(_this.fullName)}}}
    }
    
    const obj = info.getName()
    obj.getObjName() 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.当我调用 info.getName() 返回了一个新对象
    2.当我调用返回对象的getObjName方法时,我想拿到最外层的fullName,我通过,getObjName的this访问,拿到的this却是obj,不是我想要的结果
    3.我需要在调用info.getName() 把this保存下来,info.getName() 是通过隐式调用,所以它内部的this就是info对象
    4.getObjName是obj对象,因为也是隐式绑定,this必定是obj对象,绕了一大圈我只是想拿到上层作用域的this而已,恰好箭头函数解决了这一问题

    const info = {fullName: 'ice',getName: function() {return {fullName: 'panpan',getObjName: () => {console.log(this.fullName)}}}
    }
    
    const obj = info.getName()
    obj.getObjName() 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5. 总结

    5.1 this的四种绑定规则

    1.默认绑定
    2.隐式绑定
    3.显示绑定 apply/call/bind(也称硬绑定)
    4.new绑定

    5.2 this的优先级 从高到低

    1.new绑定
    2.bind
    3.call/apply
    4.隐式绑定
    5.默认绑定

    最后

    整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



    有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 相关阅读:
    50、Spring WebFlux 的 自动配置 的一些介绍,与 Spring MVC 的一些对比
    浅谈MyBatis中遇到的问题~
    Linux系统中 uboot、内核与文件系统之间的关系
    (Java版)大小写转换设计一个程序, 输入一行字符串, 将其中大写转为小写, 小写转为大写. 其余字符不变
    按键中断
    flutter系列之:做一个下载按钮的动画
    vue3.0运行npm run dev 报错Cannot find module ‘node:url‘
    什么是Selenium?如何使用Selenium进行自动化测试?
    第四章:单例模式与final
    Python编程实例-音频数据可视化
  • 原文地址:https://blog.csdn.net/weixin_53312997/article/details/127341503