JavaScript - this、call、apply、bind
#this 关键字概述
与其他语言相比,函数的 this
关键字在 JavaScript 中的表现有着显著差异,在 严格模式
和 非严格模式
下也存在行为上的不同。
在大多数情况下,函数的 调用方式
决定了 this
的值(这被称为 运行时绑定
)。this
不能在执行期间被赋值,并且在每次函数被调用时 this
的值可能会发生变化。ES5 引入了 bind
方法来设置函数的 this
值,而不用考虑函数如何被调用。ES2015 引入了 箭头函数
,其不提供自身的 this 绑定,而是继承自外部词法作用域的 this
值。
this
的值取决于它出现的上下文:函数 上下文
、类上下文
或 全局上下文
。
#this 在不同上下文中的指向
#全局上下文
在全局执行上下文中(在任何函数体外部),this
指向全局对象,在浏览器中为 window
对象。
全局上下文中的thisJavaScript
console.log(this) // 在浏览器中输出 window 对象
#函数上下文
在函数内部,this
的值取决于函数的调用方式:
#默认绑定
当函数被直接调用时,this
在非严格模式下指向 全局对象
,在严格模式下为 undefined
。
默认绑定JavaScript
function fn() {console.log(this) // 非严格模式下是 window,严格模式下是 undefined}fn()
#隐式绑定
当函数作为对象的方法调用时,this
指向调用该方法的对象。
隐式绑定JavaScript
const weatherData = {condition: "晴天",fn() {console.log(this) // 指向 weatherData 对象console.log(this.condition) // "晴天"},}weatherData.fn()
但要注意,如果将该方法赋值给一个变量后再调用,this
会丢失原来的指向:
隐式绑定丢失JavaScript
const weatherData = {condition: "多云",fn() {console.log(this.condition)},}const getFn = weatherData.fn // 函数引用赋值给变量getFn() // 输出 undefined,因为此时 this 指向全局对象
#显式绑定
通过 call
、apply
或 bind
方法,我们可以显式地指定函数执行时的 this
值。
显式绑定JavaScript
function reportWeather() {console.log(`今天的天气是:${this.condition}`)}const forecast = { condition: "雨天" }reportWeather.call(forecast) // 输出:今天的天气是:雨天
#构造函数绑定
当使用 new
操作符调用函数时,函数内部的 this
会指向新创建的对象实例。
构造函数绑定JavaScript
function Weather(condition) {this.condition = conditionconsole.log(this) // 指向新创建的 Weather 实例}const weather = new Weather("大风")console.log(weather.condition) // "大风"
#箭头函数中的 this
箭头函数
不会创建自己的 this
上下文,而是继承父级作用域的 this
值。这意味着箭头函数中的 this
与定义它的上下文中的 this
相同。
箭头函数中的thisJavaScript
const forecast = {condition: "雪天",regularFunc: function () {console.log(this.condition) // "雪天"const arrowFunc = () => {console.log(this.condition) // 仍然是 "雪天",因为继承了 regularFunc 中的 this}arrowFunc()},}forecast.regularFunc()
#改变 this 指向的方法
JavaScript 提供了三种主要方法来改变函数执行时的 this
指向:call
、apply
和 bind
。
#call
call
方法允许调用一个函数,并明确指定函数执行时的 this
值,同时可以传入一系列参数。
#call 语法
JavaScript
function.call(thisArg, arg1, arg2, ...)
thisArg
调用 func
时提供的 this
值。如果函数不处于严格模式,则 null 和 undefined 会被替换为全局对象,原始值会被转换为对象。
argsArray
可选
函数的参数。
返回值
使用指定的 this
值和参数调用函数的结果。
#call 示例用法
call方法示例JavaScript
function forecast(time1, time2) {console.log(`${time1}和${time2}的天气是${this.condition}`)}const weather = { condition: "晴朗" }forecast.call(weather, "上午", "下午") // 输出:上午和下午的天气是晴朗
#手动实现 call 方法
手动实现call方法JavaScript
Function.prototype.myCall = function (context, ...args) {// 处理 context 为 null 或 undefined 的情况context = context == null ? globalThis : Object(context)// 使用 Symbol 创建唯一的方法名,避免属性名冲突const uniqueKey = Symbol("tempMethod")// 将当前函数设为 context 的一个方法context[uniqueKey] = this// 调用该方法const result = context[uniqueKey](...args)// 删除添加的临时方法delete context[uniqueKey]// 返回函数执行结果return result}
#apply
Function
实例的 apply()
方法会以给定的 this
值和作为数组(或类数组对象
)提供的 arguments
调用该函数。
#apply 语法
JavaScript
function.apply(thisArg, [argsArray])
thisArg
调用 func
时提供的 this
值。如果函数不处于严格模式,则 null 和 undefined 会被替换为全局对象,原始值会被转换为对象。
argsArray
可选
一个类数组对象,用于指定调用 func
时的参数,或者如果不需要向函数提供参数,则为 null 或 undefined。
返回值
使用指定的 this
值和参数调用函数的结果。
#apply 示例用法
apply方法示例JavaScript
function forecast(time1, time2) {console.log(`${time1}和${time2}的天气是${this.condition}`)}const weather = { condition: "多云转阴" }forecast.apply(weather, ["早晨", "傍晚"]) // 输出:早晨和傍晚的天气是多云转阴
#手动实现 apply 方法
手动实现apply方法JavaScript
Function.prototype.myApply = function (context, argsArray = []) {// 处理 context 为 null 或 undefined 的情况context = context == null ? globalThis : Object(context)// 使用 Symbol 创建唯一的方法名,避免属性名冲突const uniqueKey = Symbol("tempMethod")// 将当前函数设为 context 的一个方法context[uniqueKey] = this// 调用该方法,传入参数数组const result = context[uniqueKey](...argsArray)// 删除添加的临时方法delete context[uniqueKey]// 返回函数执行结果return result}
#bind
bind
方法与 call
和 apply
不同,它不会立即调用函数,而是返回一个新函数,新函数的 this
值被永久地绑定到指定的值。
#bind 语法
JavaScript
const newFunc = function.bind(thisArg, arg1, arg2, ...)
thisArg
调用 func
时提供的 this
值。如果函数不处于严格模式,则 null 和 undefined 会被替换为全局对象,原始值会被转换为对象,并且原始值会被转换为对象。如果使用 new 运算符构造绑定函数,则忽略该值。
argsArray
可选
在调用 func
时,插入到传入绑 定函数的参数前的参数。
返回值
使用指定的 this
值和初始参数(如果提供)创建的给定函数的副本。
#bind 示例用法
bind方法示例JavaScript
function forecast(period1, period2) {console.log(`${period1}和${period2}的天气是${this.condition}`)}const weather = { condition: "雷阵雨" }const morningForecast = forecast.bind(weather, "早上")morningForecast("中午") // 输出:早上和中午的天气是雷阵雨
#手动实现 bind 方法
手动实现bind方法JavaScript
Function.prototype.myBind = function (context, ...initialArgs) {// 保存原函数引用const originalFunc = this// 返回一个新函数return function boundFunc(...args) {// 处理构造函数调用的情况(使用 new 操作符)if (this instanceof boundFunc) {// 如果是通过 new 调用,则忽略 context,将 this 指向新创建的对象return new originalFunc(...initialArgs, ...args)}// 否则,使用 apply 方法调用原函数,绑定 this 和合并参数return originalFunc.apply(context, [...initialArgs, ...args])}}
#this 绑定的优先级
当多种 this
绑定规则同时适用时,它们遵循一定的优先级:
- 构造函数绑定(new):最高优先级
- 显式绑定(call、apply、bind):次高优先级
- 隐式绑定(对象方法调用):第三优先级
- 默认绑定(独立函数调用):最低优先级
#常见陷阱和最佳实践
#嵌套函数中的 this
在嵌套函数中,内部函数不会自动继承外部函数的 this
值:
嵌套函数中的thisJavaScript
const forecast = {condition: "阴天",outerMethod() {console.log(this.condition) // "阴天"function innerMethod() {console.log(this.condition) // undefined(在非严格模式下,指向全局对象)}innerMethod()},}forecast.outerMethod()
解决方案:
- 使用
箭头函数
,它会继承父作用域的 this - 在外部函数中保存 this 引用(如
const self = this
) - 使用
bind
方法明确绑定内部函数的 this
#回调函数中的 this
在将方法作为回调函数传递时,常常会丢失 this
上下文:
回调函数中的thisJavaScript
const temperature = {value: "26°C",display() {console.log(`当前温度:${this.value}`)},}// this 指向丢失setTimeout(temperature.display, 1000) // 输出:当前温度:undefined
解决方案:
- 使用
bind
方法绑定正确的 this:setTimeout(temperature.display.bind(temperature), 1000)
- 使用
箭头函数
封装调用:setTimeout(() => temperature.display(), 1000)
#总结
理解 JavaScript 中的 this
机制是编写高质量代码的关键。通过掌握不同上下文中 this
的行为规则,以及 call
、apply
和 bind
方法的使用,可以更精确地控制函数执行的上下文,避免常见的 this 指向错误,提高代码的可维护性和健壮性。