【手写代码】call,apply,bind 的区别和实现原理

目录

一、call,apply,bind 区别

1.作用

2.区别

3.应用场景

二、手写 call,apply,bind

1.手写 call 函数

2.手写 apply 函数

3.手写 bind 函数

简陋版

完整版

终极版


一、call,apply,bind 区别

1.作用

强制绑定this的指向

问:为什么要强制绑定this的指向?

答:使用call()或apply()可以让任意对象调用任意函数,等同于将函数作为了对象的属性,然后我们就可以通过对象.属性的方式调用函数。 而bind()会拷贝原函数来创建新的函数实例,其this值会被绑定到传给bind()的对象。

2.区别

1. 传参

  • 传参形式不一样, call() 方法接受的是参数列表,而 apply() 方法接受的是一个参数数组
fn.call(obj, 1, 2);     //this-->obj,fun传入参数 1,2
fn.apply(obj, [1, 2]);  //this-->obj,fun传入参数 1,2

具体的可以看这篇call 和apply的作用与区别(文章中最后构造函数的例子是 call 的一个使用场景)

细节:

非严格模式:如果不传参数,或者第一个参数是nullundefinedthis都指向window

严格模式:第一个参数是谁,this就指向谁,包括nullundefined,如果不传参数this就是undefined

2. 返回值

  • bind绑定完不会立即执行函数,而是将函数返回【当要为回调函数绑定this只能用bind】;
  • call,apply指定this后立即执行
fn.bind(obj);  //需要调用
//---------------------
var fn2=fn.bind(obj);
fn2();
//--->等价于
fn.bind(obj)();

3.应用场景

setTimeout(function(){
    console.log(this);
}.bind(obj),2000)
//给类数组增加属性
var arrLike={
    '0':'chalio',
    '1':'tom',
    '2':'jarry',
    length:4
};
arrLike.push('larry');  //报错
Array.prototype.push.call(arrLike,'larry'); //添加成功
//另一种办法 
Object.setPrototypeOf(arrLike,Array.prototype); //让arrLike继承Array的属性
arrLike.push('larry');  //添加成功
//另一种办法--同理
arrLike.__proto__=Array.prototype;
arrLike.push('larry');  //添加成功

二、手写 call,apply,bind

1.手写 call 函数

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。

  3. 处理传入的参数,截取第一个参数后的所有参数。

  4. 将函数作为上下文对象的一个属性。

  5. 使用上下文对象来调用这个方法,并保存返回结果。

  6. 删除刚才新增的属性。

  7. 返回结果。

Function.prototype.myCall = function(context) {
    // 准备1:判断调用对象是否为函数
    if (typeof this !== "function") {
      console.error("type error");
    }
    // 准备2:判断 context 是否传入,如果未传入则设置为 window
    context = context || window;
    // 准备3:获取参数
    let args = [...arguments].slice(1);
    
    // 1.将调用函数设为对象的方法
    context.fn = this;
    // 2.调用函数
    let result = context.fn(...args);
    // 3.将属性删除
    delete context.fn;
    return result;
}

2.手写 apply 函数

我们已经了解了 call的实现原理,call() 方法接受的是参数列表,而 apply() 方法接受的是一个参数数组。所以在 call 实现的基础上我们很容易写出 apply

apply 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。

  3. 将函数作为上下文对象的一个属性。

  4. 判断参数值是否传入

  5. 使用上下文对象来调用这个方法,并保存返回结果。

  6. 删除刚才新增的属性

  7. 返回结果

// apply 函数实现
Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;
  
  let result = null;
  // 1.将函数设为对象的方法
  context.fn = this;
  // 2.调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  // 3.将属性删除
  delete context.fn;
  return result;
};

3.手写 bind 函数

简陋版

call、aplly是直接调用函数,bind是返回一个新的函数。

  1. 在bind之前,我们已经了解了call和apply的实现,主要区别就是bind返回一个新的函数(具有原函数的功能)。
  2. 在绑定this之后,可以使用call或apply来表示那个返回的函数
// bind 函数实现—自己写的简陋版
// 俺只写了关键步骤,原理就是这么个原理,浓缩的都是精华!
Function.prototype.myBind = function(thisArg,...list){
    let fn = this; // 目标函数
    return function(...arg2){
      return fn.apply(thisArg,[...list,...arg2])
    }
}

完整版

bind 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

  2. 保存当前函数的引用,获取其余传入参数值。

  3. 创建一个函数返回

  4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。

// bind 函数实现—完整版
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
      fn = this;
  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,  // 这一步又在判断个什么,也太细节了吧!
      args.concat(...arguments)
    );
  };
};

如果还是有点不明白bind,看这篇!js 简单实现 bind 函数 思路清晰,从简单的实现一步一步地完善代码,咱就是说这种整理思路的方式一整个爱住了。

终极版

其实学到这儿,已经可以完结❀撒花。但发现一个问题,既然返回的是一个函数,这个函数除了自调用,我们把它作为构造函数(也就是说使用new来调用)可以吗,按常理来说是可以的。原生 bind 函数也是可以实现的。

但此时的myBind函数创建的新函数并不能当做构造函数使用,为了解决此问题,我们需要将myBind函数中的prototype指向目标函数的prototype,constructor也需要指向目标函数(也就是手写一个继承)

// 我就直接在完整版的基础上做修改了
// bind 函数实现—终极版
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  let args = [...arguments].slice(1)
  let fn = this
  
  let Fn = function Fn() {
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    )
  // 将myBind函数中的prototype指向目标函数的prototype,constructor也需要指向目标函数(也就是手写一个继承
  Fn.prototype = Object.create(fn.prototype)
  Fn.prototype.constructor = fn
  return Fn
  }
}

参考资料

CUGGZ—高频前端面试题汇总之手写代码篇

你可能感兴趣的:(手写代码,前端,javascript)