阅读 484

知识复盘,手写call,apply,bind

call、apply、bind用法

call用法

var a = {
  user: 'call',
  fn: function (x,y,z) {
    console.log(this.user,x,y,z);
  }
}
var b = a.fn
b.call(a,1,2,3) // call 1 2 3
复制代码

传递的第一个参数做为调用它的函数的this指向,后边参数为函数本身的参数。

apply用法

var a = {
  user: 'apply',
  fn: function (x,y,z) {
    console.log(this.user,x,y,z);
  }
}
var b = a.fn
b.apply(a,[1,2,3]) // apply 1 2 3
复制代码

传递的第一个参数做为调用它的函数的this指向,后边参数为函数本身参数的数组形式。

bind用法

var a = {
  user: 'bind',
  fn: function (x, y, z) {
    console.log(this.user, x, y, z);
  }
}
var b = a.fn

// 下面两种都可以
var c = b.bind(a) 
console.log(typeof c); // function
c(1, 2, 3) // bind 1 2 3

var d = b.bind(a, 1, 2)
d(3) // bind 1 2 3
复制代码

传递的第一个参数做为调用它的函数的this指向,通过控制台可以看到返回的是一个函数。

call、apply实现

call实现

Function.prototype.myCall = function (context) {
  // 判断是否是个函数
  if (typeof this !== 'function') {
    throw new Error('not function')
  }
  // 如果mycall()里为空就指向window
  context = context || window
  // this指向调用的mycall的函数
  context.fn = this // 把它挂到需要指向的对象上
  // 取下后面需要的参数
  let arg = [...arguments].slice(1)
  let result;
  result = context.fn(...arg)  // 执行
  delete context.fn // 用完删掉
  return result
}
复制代码

apply与call有异曲同工之妙,只需小小改动就可完成

apply实现

Function.prototype.myApply = function (context) {
  if (typeof this !== 'function') {
    throw new Error('not function')
  }
  context = context || globalThis
  // 向context添加一个属性,挂载this,this就是调用myApply的函数
  context.fn = this
  // 解构
  let [arg] = [...arguments].slice(1)
  let result;
  if (arg) {
    result = context.fn(...arg)
  } else {
    result = context.fn()
  }
  result = context.fn(...arg)
  delete context.fn
  return result
}
复制代码

接下来我们来手写bind,首先,bind需要返回一个函数,且函数可以传参,使用可以这样实现

bind实现

bind初步实现

// 初步版
Function.prototype.myBind = function (obj) {
  if (typeof this !== 'function') {
    throw new Error('not function')
  }
  obj = obj || window
  // 因为要返回函数,所以要防止this的丢失
  var that = this
  // 参数切割,第一个arguments对象,包含mybind第一个括号的参数
  let arr = Array.prototype.slice.call(arguments, 1)
  // 会返回一个函数
  return function () {
    // 包含mybind第二个括号的参数
    let arr2 = Array.prototype.slice.call(arguments)
    // 合并前后两个数组
    arrSum = arr.concat(arr2)
    // 此时这个that指向的是调用myBind的函数,此时就是b
    that.apply(obj, arrSum);
  }
}
复制代码

相信你也看到了,这只是初步版,为什么呢。
因为 bind 还有一个特点,如果使用new来调用,也就是发生了构造函数,那么实例会绑定到函数调用的 this ,换句话说就是绑定的this已经指向了实例对象,所以this值会因此失效,但传入的参数依旧会生效,所以,我们来看第二版,考虑 new 调用的情况。

bind的进一步改进

// 改进版,bind方法可以配合new使用而this值会因此失效
Function.prototype.myBind2 = function (obj) {
  if (typeof this !== 'function') {
    throw new Error('not function')
  }
  obj = obj || window
  var that = this
  let arr = Array.prototype.slice.call(arguments, 1)
  // 将返回函数设置为具名函数
  let newFn = function () {
    // 此时的this不是调用myBind的函数,而是newFn的实例
    let arr2 = Array.prototype.slice.call(arguments)
    arrSum = arr.concat(arr2)
    // 判断有没有使用new绑定
    if (this instanceof newFn) {
      // 将this绑定到新实例上面
      that.apply(this, arrSum)
    } else {
      that.apply(obj, arrSum);
    }
  }
  // 将调用myBind的函数的原型对象赋值给myBind里返回函数的原型对象
  newFn.prototype = that.prototype
  return newFn;
}
复制代码

在这个写法中,我们将 newFn.prototype = that.prototype,确实可以做到将调用myBind的函数的原型对象和返回的函数原型对象绑定起来。
但我们直接修改 newFn.prototype 的时候,也会直接修改函数的 prototype,解决这个问题,我们可以通过一个空函数来进行中转。

bind的再一步改进

Function.prototype.myBind3 = function (obj) {
  if (typeof this !== 'function') {
    throw new Error('not function')
  }
  obj = obj || window
  var that = this
  let arr = Array.prototype.slice.call(arguments, 1)
  // 中转函数
  let o = function () {}
  // 将返回函数设置为具名函数
  let newFn = function () {
    // 此时的this不是调用myBind的函数,而是newFn的实例
    let arr2 = Array.prototype.slice.call(arguments)
    arrSum = arr.concat(arr2)
    // 判断有没有使用new绑定
    if (this instanceof newFn) {
      // 将this绑定到新实例上面
      that.apply(this, arrSum)
    } else {
      that.apply(obj, arrSum);
    }
  }
  // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承函数的原型中的值
  // 这里我添加了一个中转函数,防止直接修改函数的prototype
  o.prototype = that.prototype
  newFn.prototype = new o
  return newFn;
}
复制代码

文章写到这就结束啦,如果本文有错误或者不严谨的地方,请务必给予指正,万分感谢。

文章分类
前端
文章标签