欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JS call apply bind原理与实现和函数柯理化总结

程序员文章站 2022-03-03 12:12:06
以下方法来自于各种资料大厂面试题收集。添加了一些个人理解的注释和解释。对下面概念熟悉的小可爱,不喜勿喷。侵删本人脑回路略慢,写的是我认为的比较详细的版本call,apply,bind的区别相同点:能够改变this指向不同点:call apply在使用的时候会触发运行一次,而bind只是绑定了函数,不会立即执行call、apply因为要立即执行函数,所以第二个参数或之后的参数都是当前的真实参数,bind是“预设参数”call 与 apply 传参不同:call可以多参数;apply 是一个...

以下方法来自于各种资料大厂面试题收集。
添加了一些个人理解的注释和解释。
对下面概念熟悉的小可爱,不喜勿喷。
侵删

本人脑回路略慢,写的是我认为的比较详细的版本

call,apply,bind的区别

相同点:能够改变this指向
不同点:
call apply在使用的时候会触发运行一次,而bind只是绑定了函数,不会立即执行
call、apply因为要立即执行函数,所以第二个参数或之后的参数都是当前的真实参数,bind是“预设参数”
call 与 apply 传参不同:call可以多参数;apply 是一个arguments

    const obj = {
      name:'obj name'
    }
    function getName(para1,para2){
      return this.name +'-'+ para1 +'-' +para2;
    }

    /*
    call  apply  bind 使用
    */
    console.log(getName.call(obj,'from','call'));
    console.log(getName.apply(obj,['from','apply']));
    var b = getName.bind(obj)
    console.log(b('from','bind'));

call,apply,bind的实现

手动实现call()

call使用

先看一下call的使用

    const obj = {
      name:'obj name'
    }
    function getName(){
      return this.name;
    }
    console.log(getName.call(obj)); //obj name

思考:

1.如何调用getName.func?
2.除了obj.name还能如何取到 name= 'obj name'

这里废话一下:
说我买了一台冰箱,我想试试冰箱的制冷能力,应该如何验证?
很简单,我只需要把大象放冰箱里一段时间,拿出来看一下大象结冰了没有就可以了

思考解答:

1.getName是一个function ,getName的构造函数是Function 。
验证:getName.__ proto__ == Function.prototype
在控制台看到 Function.call,就是说call 是构造函数给每个函数的标配。我们可以在			
Function的原型上给一个mycall我们自己的mycall
所以 看到了 **Function.prototype.mycall= funciton(){}**
后面我们可以这样调用  getName.mycall()
2.调用obj里面的getName方法,如下:
var obj = {
	name:"obj name",
	getName:function(){
		return this.name;
	}
}

console.log(obj.getName())

看了上面 我们只需要把这两步整合下。把方法放到obj里面一下取到值就可以啦。

  const obj = {
      name:'obj name'
    }
    function getName(){
      return this.name;
    }
    Function.prototype.mycall= function(obj,){//(1)原型上绑定自己的mycall
      //this 指向了调用mycal 的函数  函数原型上有name length属性
      
      const fnName = this.name		//(2)getName
      obj[fnName] = this;  			//(3)this指向了调用mycall的对象 即getName
      const result = obj[fnName]();     //(4)运行obj的getName方法  
      delete obj[fnName]; 			// (5) 删除obj中的 getName方法 使得obj不受影响
      return result;			// (6) 返回 this.name
    }
    console.log(getName.mycall(obj));

思路:把大象放进冰箱

(1)Function原型上绑定自己的mycall
(2)this指向了调用mycall的对象 即getName 可以获得func 以及func名(getName)
(3)把func放进obj
(4)调用一次 把结果存起来
(5) 不可以让这个obj[func] 一直存在 delete 删除对象属性
(6)返回运行结果
你品,你细品 3,4,5 是不是把大象放进冰箱 又拿了出来,大象身上的结晶就是我们要的结果, 是不是感觉 到这里就变成了 很简单的 一步两步三步
底层实现也没那么高大上 okok,我真是个话痨,

实现传参
上面是没有传参数的情况,我们需要改进下
最终代码

    const obj = {
      name:'obj name'
    }
    function getName(para1,para2){
      return this.name +'-'+ para1 +'-' +para2;
    }
    //(1)原型上绑定自己的mycall
    Function.prototype.mycall= function(obj,para1,para2){
      //this 指向了调用mycal 的函数  函数原型上有name length属性
      
      let fnName = this.name		//(2)getName
      obj[fnName] = this;  			//(3)this指向了调用mycall的对象 即getName
      const result = obj[fnName](para1,para2);  //(4)运行obj的getName方法  
      delete obj[fnName]; 			// (5) 删除obj中的 getName方法 使得obj不受影响
      return result;			// (6) 返回 this.name
    }
    console.log(getName.mycall(obj,'from','mycall'));

ok 写完了call,那么apply当然就只是传参变成一个arguments啦 不多赘述。

手动实现bind

由于bind 是先绑定。可以随时调用随时取,封装上有一点区别

    
    const obj = {
      name:'obj name'
    }
    function getName(para1,para2){
      return this.name +'-'+ para1 +'-' +para2;
    }
    const bindFn = getName.bind(obj)
    console.log(bindFn('from','bind')

	

思考:

这里提一句,封装的时候,先考虑使用方式,
能够随时访问到别的函数内部变量的函数,是什么?
没错是闭包

//第一种
Function.prototype.mybind(obj,para1,para2){
	const _this = this;
	function inner(para1,para2){
		return _this.call(obj)
		
	}
	inner.prototype = Object.create(_this.prototype) //修正原型链
	return inner;
}
//第二种
Function.prototype.mybind = function(obj,para1,para2){
     const _this = this;
      function inner(para1,para2){  //这是一个闭包  obj在bind的时候 绑定到了inner
        // return _this.call(obj)
        //等同于 上面call的实现原理
        let fnName = _this.name
        obj[fnName] = _this;  			
        const result = obj[fnName](para1,para2);  
        delete obj[fnName]; 			
        return result;			
      }
      inner.prototype = Object.create(_this.prototype) //修正原型链
      return inner;
   }
    let mybindFn = getName.bind(obj)
    console.log(mybindFn('from','bind')

注意:如果不修正原型链的话 mybindFn 指向了func inner

最终版本

okok,封装完成,整理代码

<script>
    
    const obj = {
      name:'obj name'
    }
    function getName(para1,para2){
      return this.name +'-'+ para1 +'-' +para2;
    }

    /*
    call  apply  bind 使用
    */
    console.log(getName.call(obj,'from','call'));
    console.log(getName.apply(obj,['from','apply']));
    var b = getName.bind(obj)
    console.log(b('from','bind'));

    /*
      mycall  
    */
    //(1)原型上绑定自己的mycall
    Function.prototype.mycall= function(obj,para1,para2){
      //this 指向了调用mycal 的函数  函数原型上有name length属性
      let fnName = this.name		//(2)getName
      obj[fnName] = this;  			//(3)this指向了调用mycall的对象 即getName
      const result = obj[fnName](para1,para2);  //(4)运行obj的getName方法  
      delete obj[fnName]; 			// (5) 删除obj中的 getName方法 使得obj不受影响
      return result;			// (6) 返回 this.name
    }
    console.log(getName.mycall(obj,'from','mycall'));

    /*
      myapply
    */
    Function.prototype.myapply = function(obj,arguments){
      let fnName = this.name;
      obj[fnName] = this;
      const result = obj[fnName](arguments)
      delete obj[fnName];
      return result;
    }
    console.log(getName.mycall(obj,'from','myapply'));
    /*
      mybind
    */
   Function.prototype.mybind = function(obj,para1,para2){
     const _this = this;
      function inner(para1,para2){  //这是一个闭包  obj在bind的时候 绑定到了inner
        // return _this.call(obj)
        //等同于 上面call的实现原理
        let fnName = _this.name
        obj[fnName] = _this;  			
        const result = obj[fnName](para1,para2);  
        delete obj[fnName]; 			
        return result;			
      }
      inner.prototype = Object.create(_this.prototype) //修正原型链
      return inner;
   }
   let bindFn = getName.mybind(obj)
   console.log(bindFn('from','mybind'));
   bindFn = getName.bind({name:'new name'})
   console.log(bindFn('from','mybind'));

  </script>

函数柯理化

柯理化

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术

个人理解:

sum(1,2,3) <=> currySum(1)(2)(3)

看到这个表达式第一印象是

初级return 嵌套

function sum(a,b,c){
	return (a + b + c)
}

function currySum(a,b,c){
	return function(b){
		return function(c){
			return a+b+c
		}
	}
}

递归版本

上面这种方式 太low了,我们来换一种实现,思考一下哈,如果我们有n个参数[…a,b,c,d,e]等等。我们就要无限的return里面return 函数。这个时候用到了什么???没错,假装听到你的回答,递归。看下面

 let add = (a,b,c) => a + b + c
 function curry(fn,args){
   let _this = this;
   let _args = args || [];
   let len = fn.length;
   return function(){
     let args = Array.prototype.slice.apply(arguments) //伪数组转为真数组
     args = Array.prototype.concat.call(_args,args)//合并数组
     if(args.length < len){
       // return curry(fn,args) // 直接调用自己 也可以
       return curry.call(_this,fn,args) //call可以传递 多个参数
     }
     // console.log(args); // 整合了 所有传参 在args
     return fn.apply(this,args)  //注意这里 没有用call,为什么???
     //因为需要传递一个数组吖
   }
 }
 let currySum = curry(add)
 console.log(currySum(1)(2)(3));
    
    

这样的话呢,我们就可以写成currySum(1)(2)(3) 这种形式

存在问题

以下方式是无法输出的

currySum(1)
currySum(1,2)(3)

最终版本

代码

function add() {
        // 第一次执行时,定义一个数组专门用来存储所有的参数
        var _args = Array.prototype.slice.call(arguments);

        // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
        var _adder = function () {
          _args.push(...arguments);
          console.log('111');
          return _adder;
        };
        // console.log(_adder);

        // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
        _adder.toString = function () {
          console.log('222');  //在浏览器输出 会打印两次
          return _args.reduce(function (a, b) {
            return a + b;
          });
        };
        return _adder;
      }

      console.log(add(1)(2)(3) instanceof Function);
      console.log(add(1)(2)(3));
      console.log(add(1, 2, 3)(4));
      console.log(add(1)(2)(3,4)(4)(5,6));
      console.log(add(1)(2)(3,4)(4)(5,6).toString());//解释

这样就可以无限输出了
代码是赋值来的 链接在文末
解释:
toString是挂载在 func _adder下面的属性,可以在func的constructor下面查看到,还有func自带的length,name等属性。所以如果不加toString的话 得到的是 f num,toString之后得到方法的返回值。
关于 222 会输出两次的问题,个人用node 运行js 是没有被输出的,浏览器打开会被浏览器运行打印两次。这个地方小作者我也不是很懂,希望有幸被大神读到,指点一下,不胜感激。

柯理化应用

这里没有个人原创,来自各种大佬

函数复用

比较常见的 正则复用
柯理化前

      function reg(reg,str){
        return reg.test(str)
      }
      console.log(reg(/\d+/g,'kk'));
      console.log(reg(/[a-z]+/g,'kk'));

柯理化后

      //柯理化函数
      function curryReg(reg){
        return function(str){
          return reg.test(str)
        }
      }
      let cRegNumber = curryReg(/[0-9]+/g)
      let cRegString = curryReg(/[a-z]+/g)
      console.log(cRegNumber(1314));
      console.log(cRegString('kk'))

总结
项目中可以把这些可复用的函数封装出来,需要用到的页面引入使用即可,这样的好处是:你不用每次都传入一次正则,在公共的地方规定一次就好,对于后期的维护也非常方便

提前确认

由于浏览器的兼容性,一些操作可能需要不同的初始化api

var on = (function(){
        if(document.addEventListener){
          return function(element,event,handler){
            if(element && event && handler){
              element.addEventListener(event,handler,false)
            }
          }
        }else{
          return function(element,event,handler){
            if(element && event&&handler){
              element.attachEvent('on'+event,handler)
            }
          }
        }
      })()
      const btn = document.getElementById('btn')
      on(btn,'click',function(){  //调用
        alert('PianistK')
      })

延迟运行

说到 延迟 一定能想到 bind 只是预先绑定,这里有些柯理化的意思

Function.prototype.mybind = function(obj,para1,para2){
     const _this = this;
      function inner(para1,para2){  //这是一个闭包  obj在bind的时候 绑定到了inner
        // return _this.call(obj)
        //等同于 上面call的实现原理
        let fnName = _this.name
        obj[fnName] = _this;  			
        const result = obj[fnName](para1,para2);  
        delete obj[fnName]; 			
        return result;			
      }
      inner.prototype = Object.create(_this.prototype) //修正原型链
      return inner;
   }
    let mybindFn = getName.bind(obj)
    console.log(mybindFn('from','bind')

参考资料

链接: 函数柯理化.

全力以赴PianistK

本文地址:https://blog.csdn.net/lemaktub/article/details/107360072

相关标签: js