[路飞]_手写call,apply,bind函数

187 阅读5分钟

call

  1. 定义:调用一个对象的一个方法,以另一个对象替换当前对象。call方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
  2. 使用:call([thisObj[,arg1[, arg2[, [,.argN]]]]])。
  3. thisObj的取值有以下4种情况:
  • 不传,或者传null,undefined, 函数中的this指向window对象。
  • 传递另一个函数的函数名,函数中的this指向这个函数的引用。
  • 传递字符串、数值或布尔类型等基础类型,函数中的this指向其对应的包装对象,如 String、Number、Boolean。
  • 传递一个对象,函数中的this指向这个对象。
  1. 需求: 在一个方法中,通过this输出另一个对象中的某一属性
let ooo = {
  name:'haha'
}

function showname(show,more) {
  let newName = this.name;
  console.log(newName)
  console.log(show,more)
}

showname("show","more");

显然输出救过为undefined,show,more; 因为在showname中this指向的是showname本身,而showname并没有name这个属性。

  1. 解决方案(call方法实现): 如果我们将上述showname方法移到obj对象内部,成为obj内的一个属性,此时再调用showname时,showname内的this指向的是obj对象,那么this.name即为obj的name属性。
let ooo = {
  name:'haha'fn:function (show,more) {
      let newName = this.name;
      console.log(newName)
      console.log(show,more)
    }
}
ooo.fn("show","more");

此时输出haha,show,more。那么为了实现这个功能便出现了call方法。

Function.prototype.myCall = function(obj,...args) {
  let ctx = obj || window;//没有指定指向对象则指向对象设为window
  ctx.fn = this;//这个this指的是调用myCall的函数,将该函数绑定到ctx的fn属性上
  ctx.fn(...args);
  delete ctx.fn;
}

showname.myCall(ooo,"show","more");//haha show more

apply

apply函数和call函数功能是相同的,不同的只是,call函数第一个参数是需要绑定的对象,之后的参数是调用call函数的函数所需要的参数,以","分隔,而apply函数的第一个参数也是需要绑定的对象,之后的参数是调用call函数的函数所需要的参数,只不过这些参数被集中在一个数组中来传递。所以apply的实现与call大同小异,这里直接上代码。

Function.prototype.myApply = function (obj,args) {
  let ctx = obj || window;//没有指定指向对象则指向对象设为window
  ctx.fn = this;//这个this指的是调用myApply的函数,将该函数绑定到ctx的fn属性上
  args? ctx.fn(...args): ctx.fn();
  delete ctx.fn;
}
showname.myApply(ooo,["show","more"]);//haha show more

bind

  1. 定义:bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
  2. bind函数的特点:
  • 返回一个函数
  • 可以传入参数
  1. 需求:同call函数需求一样,在一个方法中,通过this输出另一个对象中的某一属性,只不过这次我们不直接执行调用绑定的函数,而是将调用绑定的函数的this指向改变后返回新的函数。
let ooo = {
  name:'haha'
}

function showname(show,more) {
  let newName = this.name;
  console.log(newName)
  console.log(show,more)
}

showname("show","more");
  • 既然用到了对象绑定,那自然需要使用我们之前的call或apply方法来绑定新的对象。
  • 这里需要注意的是,bind只能应用于方法,所以需要先做判断处理
Function.prototype.myBind = function (obj) {
  if (typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  };
  //取出绑定时的参数,此时的arguments指的是调用myBind函数时传的参数
  var args = Array.prototype.slice.call(arguments,1)[0];
  var self = this;

  var result = function() {
  //取出调用时的参数,此时的arguments指的是result函数传的参数
    var bindArgs = Array.prototype.slice.call(arguments)[0];
    self.apply(obj,args.concat(bindArgs));
  }
  return result;
}
  • 这样只是实现了当调用myBind的函数是个普通方法时的功能,但是bind函数还有一个特点,一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
  • 所以我们在让this值重新指向的时候需要考虑,调用绑定函数的函数是否是个构造函数,如果是构造函数,那么就需要将this指向构造函数的实例而不是bind方法的第一个参数对象(这也符合了js中this的绑定原则,this的绑定分为new绑定,显示绑定call、apply、bind,隐式绑定对象中的方法的this隐式绑定为该对象,默认绑定window全局对象或严格模式的undefined)
  • 我们使用一个中间函数,让他继承调用绑定函数的原型,再让生成的函数原型继承这个中间函数,这样,当我们改变生成函数的原型中的某个属性时,就不会影响到调用绑定函数的函数的属性,因为此时生成的函数与调用函数之间多了一层原型,就是中间函数的原型。
Function.prototype.myBind = function (obj) {
  if (typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  };
  var args = Array.prototype.slice.call(arguments,1)[0];//取出绑定时的参数
  var self = this;
  var middleFun = function() {};//设置中间函数

  var result = function() {
    var bindArgs = Array.prototype.slice.call(arguments)[0];//取出调用时的总参数
    /**
     * 当绑定函数作为构造器时,this指向实例,可以让实例获得绑定函数的值
     * 当绑定函数为普通函数时,this指向window,将绑定函数的this指向obj
     */
    self.apply(this instanceof middleFun? this: obj,args.concat(bindArgs));
  }
  
  //中间函数继承调用绑定函数的函数
  middleFun.prototype = self.prototype;
  //生成的函数再继承中间函数,这样当我们改变生成函数的原型中的某个属性时,
  //就不会影响到调用绑定函数的函数的属性,因为此时生成的函数与调用函数之间多了一层原型,
  //就是中间函数的原型。
  result.prototype = new middleFun();//使用new继承,可以使middleFun的this丢失,重新指向new的新对象
  return result;
}

var aaa = showname.myBind(ooo,["show"]);//haha show more
aaa(["more"])
var bbb = new aaa(111);//undefined show 111