Skip to content

JavaScript之call和apply的模拟实现 #7

@chenyong9528

Description

@chenyong9528

call

MDN中的定义:

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

先看看call方法如何使用:

function foo(age) {
  console.log(this.name, age)
}

var obj = {
  name: "faker"
}

foo.call(obj, 18) // faker 18

透过现象看本质,我们会发现:

  1. call在函数foo上调用,而foo是Function的实例,它的所有实例都拥有这个方法,说明call存在于foo的原型(Function.prototype)中
  2. foo函数调用了,并且foo中的this指向了我们传入的对象obj,想一想,this要指向某个对象只有在我们以对象方法的形式调用(o.fn())时才成立
  3. 参数的传递

根据我们的发现,写一个理想状态下(至少传入一个对象参数)的call的模拟实现:

Function.prototype.call1 = function(context) {
  var args = []
  var res

  // this指向foo,foo函数成为了context的一个属性,所以context.fn()会使this指向context
  context.fn = this

  for (var i = 1; i < arguments.length; i++) {

    // 为什么不直接args.push(arguments[i])?
    args.push('arguments[' + i + ']')
  }
  
  
  // 之所以不args.push(arguments[i]),因为此步骤会发生类型转换,想一想当参数是引用类型会发生什么
  res = eval('context.fn(' + args + ')') // 等同于context.fn(arguments[1], arguments[2])

  // context和全局中的o引用同一个对象,context的修改也会在o中反映出来,所以我们在使用之后应该删除添加的方法
  delete context.fn
  return res
}

function foo(info) {
  console.log(this.name, info)
}

let o = {
  name: "faker"
}

foo.call1(o, 18) // faker 18
foo.call1(o, { age: 18 }) // faker { age: 18 }

apply

apply的作用和call一样,唯一不同的是传递的参数是一个数组,同样考虑理想状态下(至少传入一个对象)的代码:

Function.prototype.apply1 = function(context, arr) {
  var args = []
  var res

  context.fn = this

  for (var i = 0; i < (arr || []).length; i++) {

    args.push('arr[' + i + ']')
  }
  
  res = eval('context.fn(' + args + ')')

  delete context.fn
  return res
}

function foo(info) {
  console.log(this.name, info)
}

let o = {
  name: "faker"
}

foo.apply1(o, [18]) // faker 18
foo.apply1(o, [{ age: 18 }]) // faker { age: 18 }

ES6实现call和apply

Function.prototype.call1 = function(context, ...args) {
  
  context.fn = this
  
  var res = context.fn(...args)

  delete context.fn
  return res
}

function foo(info) {
  console.log(this.name, info)
}

let o = {
  name: "faker"
}

foo.call1(o, 18) // faker 18
foo.call1(o, { age: 18 }) // faker { age: 18 }
Function.prototype.apply1 = function(context, args) {

  context.fn = this
  
  const res = context.fn(...args)

  delete context.fn
  return res
}

function foo(info) {
  console.log(this.name, info)
}

const o = {
  name: "faker"
}

foo.apply1(o, [18]) // faker 18
foo.apply1(o, [{ age: 18 }]) // faker { age: 18 }

有扩展运算符的加成确实简单很多

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions