首頁  >  文章  >  一篇搞懂this指向,追趕70%的前端人

一篇搞懂this指向,追趕70%的前端人

青灯夜游
青灯夜游轉載
2022-09-06 17:03:132916瀏覽

同事因為this指向的問題卡住的bugvue2的this指向問題,使用了箭頭函數,導致拿不到對應的props 。當我給他介紹的時候他竟然不知道,隨後也刻意的看了一下前端交流群,至今最起碼還有70%以上的前端程式設計師搞不明白,今天給大家分享一下this指向,如果啥都沒學會,請給我一個大嘴巴子。

1. 呼叫位置

  • 作用域跟在哪裡定義有關,與在哪裡執行無關
  • ##this指向跟在哪裡定義無關,跟如何調用,透過什麼樣的形式調用有關
  • this#(這個) 這個函數如何被調用(方便記憶)
  • #為了方便理解,預設不開啟嚴格模式

2. 綁定規則

  上面我們介紹了,

this的指向主要跟透過什麼樣的形式呼叫有關。接下來我就跟大家介紹一下調用規則,沒有規矩不成方圓,大家把這幾種調用規則牢記於心就行了,沒有什麼難的地方。

    你必須找到呼叫位置,然後判斷是下面四種的哪一種綁定規則
  • #其次你要也要曉得,這四種綁定規則的優先順序
  • 這兩點你都知道了知道this的指向對於你來說易如反掌

#2.1 預設綁定

   函數最常用的呼叫方式,呼叫函數的類型:獨立函數呼叫

function bar() {
  console.log(this) // window
}
    bar是不帶任何修飾符的直接呼叫所以為預設綁定為
  • window
  • 在嚴格模式下這裡的
  • thisundefined

#2.2 隱式綁定

  用最通俗的話表示就是:物件擁有某個方法,透過這個物件存取方法且直接呼叫(註:箭頭函數特殊,下面會講解)

const info = {
  fullName: 'ice',
  getName: function() {
    console.log(this.fullName)
  }
}

info.getName() // 'ice'
    這個函數被
  • info發起調用,進行了隱式綁定,所以當前的thisinfo,透過this.fullName毫無疑問的就訪問值為 ice

隱含丟失普通

  有些情況下會進行隱式丟失,被隱式綁定的函數會丟失綁定對象,也就是說它為變成預設綁定,預設綁定的

this值,為window還是undefined取決於您目前所處的環境,是否為嚴格模式。

const info = {
  fullName: 'ice',
  getName: function() {
    console.log(this.fullName)
  }
}

const fn = info.getName

fn() //undefined
  這種情況下就進行了隱式丟失,丟失了綁定的對象,為什麼會產生這樣的問題呢?如果熟悉記憶體的小夥伴,就會很容易理解。

    這裡並沒有直接調用,而是透過
  • 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.getName
  fn() // panpan
}

bar(info.getName)
    首先
  • bar中的fn為一個回呼函數
  • fn = info.getName 參數傳遞就是一種隱式賦值,其實跟上面的隱式遺失是一個意思,他們都是指向的fn = info.getName引用,也就是它們的記憶體位址
  • 因為他們的
  • this遺失,也就是函數獨立調用,預設綁定規則,this為全域的window物件##注意: 為什麼申明必須為
  • var
  • 呢? 因為只有
      var
    • 申明的變數才會加入到全域window物件上如果採用
    • let\const
    • 則不是,具體的後續介紹一下這兩個申明變數的關鍵字
    但是有些場景,我不想讓隱式丟失怎麼辦,下面就來給大家介紹一下顯示綁定,也就是固定調用。
2.3 顯示綁定

  但是在某些場景下,

this

的改變都是意想不到的,實際上我們無法控制回呼函數的執行方式,因此沒有辦法控制呼叫位置已得到期望的綁定即this指向。 接下來的顯示綁定就可以用來解決這個隱式遺失問題。

2.3.1 call/apply/bind

  js中的」所有「函數都有一些有用的特性,這個跟它的原型鏈有關係,後續我會在原型介紹,透過原型鏈js中變相實現繼承的方法,其中

call/apply/bind

這三個方法就是函數原型鏈上的方法,可以在函數中呼叫它們。 <h4 data-id="heading-7"><strong>2.3.2 call</strong></h4> <ul> <li> <code>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)

    2.3.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)

    2.3.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)

    2.4 new绑定

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

    • 创建了一个空对象

    • 将this指向所创建出来的对象

    • 把这个对象的[[prototype]] 指向了构造函数的prototype属性

    • 执行代码块代码

    • 如果没有明确返回一个非空对象,那么返回的对象就是这个创建出来的对象

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

    3. 绑定优先级

    3.1 隐式绑定 > 默认绑定

    function bar() {
      console.log(this) //info
    }
    
    const info = {
      bar: bar
    }
    
    info.bar()
    • 虽然这边比较有些勉强,有些开发者会认为这是默认绑定的规则不能直接的显示谁的优先级高
    • 但是从另外一个角度来看,隐式绑定,的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
    • 通过隐式绑定和显示绑定的一起使用很明显 显示绑定 > 隐式绑定

    3.3 bind(硬绑定) >  apply/call

    function bar() {
      console.log(this) //123
    }
    
    const newFn = bar.bind(123)
    newFn.call(456)

    3.4 new绑定 > bind绑定

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

    const info = {
      height: 1.88
    }
    
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    
    const p1 = new Person.call('ice', 20)
    
    //报错: Uncaught TypeError: Person.call is not a constructor

    new绑定和bind绑定比较

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

    3.5 总结

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

    4. 箭头函数 (arrow function)

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

    const foo = () => {}

    4.1 箭头函数this

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

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

    • 需求: 在getObjName通过this拿到info中的fullName (值为icefullName)
    const info = {
      fullName: 'ice',
      getName: function() {
        let _this = this
        return {
          fullName: 'panpan',
          getObjName: function() {
            console.log(this) // obj
            console.log(_this.fullName)
          }
        }
      }
    }
    
    const obj = info.getName()
    obj.getObjName()
    • 当我调用 info.getName() 返回了一个新对象

    • 当我调用返回对象的getObjName方法时,我想拿到最外层的fullName,我通过,getObjName的this访问,拿到的this却是obj,不是我想要的结果

    • 我需要在调用info.getName() 把this保存下来,info.getName() 是通过隐式调用,所以它内部的this就是info对象

    • 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()

    5. 总结

    5.1 this的四种绑定规则

    • 默认绑定

    • 隐式绑定

    • 显示绑定 apply/call/bind(也称硬绑定)

    • new绑定

    5.2 this的优先级 从高到低

    • new绑定

    • bind

    • call/apply

    • 隐式绑定

    • 默认绑定

    6. 结语

      当一切都看起来不起作用的时候,我就会像个石匠一样去敲打石头,可能敲100次,石头没有任何反应,但是101次,石头可能就会裂为两半 我知道并不是第101次起了作用,而是前面积累所致。

      大家有疑惑可以在评论区留言 第一时间为大家解答。

    (学习视频分享:web前端开发

    陳述:
    本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除