javascript基础- 函数中 this 指向、call、apply、bind

前言

本文简单讲解函数中this的指向问题,及涉及主动改变 this 指向的 call apply bind三个方法的实现原理。

函数内this的指向

在函数内部,this 的值取决于函数如何被调用。

我们先定义两个函数,分别使用和不使用严格模式,返回this,以供后续使用。

js 复制代码
function fnReturnThis() {
  return this
}
function fnReturnThisStrict() {
  "use strict"
  return this
}
console.log(this === window) // true

典型函数调用

js 复制代码
console.log(fnReturnThisStrict() === undefined) // true

const obj = { 
  fnReturnThis, 
  fnReturnThisStrict 
}
console.log(obj.fnReturnThis()) // obj
console.log(obj.fnReturnThisStrict()) // obj

const obj2 = {
  fnReturnThis: obj.fnReturnThis,
  fnReturnThisStrict: obj.fnReturnThisStrict,
  obj,
}
console.log(obj2.fnReturnThis()) // obj2
console.log(obj2.fnReturnThisStrict()) // obj2

console.log(obj2.obj.fnReturnThis()) // obj
console.log(obj2.obj.fnReturnThisStrict()) // obj

const fnReturnObj = {
  fnReturnWrapper: function() { 
    return fnReturnThisStrict()
  }
}
console.log(fnReturnObj.fnReturnWrapper()) // undefined
  • 可以看到,通常情况下,函数内的this都指向当前函数的调用对象(.前面的对象)
  • 部分特殊场景,在非严格模式下存在如下差异化

非严格模式差异化

js 复制代码
console.log(fnReturnThis()) // window
console.log(fnReturnThisStrict()) // undefined

console.log(fnReturnThis.call(null)) // window
console.log(fnReturnThisStrict.call(null)) // null

console.log(fnReturnThis.call(undefined)) // window
console.log(fnReturnThisStrict.call(undefined)) // undefined

console.log(fnReturnThis.call(false)) // Boolean {false}
console.log(fnReturnThisStrict.call(false)) // false

console.log(fnReturnThis.call(0)) // Number {0}
console.log(fnReturnThisStrict.call(0)) // 0

console.log(fnReturnThis.call(BigInt(0))) // BigInt {0}
console.log(fnReturnThisStrict.call(BigInt(0))) // 0n

const symbol = Symbol()
console.log(fnReturnThis.call(symbol)) // Symbol {Symbol()}
console.log(fnReturnThisStrict.call(symbol) === symbol) // true

console.log(fnReturnThis.call('0')) // String {'0'}
console.log(fnReturnThisStrict.call('0')) // '0'

const fnReturnObj = {
  fnReturnWrapper: function() { 
    console.log(
      this,
      fnReturnThis(),
      fnReturnThisStrict()
    )
  }
}
fnReturnObj.fnReturnWrapper() // fnReturnObj  window  undefined
  • 非严格模式下,一个特殊的过程称为 this 替换,确保 this 的值总是一个对象
    • 如果一个函数被调用时 this 被设置为 undefinednullthis 会被替换为 globalThis
    • 如果函数被调用时 this 被设置为一个原始值,this 会被替换为原始值的包装对象。
  • 而严格模式下,this的值自始自终都遵循规范,若未指定值,则指向当前函数的调用对象,否则为指定值。

通过修改原型链,也符合上述标准

js 复制代码
Number.prototype.fnReturnThis = fnReturnThis
Number.prototype.fnReturnThisStrict = fnReturnThisStrict
const num = 1
console.log(num.fnReturnThis()) // Number {1}
console.log(num.fnReturnThisStrict()) // num

作为回调函数执行,同样也符合上述标准

js 复制代码
function callbackFn(fnReturnThis) {
  return fnReturnThis()
}
console.log(callbackFn(fnReturnThis)) // window
console.log(callbackFn(fnReturnThisStrict)) // undefined

const objUnStrict = { fnReturnThis }
const objStrict = { fnReturnThis: fnReturnThisStrict }
function callbackFn2(obj) {
  return obj.fnReturnThis()
}
console.log(callbackFn2(objStrict)) // objStrict
console.log(callbackFn2(objUnStrict)) // objUnStrict

console.log(Array.from(new Array(1)).map(fnReturnThis)[0]) // window
console.log(Array.from(new Array(1)).map(fnReturnThisStrict)[0]) // undefined

console.log(Array.from(new Array(1)).map(fnReturnThis, null)[0]) // window
console.log(Array.from(new Array(1)).map(fnReturnThisStrict, null)[0]) // null

console.log(Array.from(new Array(1)).map(fnReturnThis, 0)[0]) // Number {0}
console.log(Array.from(new Array(1)).map(fnReturnThisStrict, 0)[0]) // 0

所以要确定函数中this,只需按以下步骤:

  • 找到调用的这个函数(一定要找准确),调用this就是
    • 严格模式下,this就是调用对象
    • 非严格模式下,this一定为一个对象
      • 如果为undefinednull,则会被替换为 globalThis
      • 如果为其它原始值,则会被替换为这个原始值的包装对象。

主动修改this指向,call applybind

ts 复制代码
apply<T, R>(this: (this: T) => R, thisArg: T): R;
apply<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, args: A): R;

call<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A): R;

bind<T>(this: T, thisArg: ThisParameterType<T>): OmitThisParameter<T>;
bind<T, A extends any[], B extends any[], R>(this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R;

由于函数中的this指向是在函数执行前确定的,所以同一个函数以不同方式执行会导致函数内部this指向不可预测(只看函数,你永远不会知道下一次这个函数是被谁调用),所以,在必要的时候,需要用call applybind来指定函数内部this

ts 复制代码
function PlusN(n = 1) { 
  this.a = this.a + n
}
const obj = {
  a: 1,
  plusN: PlusN,
}
obj.plusN(1)
console.log(obj.a) // 2

const plus = obj.plusN

plus.call(obj, 1)
console.log(obj.a) // 3

plus.apply(obj, [1])
console.log(obj.a) // 4

const plusBind = PlusN.bind(obj)
plusBind(1)
console.log(obj.a) // 5

plus(1) // TypeError Cannot read properties of undefined (reading 'a')

// 作为构建函数
console.log(new PlusN()) // PlusN {a: 1}
const PlusNBind = PlusN.bind(obj)
console.log(new PlusNBind()) // PlusN {a: 1}
console.log(obj.a) // 5 不会影响 obj

从原理实现 call apply

call apply都是接受第一个参数作为函数内的this,其它入参为函数入参。返回函数执行结果。

也就是需要用第一个入参来调用函数,后续入参作为函数入参。并返回函数执行结果。

js 复制代码
Function.prototype.myCall = function() {
  // 获取传入的数组参数
  let [context, ...args] = arguments

  // if myApply
  // args = args[0] 

  // 这里我们只做原理解析,注入原始值的情况不做考虑
  if (!(context instanceof Object)) throw new TypeError('context must be an object')
  
  // 将函数挂载到传入的context对象上
  let fnSymbolKey = Symbol()
  context[fnSymbolKey] = this

  // 记录返回值
  let res = args.length === 0
    ? context[fnSymbolKey]()
    : context[fnSymbolKey](...args)
  
  // 从上下文中删除函数引用
  delete context[fnSymbolKey]

  // 返回返回值
  return res
}

从原理实现 bind

从目标函数上调用,接受第一个参数作为函数内的this,其它入参为函数入参,返回一个函数,执行函数返回绑定this后的函数执行结果,支持追加函数入参(偏函数)。

ts 复制代码
Function.prototype.myBind = function(...params) {
  if (typeof this !== "function") throw new TypeError("what is trying to be bound is not a function")
  let [oThis, ...args] = params
  
  const Obj = { [this.name]: this }
  let functionToBind = this
  let FunctionBound = function(...boundFunctionParams) {
    return functionToBind.myCall(
      // 作为构建函数使用
      this instanceof FunctionBound ? this : oThis,
      // 偏函数功能
      ...args.concat(...boundFunctionParams)
    )
  }
  // 作为构建函数,将原型对象赋给新的函数
  FunctionBound.prototype = this.prototype
  return Obj[this.name]
}
相关推荐
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼10 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte10 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
NEXT0610 小时前
前端算法:从 O(n²) 到 O(n),列表转树的极致优化
前端·数据结构·算法