Function实例的call()/apply()/bind()方法使用及自定义实现详细步骤

57 阅读9分钟

本文主要详细记录Function 实例的call()/apply()/bind()方法的使用及自定义实现详细步骤

  1. 函数的call方法-文档链接
// 以指定的this调用函数,并通过 从第二个参数开始依次传递参数
function func(food,drink){
  console.log(this)
  console.log(food)
  console.log(drink)
}
const obj = {
  name:'测试'
}
func.call(obj,'面包','可乐')

// obj
// 面包
// 可乐
  1. 函数的apply方法-文档链接
// 以指定的this调用函数,并通过 数组的形式 传递参数
function func(food,drink){
  console.log(this)
  console.log(food)
  console.log(drink)
}
const obj = {
  name:'测试'
}
func.apply(obj,['面包','可乐'])

// obj
// 面包
// 可乐
  1. 函数的bind方法-文档链接
function func(food, drink) {
  console.log(this)
  console.log(food)
  console.log(drink)
}
const obj = {
  name: '测试'
}
const bindFunc = func.bind(obj, '面包')
bindFunc('可乐')

// obj
// 面包
// 可乐

如何确认this的值:

在非严格模式下,总是指向一个对象,在严格模式下可以是任意值,开启严格模式可以使用如下两种方式:

  1. 在整个脚本顶部开启
  2. 在函数顶部开启
// 1.为整个脚本开启严格模式
'use strict'


function func() {
  // 2.为函数开启严格模式
  'use strict'
}

然后就可以根据不同的模式来确认this指向啦,

  1. 全局执行环境中,指向全局对象(非严格模式、严格模式)
  2. 函数内部,取决于函数被调用的方式
    1. 直接调用的this值:
      1. 非严格模式:全局对象(window)
      2. 严格模式:undefined
    2. 对象方法调用的this值:
      1. 严格模式 和 非严格模式 都指向调用者
1.全局执行环境
非严格模式: 不做任何设置,直接写就是非严格模式
console.log(this) // window
严格模式: 代码顶部加上 'use strict' 即可
'use strict' // 为整个脚本开启严格模式
console.log(this) // window



2.函数内部
 2.1 直接调用-非严格模式
function func() {
  console.log(this) // 全局对象window
}
func()



 2.1 直接调用-严格模式
function func() {
  'use strict'
  console.log(this) // undefined
}
func()



 2.2 对象方法调用
const food = {
  name: '猪脚饭',
  eat() {
    console.log('吧唧吧唧')
    console.log(this)
  }
}
// 对象方法调用 严格模式 和 非严格模式 都指向调用者 food
food.eat()  // this 指向调用者  food

总结

如何确认this指向:

  1. 全局执行环境中,指向全局对象(非严格模式、严格模式)
  2. 如何开启严格模式:
// 为整个脚本开启严格模式
'use strict'


function func() {
  // 为函数开启严格模式
  'use strict'
}
  1. 函数内部,取决于函数被调用的方式
  2. 直接调用的this值: 1. 非严格模式:全局对象(window) 2. 严格模式:undefined
  3. 对象方法调用时的this值为调用者

如何改变this指向

主要有2类改变函数内部this指向的方法:

  1. 调用函数并传入具体的**this**:
    1. call:
      1. 参数1:this
      2. 参数2-n:传递给函数的参数
    2. apply-数组作为参数
      1. 参数1:this
      2. 参数2:以数组的形式,传递给函数的参数
  2. 创建时绑定**this**的函数:
    1. bind:返回一个绑定了this的新函数
    2. 箭头函数:最近的this是谁,就是谁

调用函数并传入具体的this:

function funcA(p1, p2) {
  console.log('funcA-调用')
  console.log(this)
  console.log('p1:', p1)
  console.log('p2:', p2)
}
const obj = {
  name: 'test'
}

// call参数
// 参数1 this值
// 参数2-参数n 挨个传入函数的参数
funcA.call(obj, 1, 2)   // obj 1,2

// apply参数
// 参数1 this值
// 参数2 以数组的形式传入函数的参数
funcA.apply(obj, [3, 4])  // obj 3,4

创建绑定this的函数:

function funcB(p1, p2) {
  console.log('funcB-调用')
  console.log(this)
  console.log('p1:', p1)
  console.log('p2:', p2)
}
const person = {
  name: 'test'
}

// bind参数
// 参数1 this值
// 参数2-参数n 绑定的参数
const bindFuncB = funcB.bind(person, 123)
bindFuncB(666)  // person  123  666

const student = {
  name: 'xuesheng',
  sayHi: function () {
    console.log(this)
    // 箭头会从自己作用域链的上一层继承this
    const inner = () => {
      console.log('inner-调用了')
      console.log(this)
    }
    inner()
  }
}
student.sayHi()

总结

如何改变this指向,有2类改变this指向的方法,分别是:

  1. 调用函数时并传入具体的this
    1. call:从第二个参数开始挨个传递参数
    2. apply:在第二个参数以数组的形式传递参数
  2. 创建函数时绑定this?
    1. bind:返回一个绑定了this以及参数(可选)的新函数
    2. 箭头函数:创建时会绑定上一级作用域中的this

自定义call方法

实现myCall方法,实际用法和call方法一致,核心步骤有4步

const person = {
  name: 'Test'
}

function func(numA, numB) {
  console.log(this)
  console.log(numA, numB)
  return numA + numB
}

// 参数1:指定的this值
// 参数2-参数n:原函数参数
const res = func.myCall(person, 2, 8)
console.log('返回值为:', res)
  1. 如何定义myCall?
    1. 要保证所有的函数都能调用这个方法,所以绑定在原型 prototype 上
  2. 如何让函数内部的this为某个对象?
    1. 设置this
    2. 调用原函数,原函数中的this,就是传入的值

image.png

  1. 如何让myCall接收参数2-参数n?
  2. 使用Symbol调优myCall
// 1. 如何定义`myCall`,
Function.prototype.myCall = function () {
  // 逻辑略
}

// 2 设置this并调用原函数
Function.prototype.myCall = function (thisArg) {
	// 判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('Error')
  }

  // 判断thisArg 是否存在,不存在传入window
  thisArg = thisArg || window
  
  //1. this 是调用myCall的 函数 (原函数.mycall) 
  //2. func.myCall  在mycall 里this 就是func,这里是赋值的过程
  thisArg.fn= this
  
  // 通过对象.的方式调用原函数,thisArg.fn(),原函数里的this,就是指向thisArg,在这里就是指向person对象
  const res = thisArg.fn()
  
  // 绑定成功后,移除添加的自定义属性
  delete thisArg.fn
}


// 3 接收剩余参数并返回结果
Function.prototype.myCall = function (thisArg, ...args) {
  thisArg.fn = this
  // 调用并获取结果
  const res = thisArg.fn(...args)
  // 移除添加的自定义属性
  delete thisArg.fn
  // 返回调用结果
  return res
}


// 4 使用`Symbol`调优`myCall`
Function.prototype.myCall = function (thisArg, ...args) {
	// 判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('Error')
  }

  // 判断thisArg 是否存在,不存在传入window
  thisArg = thisArg || window
  
  // 使用Symbol生成唯一标记,避免和原属性冲突
  // 此时key为动态的属性名,不能使用.key 来写
  const key = Symbol()
  thisArg[key] = this
  const res = thisArg[key](...args)
  // 移除添加的自定义属性
  delete thisArg[key]
  // 返回调用结果
  return res
}


// --------测试代码--------
const person = {
  name: 'test'
}
function func(numA, numB) {
  console.log(this)
  console.log(numA, numB)
  return numA + numB
}
// 参数1:指定的this值
// 参数2-参数n:原函数参数
const res = func.myCall(person, 2, 8)
console.log('返回值为:', res)

总结:

自定义call方法的步骤为

  1. function的原型上添加myCall方法,保证所有函数都可以调用
  2. 方法内部,通过动态为对象添加方法的形式来指定this指向
  3. 调用完毕之后通过delete关键字删除上一步动态增加的方法
  4. 方法的名字通过Symbol进行设置,避免和默认名重复
  5. 使用剩余参数的形式传递参数2-参数n(函数参数)

完整版

Function.prototype.myCall = function (thisArg, ...args) {
  // 判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('Error')
  }

  // 判断thisArg 是否存在,不存在传入window
  thisArg = thisArg || window
  const fn = Symbol()
  thisArg[fn] = this
  const res = thisArg[fn](...args)
  delete thisArg[fn]
  return res
}

自定义apply方法

实现myApply方法,实际用法和apply方法一致,核心步骤依旧4

const person = {
  name: 'test’
}

function func(numA, numB) {
  console.log(this)
  console.log(numA, numB)
  return numA + numB
}

const res = func.myApply(person, [2, 8])
console.log('返回值为:', res)
  1. 如何定义myApply?
    1. 定义在原型上
  2. 如何让函数内部的this为某个对象?
    1. 动态给对象添加方法,通过对象.方法()调用即可
    2. 使用Symbol来生成方法名
  3. 如何让myApply接收参数?
    1. 定义参数2即可
    2. 传递给原函数时需要使用...展开
// 1. 如何定义`myApply`
Function.prototype.myApply = function () {
  // 逻辑略
}

// 2 如何让函数内部的`this`为某个对象
Function.prototype.myApply = function (thisArg) {
	// 判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('Error')
  }

  // 判断thisArg 是否存在,不存在传入window
  thisArg = thisArg || window

  
  // 为他添加一个自定义属性,让函数成为他的该属性
  // 使用Symbol生成唯一标记,避免和原属性冲突
  const fn = Symbol()
  thisArg[fn] = this
  const res = thisArg[fn](...args)
  // 移除添加的自定义属性
  delete thisArg[fn]
  // 返回调用结果
  return res
}


// 3 如何让`myApply`接收参数
Function.prototype.myApply = function (thisArg, args) {
	// 判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('Error')
  }

  // 判断thisArg 是否存在,不存在传入window
  thisArg = thisArg || window
  
  const fn = Symbol()
  thisArg[fn] = this
  // 调用并获取结果
  // 用... 将args展开传入
  const res = thisArg[fn](...args)
  delete thisArg['fn']
  // 返回调用结果
  return res
}




// 测试代码
const person = {
  name: 'test’
}

function func(numA, numB) {
  console.log(this)
  console.log(numA, numB)
  return numA + numB
}

const res = func.myApply(person, [2, 8])
console.log('返回值为:', res)

总结

自定义apply方法

  1. function的原型上添加myApply方法,保证所有函数都可以调用
  2. 方法内部,通过动态为对象添加方法的形式来指定this指向
  3. 调用完毕之后通过delete关键字删除上一步动态增加的方法
  4. 方法的名字通过Symbol进行设置,避免和默认名重复
  5. 直接使用数组传递函数的参数,内部调用时结合...运算符展开数组

完整版

Function.prototype.myApply = function (thisArg, args) {
  	// 判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('Error')
  }

  // 判断thisArg 是否存在,不存在传入window
  thisArg = thisArg || window
  const fn = Symbol()
  thisArg[fn] = this
  const res = thisArg[fn](...args)
  delete thisArg[fn]
  return res
}

自定义bind方法

实现myBind方法,实际用法和bind方法一致,核心步骤为2步

const person = {
  name: 'test'
}

function func(numA, numB, numC, numD) {
  console.log(this)
  console.log(numA, numB, numC, numD)
  return numA + numB + numC + numD
}

const bindFunc = func.myBind(person, 1, 2)

const res = bindFunc(3, 4)
console.log('返回值:', res)
  1. 如何返回一个绑定了this的函数?
  2. 如何实现绑定的参数,及传入的参数合并?
// 1 如何返回一个绑定了`this`的函数
Function.prototype.myBind = function (thisArg) {
  // 判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('Error')
  }
  // myBind函数调用时,this就是函数本身 
  return () => {
    // 通过call方法将传入的 thisArg 作为this进行调用
    this.call(thisArg)
  }
}

// 2 如何实现绑定的参数,及传入的参数合并
// ...args 接收绑定参数
Function.prototype.myBind = function (thisArg, ...args) {
  // 判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('Error')
  }
  // ...args2 接收调用时的参数
  return (...args2) => {
    // thisArg 需要指定的this
    // args 调用myBind时传入的参数
    // args2 调用新函数时传入的参数
   return this.call(thisArg, ...args, ...args2)
  }
}

// 测试代码
const person = {
  name: 'test'
}

function func(numA, numB, numC, numD) {
  console.log(this)
  console.log(numA, numB, numC, numD)
  return numA + numB + numC + numD
}

const bindFunc = func.myBind(person, 1, 2)

const res = bindFunc(3, 4)
console.log('返回值:', res)

总结

自定义bind方法

  1. function原型上添加myBind函数,参数1为绑定的this,参数2-参数2为绑定的参数
  2. 内部返回一个新箭头函数,目的是绑定作用域中的this
  3. 返回的函数内部,通过call进行this和参数绑定(这里使用apply方法也是一样的,注意参数是数组)
  4. 通过call的参数2和参数3指定绑定的参数,和调用时传递的参数

完整版

Function.prototype.myBind = function (thisArg, ...args) {
  // 判断调用对象是否为函数
  if(typeof this !== 'function') {
    throw new TypeError('Error')
  }
  return (...args2) => {
   return this.call(thisArg, ...args, ...args2)
  }
}