call,apply,bind的原理以及实现

143 阅读3分钟

Function.prototype.call()

==call()== 方法调用一个函数, 其具有一个指定的this值和分别地提供的参数。 该方法和apply()类似,区别在于,==call()可以接收若干参数==,而==apply()接收的是一个包含多个参数的数组==。

用call可以继承

  1. 通过父类的构造函数 ==call== 方法可以实现继承。

例子如下:

 function Person(name,age){
     this.name = name ;
     this.age = age;
 }
 function Man(name,age,sex){
      Person.call(this,name,age) 
      this.sex= sex;
 }
 let abc = new Man('abc',18,'男');
 console.log(abc);

解释 : abc实例都会拥有 Person 类 的name,age变量。

  1. call方法调用匿名函数,例子如下
 var animals = [
    { species: 'Lion', name: 'King' },
    { species: 'Whale', name: 'Fail' }
  ];
  
  for (var i = 0; i < animals.length; i++) {
    (function(i) {
        console.log('#' + i + ' ' + this.species + ': ' + this.name) }
    ).call(animals[i], i);
  }
  1. call方法指定上下文的this
 let person = {
     age:18,
     name:'abc'
 }
 function greet(){
     console.log(this.age,this.name,this)
     //18 "abc" {age: 18, name: "abc"}
 }
 greet.call(person);

call 原理代码实现如下

 /*
1、this 参数可以传 null 或者 undefined,此时 this 指向 window
2、this 参数可以传基本类型数据,原生的 call 会自动用 Object() 转换
3、函数是可以有返回值的
 */
 Function.prototype.myCall = function(context){
     context = context ? Object(context) : window;
     //对传入的对象 添加一个fn属性 并且fn=this this即当前函数;
     context.fn = this;
     //截取当前 arguments 参数从第二个到最后一个,
     //因为第一个参数是需要绑定的目标对象
     let args = [...arguments].slice(1);
     //执行函数 传入参数;
     let result = context.fn(...args);
     delete context.fn;//删除创建了属性
     return result //返回函数的返回值 没有即undefined
 }

Function.prototype.apply()

apply( ):两个参数,第一个是运行函数的作用域,第二个是参数数组(可以是array的实例,或者arguments对象)。 call( ):参数个数不定,第一个是运行函数的作用域,其余传递给函数的参数逐个列出。 apply()和 call()的2个作用:给函数传参、扩充作用域; 至于是使用 apply( )还是 call( ),完全取决于你采取哪种给函数传递参数的方式最方便

实现原理

  Function.prototype.myapply=function(context,arr){
      context = context ? Object(context) : window;
      context.fn = this;
      let result;
      if(!arr){
          result = context.fn();
      }else{
          result = context.fn(...arr);
      }
      delete context.fn;
      return rexult;
  }

Function.prototype.bind()

==bind==方法==创建一个新函数==, 在调用时设置this关键字为提供的值。 并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。 语法: function.bind(thisArg, [arg1[, arg2[, ...]]])

举个例子

 
let obj = {
    name: 'joker'
}

function fn() {
    console.log(this.name)
}
let bindFn = fn.bind(obj)
bindFn()
// joker

从上面例子可以看出

  1. bind可以绑定 ==this== 到传入的对象
  2. bind方法返回一个函数

那么我们使用(高阶函数)实现一个简易的bind方法

 Function.prototype.bind = function(context) {
   let _me = this
    return function() {
        return _me.apply(context)
    }
}

上面方法的缺陷 :无法解决 fn 方法的传参问题;

再举个例子 2 :如下

bind可以多次传参

 let obj ={a:1};
 function fn(name,age){
      console.log(name,age,this);
 }
 let bindFn = fn.bind(obj,'abc');
 bindFn(18) //abc 18 {a: 1}
 

上面例子解释:

  1. fn函数调用bind方法 将fn函数内部的this指到obj上,并且传入参数 ‘abc’,bind方法返回的一个新创建的函数保存到 bindFn变量 上;
  2. 执行 bindFn 是又传入了一个参数 18。
  3. 以上操作 相当于 将fn函数内部的this指到obj上,并且给fn函数传参 name=‘abc’ , age = 18;

==因此之前的函数需要改一改==

 Function.prototype.bind = function(context){
      let self = this; 
       //获取bind方法传入的参数,截取第二个到末尾;
      let bindArgs = [...arguments].slice(1); 
      return function(){
          //获取函数执行传入的参数
          let fnArgs = [...arguments].slice();
          return self.apply(context,bindArgs.concat(fnArgs))
      }
 }