js中的call、apply和bind函数实现的原理

有空的时候都会看一下掘金社区,里面的文章都很棒,也学到了很多,其实我写博客主要是让自己记个笔记,俗话说好记性不如个烂笔头,有时候会忘记,所以遇到解决问题的方法我都会记载下来,都归于项目总结这一类了。慢慢我发现我写的东西还能帮助到更多的人,这更让我开心了,同时我也告诉自己要更加严谨了,可不能误人子弟。
好了,刚刚有感而发,说了那么多开场白,下面进入正题吧。

1.我们先看一下call函数实现的方法

 Function.prototype.realizeCall = function (context) {
      if (typeof this !== 'function') {
        throw new TypeError('not funciton')
      }
      context = context || window
      context.fn = this
      let arg = [...arguments].slice(1)
      let result = context.fn(...arg)
      delete context.fn
      return result
    } 

这就是call()方法实现的原理,不过这个写法中用到了es6中的一些语法。那让我们具体来分析一下吧。
首先我们先对这个函数做一下改造,加几个console.log(),打印出我们疑惑的地方。

 Function.prototype.realizeCall = function (context) {
      // 先判断一下this是不是一个函数,这个this下面会打印出来
      if (typeof this !== 'function') {
        throw new TypeError('not funciton')
      }
      console.log(1,this);  // 1  看一下this打印出来的结果
      console.log(2,context);  //  2  同时打印出context,看一下context的值
      console.log(3,arguments);  // 3 看一下传入进来的参数
      context = context || window
      context.fn = this
      console.log(4,context); // 4  这个时候我们看一下context的值并且和2进行对比一下
      console.log(5,context.fn); // 5  看一下context.fn的值
      console.log(6,this);  // 6  打印出这个时候的this值和1进行对比一下
      let arg = [...arguments].slice(1)
      console.log(7,arguments);  // 7 看一下传入进来的参数并且和3对比一下
      console.log(8,arg);  //  看一下截取之后的参数
      let result = context.fn(...arg)
      delete context.fn
      return result
    } 
    我们先打印这八个看一下,执行函数如下:
    
    Math.max.call(null,1,2,3,4,5)
    // 5
    
    Math.max.realizeCall(null,1,2,3,4,5)
    // 1 ƒ max() { [native code] }   ===可以看到到这是this指向的是Math.math()
    // 2 null      ===这个时候context是传入的null
    // 3 Arguments(6) [null, 1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]  ===这个时候arguments也是传入的参数
    // 4 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}  ===经过转化之后context变成了默认的window,这是因为我们传入的是null,所以context || window得到是window
    // 5 ƒ max() { [native code] }   === 把this的值赋值给我们自己定义的context.fn方法(其实这个时候this可以看做一个方法)
    // 6 ƒ max() { [native code] }  === 和1对比可以看到this的值没有变化
    // 7 Arguments(6) [Window, 1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]   ==== 和3对比我们发现arguments的值有变化,context的值由null变成了window,这是因为在方法内部context = context || window,所以context改变了
    // 8 (5) [1, 2, 3, 4, 5]  === 最后截取的值,也是我们的参数值
    // 5

从以上两种执行的结果可以看到,结果都是一样的,说明我们封装的这个方法是有效的。
其实实现这种方法的本质是 => 将要改变this指向的方法挂到目标this上执行并返回
从上面封装的方法来说 => 也就是把this赋值给context.fn 然后截取相应的参数,放在context.fn方法里执行,在把执行后的结果返回,而且把comtext.fn这个方法再删除掉,这就是call()方法执行的原理。

2、我们再看一下apply方法的实现原理吧

其实实现方法和call类似,只是call和apply传入参数的方式不一样,所以实现原理也就是处理参数的方式不一样。
我们先来看一下call和apply的区别:

 Math.max.call(null,1,2,3,4,5,6,7,8,9)
    // 9
    let arr = [1,2,3,4,5,6,7,8,9]
    Math.max.apply(null,arr)
    // 9

可以看到传入的参数方式不一样,那让我们来看下apply的实现原理吧

Function.prototype.realizeApply = function(context){
   if(typeof this !='function'){
     throw new TypeError('not function')
   }
   context = context || window;
   context.fn = this;
   let result;
   if(arguments[1]){
      result = context.fn(...arguments[1])
   }else{
      result = context.fn()
   }
   delete context.fn;
   return result;
}
let arr = [9,8,8,7,4,2,16,5]
Math.max.realizeApply(null,arr); // 16
Math.min.realizeApply(null,arr);  // 2

可以看到apply的实现原理和call的实现原理很相似,只是因为传入参数方式不一样而已。

3、bind方法实现的原理

     Function.prototype.realizeBind = function (context) {
      if(typeof this !='function'){
        throw new TypeError('not function')
      }
      context = context || window
      context.fn = this;
      let args = [...arguments].slice(1)
      let fn = function () {
        let fnArgs = [...arguments] // 新:参数
        // bind 函数的参数 + 延迟函数的参数
        // 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
        context.fn.apply(this instanceof context.fn ? this : context, args.concat(fnArgs))
      }
      fn.prototype = Object.create(context.fn.prototype) // 维护原型
      delete context.fn;
      return fn
    }

最后非常感谢这篇文章的启发:20道JS原理题助你面试一臂之力!

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值