【JS 基础】call、apply、bind理解及手写实现

173 阅读2分钟

概念

call,apply,bind 都可以改变 this 的指向 我们先来看一个例子

function fn(a, b) {
  console.log(this.msg, a, b)
}
fn(1, 2) // undefined 1 2
const obj1 = {
  msg: 'hello'
}
// 此时,fn的this的指向就是obj1
fn.call(obj1, 1, 2) // hello 1 2
fn.apply(obj1, [1, 2])// hello 1 2
const fn1 = fn.bind(obj1, 1, 2) // 返回的是一个函数,不会立即调用
fn1() // hello 1 2
  • call、apply 的第一个参数还是调用函数this的指向,
  • call的第二参数是列表(arg1, arg2...), apply第二个参数是数组[arg1, arg2...]
  • bind返回的是一个函数,不会立即调用

应用

  1. 使用 Math 的 max/min 求最大最小值
let arr = [3, 8, 2, 6]
let max = Math.max.apply(null, arr) // 8
let min = Math.min.apply(null, arr) // 2

当然也用ES6的的展开运算符 Math.max(...arr) Math.min(...arr)

  1. Object.prototype.toString.call() 判断数据类型

因为 Object.prototype.toString() 方法会返回对象的类型字符串,输出 "[object Object]" 其中第二个 Object 是传入参数的构造函数。所以使用 call 就可以指定任意的值和结合 toString 将组成的构造函数类型返回来判断类型。同样道理换成 apply/bind 同样也可以判断

const toString = Object.prototype.toString
console.log(toString.call(123)) // [object Number]
console.log(toString.call('abc')) // [object String]
console.log(toString.call(true)) // [object Boolean]
console.log(toString.call(undefined)) // [object Undefined]
console.log(toString.call(null)) // [object Null]
console.log(toString.call([])) // [object Array]
console.log(toString.call({})) // [object Object]
console.log(toString.call(function() {})) // [object Function]
  1. 使用 call() 实现将类数组转化成数组
let array = [12, 23, 45, 65, 32]
function fn(array){
    var args = [].slice.call(arguments)
    return args[0]
}
fn(array)   // [12, 23, 45, 65, 32]

let array = [12, 23, 45, 65, 32]
function fn(array){
    var args = [].slice.call(arguments)
    return args[0]
}
fn(array) // 12

手写实现

call

Function.prototype.myCall = function(context) {
  // 如果没有传或传的值为空对象 context指向window
  context = context || window
  let fn = Symbol()
  // 将this指向
  context[fn] = this
  // 处理参数 去除第一个参数, 获取其它传入fn函数
  let args = [...arguments].slice(1)
  // 执行fn
  context[fn](...args)
  // 删除方法
  delete context[fn]
}
// 测试结果:
function fn(a, b) {
  console.log(this.msg, a, b)
}
const obj1 = {
  msg: 'hello'
}
fn.call(obj1, 1, 2) // hello 1 2
fn.myCall(obj1, 1, 2) // hello 1 2

apply

apply跟call的实现比较类似

Function.prototype.myApply = function(context) {
  context = context || window
  const fn = Symbol()
  context[fn] = this
  const args = [...arguments][1] || []
  context[fn](...args)
  delete context[fn]
}
// 测试结果:
function fn(a, b) {
  console.log(this.msg, a, b)
}
const obj1 = {
  msg: 'hello'
}
fn.apply(obj1, [1, 2]) // hello 1 2
fn.myApply(obj1, [1, 2]) // hello 1 2

bind

Function.prototype.myBind = function(context) {
  context = context || window
  const self = this
  const args = [...arguments].slice(1)
  function fnBind() {
    const newArgs = [...arguments]
    // 判断是否为new操作
    // 如果当前函数的this指向的是构造函数中的this 则判定为new 操作
    const _this = this instanceof self ? this : context
    return self.apply(_this, args.concat(newArgs))
  }
  // 维持原型链关系
  const fnNop = function() {}
  if (this.prototype) {
    fnNop.prototype = this.prototype
  }
  fnBind.prototype = new fnNop()
  return fnBind
}
// 测试结果
const obj1 = {
}
function Foo(name) {
  this.name = name
  console.log(this.name)
}
const f1 = Foo.myBind(obj1)
f1('abc') // abc
const f2 = new f1('abc')
console.log(f2.name) // abc