Skip to content

📍 this

​ JavaScript 中的 this 关键字用于指代当前执行上下文,但由于 JavaScript 语言的动态特性,this 的值也是动态的(运行时绑定),并且在不同的场景下可能会有不同的值。

🌎 全局上下文

​ 在全局上下文中,this值为全局对象,全局对象取决于运行环境,如下。

运行环境全局对象
浏览器(Browser)window
Node.jsglobal

tip:

  1. 不建议在全局上下文中使用this,而应使用windowglobal,以避免混淆和潜在的错误。

  2. ES11引入了新的全局对象globalThis,它提供了一种跨平台访问全局环境的标准方式。

📦 模块上下文

​ 在模块上下文中,this的值便不再是全局对象。同时,不同的运行环境,模块上下文中的this值也是不同的,如下。

运行环境this值
浏览器(Browser)undefined
Node.jsmodule.exports

tip:

  1. module.exports重新赋值时,thismodule.exports便不相等了。
  2. 不建议在模块上下文中使用this,而应使用Node模块中提供的exportsmodule.exports

🧩 函数上下文

​ 在函数上下文中, this 值在定义函数时是无法确定的,而是根据运行时函数被调用的方式来确定,以下是几种常见情况。

调用方式示例函数中的this值
全局中直接调用method()全局对象
模块中直接调用method()undefined/module.exports
通过new关键字调用new method()新对象
通过对象的属性调用obj.method()所属对象
通过call方法调用method.call(context)第一个参数,即context
通过apply方法调用method.apply(context)第一个参数,即context
通过bind方法生成新函数,再调用新函数method.bind(context)()bind方法的第一个参数,即context

​ 需要注意的是,调用全局对象中的函数时,尽管全局对象可以省略,但此时函数体中的this值依然为函数所属对象。例如下面的函数中的this值依然为window,运行环境为浏览器。

普通函数this值示例


​ 此外,箭头函数没有自身的this绑定,出现在箭头函数内部中的this值与其定义时最近的外层上下文中的this值保持一致。以下为箭头函数this值示例,运行环境为浏览器。

箭头函数this值示例

​ 同时,由于箭头函数没有this指针,通过applycallbind方法调用一个函数时,第一个参数将会被忽略。

✍️ call、apply、bind

call

Function.prototype.call方法用于显式地调用一个函数(API调用),并在调用时可以指定函数的this值和参数列表。

call方法签名

call方法是Function原型上的方法,常用的语法是通过method.call(context)进行调用。在调用过程中,call函数体内部的this值会被设置为method。由此,我们可以直接在call函数体内部间接调用context.method(),并将参数传递进去。这样一来,method函数调用时其内部的this就指向了传入的context对象。

call方法原理图

​ 从上图可知,实现call方法的重点是将method作为context身上的属性,并通过context.method()调用即可,method的值便是call函数体中的this值,但此过程仍需注意以下几点。

  1. 若传递的context值为nullundefined,则this值为全局对象。
  2. 若传递的context值为基本数据类型,如string、number、boolean等,context将被包装。
  3. method作为context对象的属性时,其属性名必须唯一,且需要在call方法结束后删除。
javascript
Function.prototype.call = function (context, ...args) {
    context = context === null || context === undefined
        ? globalThis : Object(context)
    const tempKey = Symbol('temp')
    Object.defineProperty(context, tempKey, {
        value: this,
        enumerable: false
    })
    const result = context[tempKey](...args)
    delete context[tempKey]
    return result
}

apply

Function.prototype.apply方法与call方法类似,也用于显式地调用一个函数(API调用),并在调用时可以指定函数的this值和参数列表。

apply方法签名

​ 与call方法不同的是,apply方法接收的参数列表不是展开的,而是数组。所以,我们只需要将数组展开,调用call方法即可,但此过程仍需注意以下几点。

  1. 当参数列表,即方法的第二个参数为nullundefined时,原函数调用时无参数。
  2. 当参数列表,即方法的第二个参数不是数组类型,且不是nullundefined,则抛出错误。
  3. apply函数体内的this值为原函数。
javascript
Function.prototype.apply = function (context, args) {
    this.call(context, ...(args || []))
}

bind

Function.prototype.bind方法用于创建一个新的绑定函数,新函数的this值为bind方法的第一个参数。bind方法还可以接受额外的参数,这些参数会被提前绑定到新函数的参数列表。当调用新函数时,这些绑定的参数和传入的参数会一起传递给原函数。

bind方法签名

​ 与callapply方法不同的是,bind函数的调用不会触发原函数的执行,而是返回新的绑定函数。我们可以在绑定函数中调用apply来触发原函数的执行,并在调用时合并参数列表,但此过程仍需注意以下几点。

  1. 绑定函数也可以被当作构造函数使用,此时bind方法绑定的this值将被忽略,函数体中的this值将指向新实例,合并的参数列表依然传入。
  2. bind函数体内的this值为原函数。
javascript
Function.prototype.bind = function (context, ...args) {
    const func = this
    const boundFunc = function (...argArray) {
        if (this instanceof boundFunc) {
            return new func(...args, ...argArray)
        }
        return func.apply(context, argArray.concat(args))
    }
    Object.setPrototypeOf(func.prototype, boundFunc.prototype)
    return boundFunc
}

上次更新于: