JS:如何实现一个bind和call?

98 阅读3分钟

bind/apply/call

一.联系与区别

相同点
三个方法都是Function的原型方法,都可以改变this的指向,第一个参数都是this需要指向的对象,如果第一个参数为nullundefined时,指向window或者Global
不同点

  • call和apply用法基本一致,call'的参数是依次传参,是一一映射的。apply除了一个绑定的对象外,剩下的参数都放在一个数组里,作为第二个参数传回去。call和apply都是对函数直接调用的。
  • bind的传参与call相同,但是bind方法返回的还是一个函数。
function fn(age) {
  console.log(`我是${this.name},今年${age}岁,喜欢吃${this.fruit}`)
}
let obj1 = {
  name:"张三",
  fruit:"苹果"
}
let obj2 = {
  name:"李四",
  fruit:"西瓜"
}
fn()
fn.call(obj1,18)
fn.apply(obj2,[19])
fn.bind(obj2,20)()
///////
我是undefined,今年undefined岁,喜欢吃undefined
我是张三,今年18岁,喜欢吃苹果
我是李四,今年19岁,喜欢吃西瓜
我是李四,今年20岁,喜欢吃西瓜

二. 如何实现一个bind?

Function.prototype.bind()
分析:

  • bind是接收n个参数,但是第一个值为绑定的对象。
  • bind的返回是返回一个函数 先按照思路写一些伪代码:
  function _bind(params){
    处理原本的this
    获取剩下的参数  ...args
    返回一个function(){
       改变一个this的指向,并可以直接调用
    }
  }
function _bind(context){
  const fn = this;
  const  args = Array.prototype.slice.call(arguments,1)
  return function (...innerArgs) {
      return fn.apply(context,[...args,...innerArgs])
  }
}

测试使用一下:

Function.prototype._bind = function(context){
  const fn = this;
  const  args = Array.prototype.slice.call(arguments,1)
  return function (...innerArgs) {
    const all = [...args,...innerArgs]
     return fn.apply(context,all)
  }
}
function show() {
  this.bar = "bar"
  return this.name

}
var fnShow = show._bind({name:"name2"})
console.log(fnShow())
打印结果:name2

但是如果使用new关键字,new中this的指向会高于bind,bind的返回的函数如果作为构造函数,搭配new关键字出现的话,这种绑定就需要被忽略,绑定到实例上。

Function.prototype._bind = function(context){
  const fn = this;
  const  args = Array.prototype.slice.call(arguments,1)
  //创建一个F函数
   const F = function () {}  
   //将this的原型对象指向F的原型对象,这里保存,之后可以用 this instanceof F 来判断是不是构造函数
  F.prototype = this.prototype
  let bound = function () {
    let innerArgs =  Array.prototype.slice.call(arguments)
    const finalArgs = [...args,...innerArgs]
    // 在这里进行new 的判断
    return fn.apply(this instanceof F ? this : context || this , finalArgs)
  }
  bound.prototype = new F()
  return bound
}
function fn(name) {
  this.bar = "bar"
  this.name = name

}
function show() {
  console.log(this.name)
}
var fnShow =show._bind(new fn('baz'))
console.log(fnShow())

构造函数中 this 指向 new 创建的实例。(this instanceof F)所以可以通过在函数内判断 this 是否为当前函数的实例进而判断当前函数是否作为构造函数。

1653462317(1).png

三、如何实现一个call?

分析一下call的使用方法

function show(bar,baz,foo) {
   console.log(this.name || "",bar,baz,foo)
}
let obj = {
  name :"name"
}
show('bar','baz','foo')
show.call(null,'bar','baz','foo')
show.call(obj,'bar','baz','foo')
  • 用函数绑定call之后,对函数直接调用
  • 没有绑定对象,立即执行 分析:
  • 参数依次展开 context ,参数1,参数2,参数3....
  • if(对象){ 修改this指向,执行 }else{ 直接执行 }
    Function.prototype._call = function (context,...args) {
      //this 为 Function 给context绑上要执行的方法,用显示调用的方式进行模拟
      context.fn = this
      if(context){
        const result = context.fn(...args)
        delete context.fn
        return result
      }else{
        return  this(...args)
      }
    }