blog icon indicating copy to clipboard operation
blog copied to clipboard

JS中Call、Apply、Bind解析

Open BlingSu opened this issue 4 years ago • 0 comments

一句话概括 call、apply、bind 主要就是改变this的指向

call

call语法:

fun.call(thisArg, arg1, arg2, ...),调用一个函数,具有一个指定的this值和干个指定的参数值(参数列表)

例子
var foo = {
  value: 1
}
function bar () {
  console.log(this.value)
}

bar.call(foo) // 1

注意:

  1. call改变了this的指向,把bar的this指向了foo
  2. bar函数执行

简单点来说就是在调用call的时候把函数改造成这样

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

如何实现call核心:

  • 把函数设为对象的属性
  • 执行并且删除这个函数
  • 指定this到函数并传入给定参数执行函数
  • 若不传入参数,默认指向window
const selfCall = function (context, ...args) {
  console.log(context, 'context')
  let func = this
  console.log(func, 'func')
  console.log(args, 'args')
  context = context || window // context带着value
  if (typeof func !== 'function') throw new TypeError('this is no function')
  let caller = Symbol('caller')
  // 把函数放到context里面,执行以后删除,使用symbol的原因就是防止属性冲突咯!
  context[caller] = func
  let res = context[caller](...args)
  console.log(res)
  delete context[caller]
  return res
}
Function.prototype.selfCall = selfCall

let foo = { value: 1 }
function bar (name, age) {
  console.log('--------------------------------')
  console.log(name, 'bar---name')
  console.log(age, 'bar---age')
  console.log(this.value, 'foo---value')
}
bar.selfCall(foo, 'black', 18)

apply

apply的实现方式和call其实是一样的,只是第二个参数是否数组的原因

const selfApply = function (context) {
  context = context || window
  console.log(arguments)
  let func = this
  let caller = Symbol('caller')
  context[caller] = func
  let res 
  if (arguments[1]) {
    res = context[caller](...arguments[1])
  } else {
    res = context[caller]()
  }
  return res
}

Function.prototype.selfApply = selfApply

let person = {
  name: 'Tom'
}
function sayMsg (a, b, c) {
  console.log(`${this.name} - ${a} - ${b} - ${c}`)
}
sayMsg.selfApply(person, ['a', 'b', 'c'])

bind

bind()方法

会创建一个新函数,当这个新函数被调用时,bind()的第一个参数作为它运行时的this,之后的一序列参数将会在传递的实参前作为它的参数(bind的实现必须考虑实例化后对原型链的影响)

const selfBind = function (context) {
  console.log(context, '=----=> context')
  if (typeof this !== 'function') return
  let self = this
  console.log(this, '=====> this')
  let arg = [].slice.call(arguments, 1) // 不要被绑定方法的那个对象,就是context
  console.log('第一个args ->  ', arg)

  console.log('-------分割线-------')

  let fNop = function () {}

  let fBound = function () {
    let bindArg = [].slice.call(arguments)
    console.log('第二个args ->  ', bindArg)
    console.log('-------分割线-------')
    /**
     * 这里的 this instanceof 中的 this 是指的是调用func()时的执行环境
     * 判断是否用了new关键字 使用忽略bind(obj)的obj,不改变this
     * 没有就让this指向obj
    */
    console.log('new this---->', this instanceof fNop)
    return self.apply(this instanceof fNop ? this : context, arg.concat(bindArg))
  }
  fNop.prototype = this.prototype
  fBound.prototype = new fNop()
  return fBound
}
Function.prototype.selfBind = selfBind
// 测试
function func (name, age) {
  console.log(this.value)
  console.log(name)
  console.log(age)
}
let obj = {
  value: '一个值'
}
let bind = func.selfBind(obj, [1, 2, 3], 4)
bind(333, 'bind-333')
new bind()

实现函数bind的方法核心就是利用call绑定this的指向,同时也要考虑一些其他的问题:

  • 旧函数调用bind(obj)会返回一个新函数
  • 新函数使用旧函数的功能且判断是否改变this指向
  • 调用bind函数可传递参数,作为返回的新函数的默认参数
  • 调用bind函数后返回的新函数的实际参数包含 (this, [默认参数], [新函数参数])
  • 旧函数调用bind函数返回的新函数new出的实例化对象的constructor是旧函数, 利用继承的方式

BlingSu avatar Nov 02 '20 02:11 BlingSu