手写call,apply,bind方法

一. 概览

函数call,apply,bind方法在工作中经常用到,所以对其底层实现逻辑的探究很有必要,本文就从0开始手写封装call,apply,bind方法。

二. call,apply

原生call,apply

var name = 'sandy'

function person(a,b,c) {
    console.log(this.name)
    console.log(a,b,c,'a,b,c')
    return {
      a,b,c
    }
}
const egg = {name: 'jack'}
let result =  person.call(egg,'a','b','c');
let result =  person.apply(egg,['a','b','c'])

封装思路

  1. 改变this指向,实则是将调用的函数包含在新的this的属性中;
  2. 当传参为null时,默认是全局对象;
  3. apply和call差别不大,主要差别是apply传的是数组

封装代码(call)

Function.prototype.newMycall = function(obj) {
  // this 为要调用的函数
  var obj = obj || window
  obj.p = this;
  const arr = [].slice.call(arguments,1)
  let result = obj.p(...arr);
  delete obj.p;
  return result
}
let result = person.newMycall(null,'a','b','c')

封装代码(apply)

Function.prototype.newMyapply = function(obj) {
  // this 为要调用的函数
  var obj = obj || window
  obj.p = this;
  const arr = arguments[1] || []
  let result = obj.p(...arr);
  delete obj.p;
  return result
}
let result = person.newMyapply(egg,['a','b','c'])

三. bind

原生用法

function person(a,b,c) {
    console.log(this.name)
    console.log(a,b,c,'a,b,c')
    return {
      a,b,c
    }
}

person.prototype.age = 18
const egg = {name: 'jack'}
const aa = person.bind(egg,'a','b')
// 构造函数用法
const sl = new aa('c')
1. 此时this会恢复默认的,指向全局;
2. s1会继承aa函数实例上的方法和属性

bind方法的封装较apply和call要复杂些,现在一步一步去实现

1. 柯里化传参实现

通过原生bind我们可以看到,bind的使用是传了两次参的,但是最后调用是在一个函数里面调用的,也就是说两次传参聚合在一个函数里面使用,也就是咱们所说的柯里化传参
实现代码

Function.prototype.newBind = function(obj) {
  // this 为要调用的函数
  var obj = obj || window;
  const that = this;
  const arr1 = [].slice.call(arguments,1)
  return function() {
   const arr2 = [].slice.all(arguments)
   const arr = [...arr1,...arr2]
   that.apply(obj,arr)
  }
 
}

这样就初步实现了简单的bind封装,但是不能满足使用构造函数实例的场景,所以虚进一步封装

2. 最终实现

封装思路:

  1. bind()调用之后应该返回一个构造函数,且要继承将要调用的函数的原型上的方法和属性;
  2. 当实例用法时,要调用函数中的this指向全局;
  3. instanceof方法用于区分是实例用法,还是简单的函数用法
  4. 用实例继承的原因是为了避免原型上的相互影响
Function.prototype.newBind = function(obj) {
  // this 为要调用的函数
  var obj = obj || window;
  const that = this;
  const arr1 = [].slice.call(arguments,1)
  const o = function() {}
  const newFun = function () {
    const arr2 = [].slice.call(arguments,0)
    const arr = [...arr1,...arr2];
    let data;
    if(this instanceof newFun) {
      that.apply(this,arr);
    } else {
      const data = that.apply(obj,arr);
      return data
    }
  }
  o.prototype = that.prototype
  newFun.prototype = new o()
  return newFun
 
}
问题

为什么不直接用newFun.prototype = new that()?

new person()的用法是将person作为构造函数去使用, 但是最后调用的person方法中有return ,会改变返回的实例值
(1) 当return一个基本类型时,return不会发生作用,构造函数依旧返回一个实例对象;
(2) 当return一个引用类型时,return会覆盖创建实例,构造函数返回自己手写的引用类型值;
(3) 当只使用return时,return只会终止代码执行。依旧返回一个创建的实例


版权声明:本文为m0_45093055原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。