JavaScript進階(深刻基礎系列)之call/apply的模擬實現

call

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。git

語法

fun.call(thisArg, arg1, arg2, ...)github

返回值

使用調用者提供的 this 值和參數調用該函數的返回值。若該方法沒有返回值,則返回 undefined。數組

call的模擬實現

首先咱們來看一個例子app

var foo = {
  value: 1
}

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

bar.call(foo, 'zhangsan', '18') // 1 zhangsan 18
複製代碼

這看起來就像是這樣函數

var foo = {
  value: 1,
  bar: function (name, age) {
    console.log(this.value, name, age)
  }
}
foo.bar('zhangsan', '18') // 1 zhangsan 18
複製代碼

但咱們發現foo會多出來一個bar方法,這是咱們不想要的,咱們能夠在foo.bar以後刪除掉這個屬性,看起來也就像這樣測試

foo.fn = function () {}
foo.fn()
delete.fn
複製代碼

開始模擬實現

var foo = {
  value: 1
}

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

Function.prototype.call2 = function (context) {
  // 咱們先來看看context、this、arguments都是那些參數
  console.log('context: ', context)
  console.log('this: ', this)
  console.log('arguments: ', arguments)
}
bar.call2(foo, 'zhangsan', '18')
複製代碼

ei5TZ6.png

咱們能夠看到,context指向的是foo,this指向bar,arguments的第一個參數是foo,剩餘的是咱們須要給bar傳遞的參數ui

所以咱們能夠這樣寫this

var foo = {
  value: 1
}

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

Function.prototype.call2 = function (context) {
  context.fn = this
  var args = [] // 用於接收arguments參數

  for (var i = 1; i < arguments.length; i++) {
    args.push(arguments[i])
  }

  context.fn(args)
  delete context.fn
}
bar.call2(foo, 'zhangsan', '18')  // 1 (2) ["zhangsan", "18"] undefined
複製代碼

咱們發現獲得的結果並非咱們想要的,context.fn(args) args只想當於一個參數,咱們須要把args解構,將每個參數都傳遞給context.fn,爲了兼容咱們不使用ES6...運算符。spa

JavaScript的eval函數會將傳入的字符串當作 JavaScript 代碼進行執行prototype

eval

代碼以下

Function.prototype.call2 = function (context) {
  context.fn = this
  var args = [] // 用於接收arguments參數

  for (var i = 1; i < arguments.length; i++) {
    args.push('arguments[' + i + ']')
  }

  eval('context.fn(' + args + ')')
  delete context.fn
}
bar.call2(foo, 'zhangsan', '18')  // 1 "zhangsan" "18"
複製代碼

咱們獲得了咱們想要的結果,但假設bar方法須要return出去一個值呢,又或者context爲null或者一個非Object的值呢

一、函數是能夠有返回值!

function bar (name, age) {
  console.log(name, age, this.value)
  return {name}
}
console.log(bar.call(foo, 'zhangsan'))  // {name: "zhangsan"}

console.log(bar.call2(foo, 'zhangsan')) // undefined
複製代碼

咱們發現咱們須要把eval('context.fn(' + args + ')')的結果return出去

代碼以下

Function.prototype.call2 = function (context) {
  context.fn = this
  var args = [] // 用於接收arguments參數

  for (var i = 1; i < arguments.length; i++) {
    args.push('arguments[' + i + ']')
  }

  var result = eval('context.fn(' + args + ')')
  delete context.fn
  return result
}

console.log(bar.call2(foo, 'zhangsan')) // {name: "zhangsan"}
複製代碼

二、this能夠傳遞爲null 當爲null時,默認指向window

代碼以下

Function.prototype.call2 = function (context) {
  context = context ? context : window
  context.fn = this
  var args = [] // 用於接收arguments參數

  for (var i = 1; i < arguments.length; i++) {
    args.push('arguments[' + i + ']')
  }

  var result = eval('context.fn(' + args + ')')
  delete context.fn
  return result
}
複製代碼

三、this爲一個非Object類型的值時

代碼以下

Function.prototype.call2 = function (context) {
  context = context ? Object(context) : window
  context.fn = this
  var args = [] // 用於接收arguments參數

  for (var i = 1; i < arguments.length; i++) {
    args.push('arguments[' + i + ']')
  }

  var result = eval('context.fn(' + args + ')')
  delete context.fn
  return result
}
複製代碼

測試代碼以下

var foo = {
  value: 1
}
function bar (name, age) {
  console.log(name, age, this.value)
  return {name}
}

console.log(bar.call(123, 'zhangsan'))

Function.prototype.call2 = function (context) {
  context = context ? Object(context) : window
  context.fn = this
  var args = []
  for (var i = 1; i < arguments.length; i++) {
    args.push('arguments[' + i + ']')
  }
  var result = eval('context.fn(' + args + ')')
  delete context.fn
  return result
}
console.log(bar.call2(123, 'zhangsan', '18'))
複製代碼

ES6的實現

Function.prototype.call2 = function (context) {
  context = context ? Object(context) : window
  context.fn = this

  const args = [...arguments].slice(1)
  const result = context.fn(...args)

  delete context.fn
  return result
}
複製代碼

apply的實現

apply和call只有一個區別,就是 call() 方法接受的是一個參數列表,而 apply() 方法接受的是一個包含多個參數的數組。

Function.prototype.apply2 = function (context, arr) {
  context = context ? Object(context) : window
  context.fn = this

  var result

  if (!arr) {
    result = context.fn
  } else {
    var args = []
    for(var i = 0; i < arr.length; i++) {
      args.push('arr[' + i + ']')
    }
    result = eval('context.fn(' + args + ')')
  }

  delete context.fn
  return result
}
複製代碼

ES6的實現

Function.prototype.apply2 = function (context, arr) {
  context = context ? Object(context) : window
  context.fn = this

  let result

  if (!arr) {
    result = context.fn
  } else {
    result = context.fn(...arr)
  }

  delete context.fn
  return result
}
複製代碼

參考:JavaScript深刻之call和apply的模擬實現

相關文章
相關標籤/搜索