美文网首页
JS模拟实现函数call、apply、bind方法 argume

JS模拟实现函数call、apply、bind方法 argume

作者: 咸鱼不咸_123 | 来源:发表于2022-04-22 11:15 被阅读0次

    1. 用js(模拟)实现apply、call、bind

    因为它原生是用c++

    接下来外面来实现一下apply、call、bind函数:

    • 注意:我们的实现是练习函数、this、调用关系,不会过度考虑一些边界情况

    1.1 实现call

    1.1.1 在Function的原型上定义hycall

    在Function的原型上添加一个属性hycall,这样通过声明函数或者使用new Function的形式创建的函数都有这个方法

    通过函数实例.方法名(),这个方法内部绑定的this是指向这个函数实例的(函数也属于是一个对象,隐式绑定的一种)。所以我们只要用一个变量fn在hycall方法内部接受this。然后通过fn()来调用这个函数。

    //给所有的函数添加一个hycall的方法
    Function.prototype.hycall=function(){
      var fn=this;
      fn()
    }
    
    function foo(){
      console.log("foo函数被执行");
    }
    
    
    function sum(){
      console.log("sum函数被调用了");
    }
    foo.hycall()
    sum.hycall()
    
    
    
    1.1.2 在hycall尝试绑定改变函数内部的this
    • 基本数据类型是不能直接在本身添加属性和方法的,所以需要将其转化为对象类型。使用Object(参数)(基本数据类型本身可以添加属性,但是这添加属性期间,会先将基本数据类型转化为临时的包装类,然后在临时的包装类添加属性,执行当前语句后,临时包装类就会被销毁。所以会在后期产生一个错觉,误以为基本数据类型是可以添加属性的,但是如果你去用基本数据类型去.的时候,发现会报错)
    • 但是由于使用Object(null)、Object(undefined)最后都会转化为空对象,而实际函数.call传入null或undefined,是要将this指向window(全局对象)
    • 所以需要先判断是否为真(不是null、undefined),为真,则使用Object(thisArg),为假则赋值为window
    //给所有的函数添加一个hycall的方法
    Function.prototype.hycall=function(thisArg){
      var fn=this;
      //null、undefined要指向全局对象
      thisArg=thisArg?Object(thisArg):window
      // 在这个对象上定义一个新属性指向这个函数
      // 然后通过这个对象调用这个函数,这个函数this会隐式绑定到这个对象上
      thisArg.fn=fn;
      thisArg.fn()
    }
    
    function foo(){
      console.log("foo函数被执行",this);
    }
    
    
    function sum(){
      console.log("sum函数被调用了",this);
    }
    foo.hycall("aaa")
    sum.hycall(null)
    
    1.1.3 实现参数列表
    1.1.3.1 剩余参数 (...参数名)

    在hycall的函数的第二个参数使用的是ES6的剩余参数

    语法

    ...参数名 
    

    这个参数是一个数组,将后面传过来的、多余的全都放入到这个数组中

    function sum(...nums){
      console.log(nums);
    }
    sum(1) //[ 1 ]
    sum(1,2) // [ 1, 2 ]
    sum(1,2,3) //[ 1, 2, 3 ]
    sum(1,2,3,4)  // [ 1, 2, 3, 4 ]
    
    1.1.3.2 展开运算符 (...变量名)
    var a=[1,2,3,4];
    console.log(...a); //1 2 3 4
    
    1.1.3.3 实现参数列表
    //给所有的函数添加一个hycall的方法
    Function.prototype.hycall=function(thisArg,...args){
      var fn=this;
      //null、undefined要指向全局对象
      thisArg=thisArg?Object(thisArg):window
      // 在这个对象上定义一个新属性指向这个函数
      // 然后通过这个对象调用这个函数,这个函数this会隐式绑定到这个对象上
      thisArg.fn=fn;
      var result=thisArg.fn(...args)
      delete thisArg.fn;
      return result;
    }
    
    function foo(){
      console.log("foo函数被执行",this);
    }
    
    
    function sum(num1,num2){
      console.log("sum函数被调用了",this,num1,num2);
      return num1+num2;
    }
    foo.hycall("aaa")
    console.log(sum.hycall('aa',10,20));
    
    
    

    1.2 实现apply

    1.2.1 在Function.prototype定义hyapply

    在Function.prototype定义的属性或方法,后面声明函数或使用new关键字创建函数时,创建的实例也会有这个属性和方法。

    Function.prototype.hyapply=function(){
    
    }
    function sum(num1,num2){
      console.log("sum",this,num1,num2);
      return num1+num2;
    }
    
    // * 系统调用
    // sum.apply("aa",[10,20])
    
    1.2.2 进行this的绑定
    Function.prototype.hyapply=function(thisArg){
      var fn=this;
      thisArg=thisArg?Object(thisArg):window;
      thisArg.fn=fn;
      thisArg.fn()
    
    }
    function sum(num1,num2){
      console.log("sum",this,num1,num2);
      return num1+num2;
    }
    
    // * 系统调用
    // sum.apply("aa",[10,20])
    sum.hyapply("aaa")
    
    1.2.3 实现数组参数
    Function.prototype.hyapply=function(thisArg,args){
      var fn=this;
      thisArg=thisArg?Object(thisArg):window;
      thisArg.fn=fn;
      var result=thisArg.fn(...args)
      delete thisArg.fn;
      return result;
    }
    function sum(num1,num2){
      console.log("sum",this,num1,num2);
      return num1+num2;
    }
    
    // * 系统调用
    // sum.apply("aa",[10,20])
    console.log(sum.hyapply("aa",[10,20]));
    
    • 这里还是存在一些问题,比如说 我们没有传第二个参数,再去使用展开运算符,可能会报错
    1.2.3.1 解决办法1 (设置默认值)
    Function.prototype.hyapply=function(thisArg,args=[]){
      var fn=this;
      thisArg=thisArg?Object(thisArg):window;
      thisArg.fn=fn;
      var result=thisArg.fn(...args)
      delete thisArg.fn;
      return result;
    }
    function sum(num1,num2){
      console.log("sum",this,num1,num2);
      return num1+num2;
    }
    
    // * 系统调用
    // sum.apply("aa",[10,20])
    console.log(sum.hyapply("aa",[10,20]));
    
    function bar(){
      console.log("调用了bar函数");
    }
    bar.hyapply()
    
    1.2.3.2 解决办法2 (if-else)
    Function.prototype.hyapply=function(thisArg,args){
      var fn=this;
      thisArg=thisArg?Object(thisArg):window;
      thisArg.fn=fn;
      var result;
      if(!args){
        result=thisArg.fn();
      }else{
        result=thisArg.fn(...args)
      }
      delete thisArg.fn;
      return result;
    }
    function sum(num1,num2){
      console.log("sum",this,num1,num2);
      return num1+num2;
    }
    
    // * 系统调用
    // sum.apply("aa",[10,20])
    console.log(sum.hyapply("aa",[10,20]));
    
    function bar(){
      console.log("调用了bar函数");
    }
    bar.hyapply()
    
    1.2.3.3 解决方法3 (三元表达式)

    args=args?args:[];

    Function.prototype.hyapply=function(thisArg,args){
      var fn=this;
      thisArg=thisArg?Object(thisArg):window;
      thisArg.fn=fn;
      args=args?args:[];
      var result=thisArg.fn(...args)
      delete thisArg.fn;
      return result;
    }
    function sum(num1,num2){
      console.log("sum",this,num1,num2);
      return num1+num2;
    }
    
    // * 系统调用
    // sum.apply("aa",[10,20])
    console.log(sum.hyapply("aa",[10,20]));
    
    function bar(){
      console.log("调用了bar函数");
    }
    bar.hyapply()
    
    1.2.3.4 解决办法4 (逻辑或)
    Function.prototype.hyapply=function(thisArg,args){
      var fn=this;
      thisArg=thisArg?Object(thisArg):window;
      thisArg.fn=fn;
      args=args|| [];
      var result=thisArg.fn(...args)
      delete thisArg.fn;
      return result;
    }
    function sum(num1,num2){
      console.log("sum",this,num1,num2);
      return num1+num2;
    }
    
    // * 系统调用
    // sum.apply("aa",[10,20])
    console.log(sum.hyapply("aa",[10,20]));
    
    function bar(){
      console.log("调用了bar函数");
    }
    bar.hyapply()
    

    1.3 实现call和apply方法的修改

    • 前面的实现存在两个问题 :0、"",this也会指向window,这是不对的,所以需要进行修改
    1.3.1 call方法的修改
    //给所有的函数添加一个hycall的方法
    Function.prototype.hycall=function(thisArg,...args){
      var fn=this;
      //null、undefined要指向全局对象
      thisArg=thisArg!==null&&thisArg!==undefined?Object(thisArg):window
      // 在这个对象上定义一个新属性指向这个函数
      // 然后通过这个对象调用这个函数,这个函数this会隐式绑定到这个对象上
      thisArg.fn=fn;
      var result=thisArg.fn(...args)
      delete thisArg.fn;
      return result;
    }
    
    function foo(){
      console.log("foo函数被执行",this);
    }
    
    
    function sum(num1,num2){
      console.log("sum函数被调用了",this,num1,num2);
      return num1+num2;
    }
    foo.hycall("aaa")
    console.log(sum.hycall('aa',10,20));
    
    
    
    1.3.2 apply方法的修改
    Function.prototype.hyapply=function(thisArg,args){
      var fn=this;
      thisArg=thisArg!==null&&thisArg!==undefined?Object(thisArg):window;
      thisArg.fn=fn;
      args=args|| [];
      var result=thisArg.fn(...args)
      delete thisArg.fn;
      return result;
    }
    function sum(num1,num2){
      console.log("sum",this,num1,num2);
      return num1+num2;
    }
    
    // * 系统调用
    // sum.apply("aa",[10,20])
    console.log(sum.hyapply("aa",[10,20]));
    
    function bar(){
      console.log("调用了bar函数",this);
    }
    bar.hyapply(0)
    

    1.4 实现bind

    function foo(){
      console.log("foo函数被执行了",this);
    }
    function sum(num1,num2,num3,num4){
      console.log(num1,num2,num3,num4);
    }
    // * 系统调用
    // var bar=foo.bind("aaa")
    // bar()
    
    //* bind的时候,传入参数
    // var newSum=sum.bind("bbb",10,20,30,40);
    // newSum()
    
    // * bind的时候 不传入参数
    // var newSum=sum.bind("ccc");
    // newSum(50,60,70,80);
    
    //* bind的时候传入部分参数,在调用的时候,传入剩余参数
    var newSum=sum.bind("ddd",10);
    newSum(70,90,100)
    
    1.4.1 在Function.prototype添加一个属性为hybind赋值为一个函数
    Function.prototype.hybind=function(){
     
     
    }
    function foo(){
      console.log("foo函数被执行了",this);
    }
    function sum(num1,num2,num3,num4){
      console.log(num1,num2,num3,num4);
    }
    
    
    1.4.2 绑定this并返回一个新的函数
    Function.prototype.hybind=function(thisArg){
      var fn=this;
      return function proxyFn(){
        thisArg=(thisArg!==null&&thisArg!==undefined)?Object(thisArg):window
        thisArg.fn=fn;
        var result=thisArg.fn();
        delete thisArg.fn;
        return result;
      }
     
    }
    function foo(){
      console.log("foo函数被执行了",this);
    }
    function sum(num1,num2,num3,num4){
      console.log(num1,num2,num3,num4);
    }
    
    
    1.4.3 实现参数
    • 在实现绑定的时候可能需要传入参数,在调用的时候可能也会传入参数
    • 所以要对两个参数,最后进行调用的时候,需要参数合并
    Function.prototype.hybind=function(thisArg,...args){
      var fn=this;
      return function proxyFn(...args2){
        thisArg=(thisArg!==null&&thisArg!==undefined)?Object(thisArg):window
        thisArg.fn=fn;
        var result=thisArg.fn(...args,...args2);
        delete thisArg.fn;
        return result;
      }
     
    }
    function foo(){
      console.log("foo函数被执行了",this);
    }
    function sum(num1,num2,num3,num4){
      console.log(num1,num2,num3,num4);
    }
    

    2. arguments

    arguments是一个对应于 传递给函数的参数类数组(array-like) 对象

    它会将传递给函数的所有参数放在一个类数组中(但实际上是一个对象),它是放在AO对象中的

    • array-like表示像一个数组,但实际上是一个对象
    • 但是它拥有数组的一些特性,例如:length,比如可以通过索引来获取
    • 但是却没有数组的一些方法,例如forEach、map

    2.1 arguments的三个常见操作

    2.1.1 获取参数个数
    arguments.length
    
    2.1.2 通过索引获取指定的参数
    arguments[0]
    arguments[1]
    
    2.1.3 通过callee获取arguments所在的函数
    arguments.callee
    

    2.2 arguments参数遍历

    2.2.1 自身遍历

    arguments自己遍历

    function foo(num1,num2){
      let arr=[];
      console.log(arguments);
      for(let i=0;i<arguments.length;i++){
        arr.push(arguments[i]*10)
      }
      console.log(arr);
    }
    foo(1,2,3,5,4,56)
    
    2.2.2 通过Array.slice将arguments转化为数组
     //*通过Array.slice将 arguments转化为数组
      var newArr=Array.prototype.slice.call(arguments);
      var newArr2=[].slice.call(arguments)
      console.log(newArr);
      console.log(newArr2);
    
    2.2.3 通过Array.from和展开运算符将arguments转化为数组
     var newArr3=Array.from(arguments);
      var newArr4=[...arguments]
      console.log("newArr3:",newArr3);
      console.log("newArr4:",newArr4);
    }
    

    2.3 实现Array.slice数组

    可能存在一些没有考虑到的问题

    Array.prototype.hyslice=function(start,end){
      let newArray=[];
      var arr=this;
      for(let i=start;i<end;i++){
        newArray.push(arr[i])
      }
      return newArray;
    }
    var newArr=Array.prototype.hyslice.call(["111","222","333"],0,2)
    console.log(newArr);
    
    
    2.3.1 优化1
    Array.prototype.hyslice=function(start,end){
      let newArray=[];
      var arr=this;
      start=start?start:0;
      end=end?end:arr.length;
    
      for(let i=start;i<end;i++){
        newArray.push(arr[i])
      }
      return newArray;
    }
    
    var newArr=Array.prototype.hyslice.call(["111","222","333"])
    console.log(newArr);
    

    2.4 箭头函数没有arguments

    箭头函数没有arguments,它会上层作用域去找,如果还是没找到,会一直往上层作用域找,直到全局作用域,如果全局作用域还是没找到,会抛出一个错误。

    • 全局环境window没有arguments,全局环境node有arguments
    • 其实不太建议arguments,推荐使用剩余参数
    function foo(){
      var bar=()=>{
        console.log(arguments);
      }
      return bar;
    }
    var fn=foo("123");
    fn()
    
    var name="abc";
    var foo=()=>{
      console.log(arguments); //会去上层作用域找arguments
      //* 全局环境window没有arguments、全局node环境下有arguments
    }
    foo()
    

    3.总结

    实现函数call、apply、bind.png

    相关文章

      网友评论

          本文标题:JS模拟实现函数call、apply、bind方法 argume

          本文链接:https://www.haomeiwen.com/subject/mnagertx.html