历经九九八十一难,终于啃下了this显示绑定,并完成了方法重写

前言

hi 各位前端大佬~ 已经好久没有写过文章了,最近一直在巩固一些基础知识,复习到显示绑定函数中 this 指向问题时,加深了一下手写实现原生方法的印象,那么下面就带大家一起看下

this的绑定规则

在我们学习 JavaScript 时避免不了 this 指向这一问题,this 绑定大致分为四种情况

  1. 默认绑定
  2. 隐式绑定
  3. new绑定
  4. 显示绑定

那么这篇文章就要重点分析一下第四点显示绑定的内容

this的显示绑定

首先我还是要说明一下,可能是我比较菜😂,在日常开发中其实我用到显示绑定还是比较少的,也许没有完全理解应用场景,所以这里大佬们看到可以多给与一些使用场景来帮助我对显示绑定的理解与应用,那么废话少说,进入正题,

call方法

const obj = {name:'张三'}
function fn() {
    console.log(this.name) // 张三
}
fn.call(obj)

想访问 obj 对象中的 name 值就需要改变 fn 函数的 this 指向,正常来讲 fn 在全局中调用的话 this 是指向 window 的,所以 name 自然是访问不到,它的值为 undefined,那么通过 call 方法显示改变了 this 绑定为 obj 对象,所以可以打印出 obj 中的 name 值

那么除了基础的使用,我们再来看下 call 方法的其他使用情景

// 添加入参
const obj = {name:'张三'}
function fn(age) {
    console.log(age,this.name) // 12 张三
}
fn.call(obj,12)

// 传入 null 值
function fn() {
    console.log(this) // window
}
fn.call(null)

// 函数返回值
const obj = {name:'张三'}
function fn() {
   return this.name
}
fn.call(obj)  // 张三

所以我们可以总结出 call 方法可以接收参数并给到其调用函数,也可以传入 null 此时 this 指向全局,还可以接收到调用函数的返回值

手写call方法

思路梳理

通过上面代码示例的介绍我们可以构思一个自定义 call 方法,整体思路如下:

  1. 函数调用call方法改变了this指向 => 在对象中定义了该函数并执行了这个函数,最后将对象中的函数删除
  2. call可以添加入参,自定义方法要获取除第一个参数的其他入参传给对象中新增的函数
  3. 传入null的this指向window,对自定义方法的第一个参数进行判断赋值如果是null则赋值为window
  4. call能获取到返回值,自定义方法中对象定义的函数获取返回值并在自定义方法中return即可

代码实现

apply方法

call 方法类似,apply 的差异在于指定对象后面接的参数为数组的形式

  const obj = {
    value: 1,
  }

  function fn(name, age) {
    console.log(this.value, name, age) // 1 测试 23
  }

  fn.apply(obj, ['测试', 23])

手写 apply 方法

那么根据手写call的思路,我们只要对参数进行一下调整就好了

  Function.prototype.MyApply = function (context, arr = []) {
    var context = context || window
    context.someFn = this
    const res = context.someFn(...arr)
    delete context.someFn
    return res
  }

bind方法

bind 方法与前者有些差异,他会创建一个函数,当新函数被调用时,bind 的第一个参数调动 this 指向,其余参数按顺序传递

  const obj = {
    value: 1,
  }

  function fn(name, age) {
    console.log(this.value, name, age)
  }

  fn.bind(obj)('测试', 12) // 1 测试 12
  fn.bind(obj, '测试')(12) // 1 测试 12
  var value = 2
  var obj = {
    value: 1,
  }

  function fn() {
    this.val = '测试val'
    this.value = '测试内容value'
    console.log(this.value) // 测试内容value
  }

  fn.prototype.other = '测试内容other'

  var bindFn = fn.bind(obj)
  var obj1 = new bindFn()
  console.log(obj1.val) // 测试val
  console.log(obj1.value) // 测试内容value  打印fn中的value
  console.log(obj1.other) // 测试内容other
let a = 1
a.bind() // TypeError: a.bind is not a function

手写 bind 方法

那么根据上面的 bind 方法使用我们就有了整体思路

  1. 自定义函数返回值是一个函数
  2. 返回的函数可以接收参数并借用apply方法进行this绑定
  3. 返回的函数作为构造函数时this的指向发生改变,指向了构造函数返回的实例
  4. 为返回的函数确定原型
  5. 调用自定义方法必须是个函数

普通函数

根据前两点我们可以这样实现自定义的 bind 方法

Function.prototype.MyBind = function (context) {
    var _this = this
    var args = [...arguments].slice(1)
    var fBound = function () {
      var bindArgs = [...arguments]
      return _this.apply(context, [...args, ...bindArgs]) // 注意这里的this是调用函数的this,可以使用剪头函数确定this,也可通过匿名函数进行重新赋值,如果不进行this重新赋值,则指向window,相当于在全局调用了这个函数
    }
  }
  
  const obj = {
      value:1
  }
  
  function fn(){
      console.log(this.value)
  }
  
  var bindFn = fn.MyBind(obj)
  bindFn()
  

根据上面的代码可知,我们在调用MyBind返回的函数时this是指向全局window的,但是我们在调用apply方法时是想让fn调用的,所以就要在一开始获取函数的this重新赋值_this,在返回的函数中使用这个重新赋值的_this进行调用apply,此时的_this就是函数的this,最后我们将外层函数和内层返回的函数获取的参数进行合并,一起传给apply即可

构造函数

根据分析出的3、4两点,我们对返回的函数作为构造函数并进行实例化时,this又发生了改变,所以这个时候就要对返回的函数的this进行判断处理了,当我们调用的this是这个返回函数的实例时就将apply绑定的就是这个实例化对象,否则就绑定传入的普通对象

Function.prototype.MyBind = function (context) {
    var _this = this
    var args = [...arguments].slice(1)
    var fBound = function () {
      var bindArgs = [...arguments]
      return _this.apply(this instanceof fBound ? this : context, [
        ...args,
        ...bindArgs,
      ])
    }
    return fBound
 }

之后我们还要确定下调用自定义 bind 还必须要是一个函数,并且要确定下返回函数的原型为this的原型

  Function.prototype.MyBind = function (context) {
    if (typeof this !== 'function') {
      throw new Error('This is not a function')
    }
    var _this = this
    var args = [...arguments].slice(1)
    var fn = function(){}
    var fBound = function () {
      var bindArgs = [...arguments]
      return _this.apply(this instanceof fBound ? this : context, [
        ...args,
        ...bindArgs,
      ])
    }
    fn.prototype = this.prototype
    fBound.prototype = new fn()
    return fBound
  }

通过一个空函数的过渡,我们就不会修改掉this的原型了,到这里我们就实现了自定义bind的功能

总结

关于这次学习,我觉得收获有一下几点

  1. 首先我们最重要的是要熟悉原生的显示绑定方法是如何使用的,根据原生的方法去构思我们自定义的方法
  2. 其次就是对this的理解,搞清楚当前的引用,this的指向;这点很重要,不然bind的方法很容易就会被绕懵
  3. 最后一点还是对JavaScript的基础掌握要牢固,之前感觉自己对一些概念知识清楚并不深刻,但通过不断学习不断磨炼,对一些模棱两可的概念逐渐理解并清晰,我觉得这一点是最重要的,遇见不会的知识点还是要多动手实现理解起来会更快吧

这次分享的东西其实是老生常谈的问题,但是自己真正理解还是收获颇多,后面还是要继续“武装”自己,如果有不对的地方欢迎大佬们批评指正!

原文链接:https://juejin.cn/post/7348475959287676955 作者:爱泡澡的小萝卜

(0)
上一篇 2024年3月21日 下午4:16
下一篇 2024年3月21日 下午4:26

相关推荐

发表回复

登录后才能评论