【 js手写系列】实现自己的call、apply

159 阅读3分钟

努力让学习成为一种习惯,自信来源于充分的准备

如果你觉得该文章对你有帮助,欢迎大家点赞关注分享

前言

深入理解this机制中,我们知道了call、apply用于函数执行的时候显示绑定this。那么它内部的原理是怎么样的呢,接下来带你一步一步实现自己的callapply

解读

function foo() {
  console.log(this.a)
}
const obj = {a:1}
foo.call(obj) // 1

在实现之前,我们先从规范结合现象来分析下call具体有哪些行为表现

call规范如下(具体规范详情可以查看Function.prototype.call

image.png

这里简单翻译下:

  1. 让当前函数赋值给this(这里其实就是this隐式绑定, 也侧面说明,函数也是一个对象)
Function.prototype.call2 = function () {
  console.log(this)
}
function bar() {}
bar.call2() // ƒ bar() {}
  1. 如果函数不可执行,抛出异常(argumens为对象且包含内部方法[[call]]代表可执行)
  2. 执行尾调用(这里与主逻辑关系不大,不详细介绍。感兴趣可以参考函数:尾调用
  3. 执行函数(传入函数体、this引用、函数形参)

额外有两点需要注意:

1.当thisArgs传入的是null或者undefined,在非严格模式下,this会指向全局对象(即采用了默认绑定规则),如果是基本属性类型,则会转化成对象类型(装箱)

function foo() {
 console.log(this); 
}

foo.call(null) // window 严格模式下为 undefined
foo.call(1) // Number {1}

2.如果函数是箭头函数或者是被bind绑定过的函数(绑定函数是一个 Exotic Function Object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行 包装函数),则call传入的对象会被thisArgs忽略

const foo = () => {
  console.log(this); // window
}
foo.call({a:1})
var a = 7
function foo() {
  console.log(this.a);
}
const _foo = foo.bind({a:1})
_foo.call({a:2}) // 1
const _foo2 = foo.bind()
_foo2.call({a:2}) // 7

好了。有关call的使用与具体细节如上,接下来让我们一步一步实现

实现

1.让this绑定call函数传入的对象

在这里,我们可以很自然的想到运用隐式绑定的规则

当函数作为一个对象方法调用的时候。函数内的this指向当前的上下文对象

Function.prototype.call2 = function (context) {
  context.fn = this
  context.fn()
  delete context.fn
}
  1. 传入nullundefined,this指向全局。传入基本类型,将其转化成对象
Function.prototype.call2 = function (context) {
  if (context === null || context === undefined) {
     context = window
  } else {
     context = Object(context)
  }
  // xxx
}
  1. 支持给函数传入参数,并支持返回值
Function.prototype.call2 = function (context) {
  var _args = []
  for (var index = 1; index < arguments.length; index++) {
    _args.push(arguments[index])
  }
  context.fn = this
  var result = eval('context.fn(' + _args + ')')
  return result
}

好了最后我们把所有代码统一下,就是整个call函数的实现了

Function.prototype.call2 = function(context) {
    if(context === null || context === undefined) {
      context = window
    } else {
      context = Object(context)
    }
    context.fn = this
    var _args = []
    for (var index = 1, len = arguments.length; index < len; index++) {
      _args.push(arguments[index])
    }
    var result = eval('context.fn(' + _args + ')')
    delete context.fn
    return result
}

apply的实现与call大同小异,这里直接给出实现

Function.prototype.apply2 = function(context) {
  if(context === null || context === undefined) {
    context = window
  } else {
    context = Object(context)
  }
  context.fn = this
  var _args = arguments[1]
  var result
  if (_args) {
     result = eval('context.fn(' + _args + ')')
  } else {
     result = context.fn()
  }
  delete context.fn
  return result
}

结语

上面的实现用es6的语法实现会更加简洁

到这里,就是本篇文章的全部内容了

如果你觉得该文章对你有帮助,欢迎大家点赞关注分享

如果你有疑问或者出入,评论区告诉我,我们一起讨论

参考文章

JavaScript深入之call和apply的模拟实现