>  기사  >  이 점을 이해하고 프론트엔드 70%를 따라잡는 글

이 점을 이해하고 프론트엔드 70%를 따라잡는 글

青灯夜游
青灯夜游앞으로
2022-09-06 17:03:132919검색

이것이 지적한 버그 문제, vue2의 이 포인팅 문제로 인해 동료가 막혔고, 화살표 함수를 사용하여 해당 소품 >. 내가 그에게 소개했을 때 그는 그것을 몰랐고, 그 후 일부러 프론트엔드 커뮤니케이션 그룹을 살펴보았습니다. 지금까지 적어도 70%의 프론트엔드 프로그래머가 그것을 이해하지 못했습니다. 이것 code>가리키고, 배운 것이 없다면 큰 소리로 말해주세요. <code>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对象上
    • 如果采用letconst 则不是,具体的后续介绍一下这两个申明变量的关键字
  • 但是有些场景,我不想让隐式丢失怎么办,下面就来给大家介绍一下显示绑定,也就是固定调用。

2.3 显示绑定

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

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

2.3.1 call/apply/bind

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

1. 호출 위치

  • 범위는 실행 위치가 아니라 정의된 위치와 관련됩니다.
  • this 정의된 위치와는 아무런 관련이 없지만 호출 방법 및 형식과 관련이 없는 함수를 가리킵니다.
  • this(this) 호출 방법(기억하기 쉬움)
  • 이해를 돕기 위해 기본적으로 strict 모드는 활성화되어 있지 않습니다.

2. 바인딩 규칙

  위에서 소개한 것처럼 this를 가리키는 것은 주로 호출 형식과 관련이 있습니다. 다음으로 호출 규칙을 소개하겠습니다. 규칙이 없으면 아무것도 완료되지 않습니다. 이 호출 규칙을 명심하십시오. 🎜
  • 통화 위치를 찾은 다음 다음 네 가지 바인딩 규칙 중 무엇을 결정해야 합니다.
  • 두 번째로 이 네 가지 바인딩 규칙의 우선 순위도 알아야 합니다.
  • 이 두 가지 사항을 알고 있으며 그 방향을 아는 것은 쉽습니다.

2.1 기본 바인딩

🎜   함수를 호출하는 가장 일반적인 방법, 함수 호출 유형: 독립적인 함수 호출🎜
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)
  • bar는 수정자가 없는 직접 호출이므로 기본 바인딩은 window
  • 엄격 모드에서 정의되지 않음

2.2 암시적 바인딩

🎜   가장 널리 사용되는 용어로, 이는 객체에 특정 메소드가 있고 해당 메소드가 이 객체를 통해 액세스되어 직접 호출된다는 의미입니다(참고: 화살표 함수는 특별합니다. 아래) 🎜
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)
  • 이 함수는 info에 의해 호출되고 암시적으로 바인딩되므로 현재 thisinfo입니다. code>this.fullName 액세스 값이 ice
🎜암묵적인 정상 손실🎜🎜  일부에서는 의심할 여지가 없습니다. 경우 암시적 손실이 발생합니다. 암시적으로 바인딩된 함수는 바인딩 개체를 잃게 됩니다. 즉, 기본 바인딩이 되며 기본 바인딩의 값이 window인지 여부입니다. code> 또는 <code>undefine은 현재 환경이 엄격 모드인지 여부에 따라 달라집니다. 🎜
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)
🎜  이 경우 암시적 손실이 수행되고 바인딩된 개체가 손실되는 이유는 무엇입니까? 기억에 익숙하다면 이해하기 쉬울 것이다. 🎜
  • 여기서 직접 호출은 없지만 info를 통해 getName에 해당하는 메모리 주소를 찾아 fn 변수에 할당합니다. code>
  • 그런 다음 fn을 통해 직접 호출이 이루어졌습니다.
  • 사실 여기서 핵심은 독립적인 함수 호출, 즉 window, window에서 fullName 속성을 ​​제거하고 정의되지 않음
🎜암시적이어야 합니다. 손실 고급
여기서 모든 사람은 먼저 콜백 함수가 무엇인지 이해해야 합니다. 사실 이렇게 이해하면 됩니다. 즉, 지금은 호출하지 않고, 매개변수 형태로 다른 곳에 전달해서 다른 곳에서 호출한다는 뜻입니다. 🎜
function Person(name, age) {
  this.name = name
  this.age = age

}

const p1 = new Person('ice', 20)

console.log(p1) // {name:'ice', age:20}
  • 우선 barfn은 콜백 함수입니다
  • fn = info.getName code> 매개변수 전달은 일종의 암시적 할당이며 실제로 위의 암시적 손실과 동일한 의미를 갖습니다. 이는 모두 메모리 주소인 <code>fn = info.getName 참조를 가리킵니다. li>this가 손실되었기 때문입니다. 즉, 함수가 독립적으로 호출되고 기본 바인딩 규칙은 this가 전역 창이라는 것입니다. code> 객체
  • 참고: 선언이 var여야 하는 이유는 무엇입니까?
    • var로 선언된 변수만 전역 window 객체에 추가되기 때문입니다.
    • letconst인 경우 사용됨 > 아니요, 변수 선언을 위한 두 가지 키워드는 나중에 자세히 소개하겠습니다.
  • 그러나 일부 시나리오에서는 암시적 손실을 원하지 않습니다. ? 다음은 고정 호출(Fixed Calling)인 디스플레이 바인딩(Display Binding)을 소개하겠습니다.

2.3 디스플레이 바인딩

🎜  그러나 일부 시나리오에서는 실제로 콜백 함수가 실행되는 방식을 제어할 수 없으므로 호출 위치가 예상되는 바인딩을 얻었는지, 즉 this가 이를 가리키는지 제어할 방법이 없습니다. 🎜🎜다음 디스플레이 바인딩을 사용하면 이 암시적 손실 문제를 해결할 수 있습니다. 🎜

2.3.1 call/apply/bind

🎜  js의 모든 함수에는 몇 가지 유용한 기능이 있으며, 이는 프로토타입과 동일합니다. 함수에서 호출하는 체인입니다. 🎜

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

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으로 문의하시기 바랍니다. 삭제