关于JS中call,apply,bind的理解

173 阅读5分钟

前言:

任何存在的东西,都有一定的目的,对于程序而言,就是解决具体的问题。call,apply,bind是改变this 的指向的,但是为什么需要改变呢?

1.为什么会有this的存在

    优雅地、准确地指向当前代码运行时所处的上下文环境,便产生了this. 

2.this的指向

   this是由执行的上下文环境决定的,箭头函数除外

     1\. 普通函数调用 指向 window

     2.严格的函数体里面,指向的是一个 undefined.这个时候,调用和this的指向没有关系。

     3.作为对象方法的话,指向本身。

     4.如果this隐式丢失,会指向最近的调用this。

     5.call apply bind 会修正this 的指向,传入unll,undefined 指向window

    6.构造函数 new 操作符 ,this指向它本身。

    7.当是箭头函数的时候,会指向定义时运行环境。

3.call,apply,bind的产生

      call、apply、bind都是和this指向有关的,为什么和需要改变this呢?

     假如A有个刀子可以削苹果,B有个苹果,B如果想用刀子削苹果,就要买个刀子,如果B能借用A的刀子,就可以不用买刀子这件事情。在程序中,就能达到功能复用的逻辑。

4.举个例子理解

      假设有个类数组对象,它想使用数组方法,把数组方法指向处理的数据的指向,就能复用逻辑。简单理解就是,A把对象给B,B处理数据指向A,返回的处理结果就是A的。

let arrLike = { 0:"0-1",1:"0-2",length:2 };
let result = Array.prototype.slice.call(arrLike); 
// 先把数据截取出来,就能变成数组 ["0-1", "0-2"]
Array.prototype.push.call(arrLike,'0-3');
// 借用push方法,arrLike =  {0: "0-1", 1: "0-2", 2: "0-3", length: 3}

5.使用场景

  一句话:借用方法

  1.只用函数能用。

  2.参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply。

  3.考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply。

  4.参数集合已经是一个数组的情况,用apply。

6.实现一个call方法

  在实现一个call 的前面,先看个小例子:

function B () {console.log(this.name) };
let testO = {name:"12",getName:B};

testO.getName() // 12,这里看到,作为一个对象的方法调用,this指向这个对象。

  前面已经说过这个结论,这里利用这个特性,实现call.

6.1目的:

     改变this的指向,为什么要改变,因为我本身这个对象没有对应的方法处理这个事情,我要借用别人的方法执行这个事情。

6.2 分析要的结果

/**
 * 1.传入一个对象,改变this的指向
 * 2.可能传入参数,也可能不传入
 * 3.执行返回结果
** 综上可以看到:问题在于this的指向实现
*/

6.3思路:来源call的实现标准

/**
 * 1.设置一个 thisArg 也就是this的指向
 * 2.将thisArg 封装成一个Object
 * 3.通过thisArg 创建一个临时的方法,这样thisArg就能调用自己的方法了,
   该临时的方法 this会指向到thisArg
* 4.执行thisArg的临时方法,并传递方法
* 5.删除临时方法,返回方法的执行结果
**/

6.4 代码实现

/**
* 1.在函数的原型上定义一个方法myCall ,因为要保证任意的函数都能调用。
  Function.prototype.myCall = function() {}
** 2.myCall 接受两个参数,一个要改变this的指向,另一个可能存在的参数 
  function(thisArg,...arr) {}
** 3\. 如果thisArg是null undefined 要指向 window,不是指向4
** 4\. 创建一个对象,thisArg 指向这个对象
** 5.把当前mycall的this作为这个对象的属性方法调用,那这个this 就指向了对象本身,也就是thisArg
** 6\. 删除临时方法
** 7.返回结果*/

6.5 具体的代码

 Function.prototype.myCall = function (thisArg,...arr) { // 1,2

  if(thisArg === null || thisArg ===undefined) { // 3  
     thisArg = window;  
  } else {   
     thisArg = Object(thisArg); // 4  
  }  
  const speMethod = Symbol('anything');  // 创建一个不常用的常量 
  thisArg[speMethod] = this; //5 把mycall 函数里面的this给thisArg 的一个属性方法调用。  
  let reault = thisArg[speMethod](...arr); 
  delete thisArg[speMethod]; //6删除此临时方法 
  return reault;//7 
}

7.apply 的实现

实现基本和上面是一样的,之时apply接受的是一个类数组和数组。

 // 实现apply ,思路和上面一样,就是参数处理不一样. 
 Function.prototype.myApply = function (thisArg) { 
  if(thisArg === undefined || thisArg === undefined) { 
   thisArg = window; 
  } else {   
    thisArg = Object(thisArg);
 }  
 const applyFunkey = Symbol('anything'); 
 thisArg[applyFunkey] = this;  
 let result ;  
 // 参数的处理  
 let params = arguments[1]; 
 if(params) { // 参数存在的情况下   
   if(params instanceof Object) {     
     result= thisArg[applyFunkey](...params);   
    } else {     
    throw new TypeError('第二个参数为数组,或者类数组');   
  }  
 } else {  
   result = thisArg[applyFunkey]();  
 }  
 delete thisArg[applyFunkey]; 
 return result; 
}

8.bind 的实现

8.1分析

 /**
  * 1.传入一个函数,返回一个代执行的函数 ,利用闭包。return function() {}
  ** 2.作用域绑定 ,通过 call,apply
  ** 3.支持二次参数传递。
  ** 4.新返回的函数可以通过new调用。返回的函数需要继承原函数的原型链方法
  ** 5\. 返回一个新函数
**/

8.2 代码实现

 Function.prototype.myBind = function (objThis,...firstParms) {  
  let  thisFn = this; // 存储函数调用,以及 函数参数firstParms   // 1 
   // 返回一个函数  
   let  funcForBind  = function (...secParams) { 

   // 判断是否通过new 调用   
   const isNewCall = this instanceof funcForBind; 

  // 如果是new调用,还是this,不是的话,保证传入的是一个对象
   const thisArg = isNewCall?this:Object(objThis); 

   return thisFn.call (thisArg,...firstParms,...secParams); //2,3 
  }   
  funcForBind.prototype = Object.create(thisFn.prototype); // 4  
 return funcForBind;
 };