JavaScript 中的 this - 动态绑定和实现细节

阅读时长 10 分钟读完

在 JavaScript 中,this 关键字是一个非常重要的概念。它代表了当前函数执行的上下文环境,可以帮助我们方便地操作 DOM 元素、调用对象方法等等。但是,this 在实际开发中经常会出现一些意想不到的问题,因为它的值取决于函数被调用的方式以及上下文环境的不同,而这些情况可能非常复杂。因此,在本篇文章中,我们将详细讨论 JavaScript 中的 this,并分析其动态绑定和实现细节,以及如何避免一些常见的问题。

1、this 的动态绑定

在 JavaScript 中,this 的值是动态绑定的,也就是说在执行函数时才确定它的值。一般情况下,this 的值取决于函数的调用方式,有以下几种情况。

1.1、全局对象

当函数不作为对象的方法调用时,this 指向全局对象(浏览器中为 window 对象,在 Node.js 环境中为 global 对象)。

例如:

在上面的例子中,当我们不给函数 test 指定上下文对象时,test 函数中的 this 指向的是全局对象 window,因此输出 true。

1.2、对象的方法调用

当函数作为对象的方法调用时,this 指向该对象。

例如:

在上面的例子中,函数 b 是对象 obj 的一个方法,因此在函数 b 中的 this 指向的是对象 obj,输出结果为 1。

1.3、构造函数调用

当一个函数作为构造函数来调用时(使用 new 关键字),this 指向新创建的对象。

例如:

在上面的例子中,当我们使用 new 关键字调用 Person 函数时,this 就指向新创建的对象 p,用来保存其属性值。

1.4、apply 和 call 方法调用

apply 和 call 方法可以改变函数的 this 指向,并立即调用该函数。其中,apply 方法接收一个数组参数,该数组包含了作为参数传递给函数的值;call 方法接收多个参数,每个参数依次作为函数的参数传递进去。

例如:

-- -------------------- ---- -------
--- ---- - -
  -- --
--

--- ---- - -
  -- --
--

-------- ------ -
  --------------------
-

----------------- -- -- -
---------------- -- -- -

在上面的例子中,我们分别使用 apply 和 call 方法来调用 test 函数,并将 this 的值分别指向了 obj1 和 obj2 对象。因此,第一个输出结果为 1,第二个输出结果为 2。

2、this 的实现细节

所谓 this 的实现细节,主要指 this 绑定规则的优先级。在 JavaScript 中,this 的值是动态绑定的,即取决于调用函数的方式和函数的执行上下文。但是,在实际开发中,我们经常会遇到一些特殊情况,例如嵌套函数、箭头函数、闭包等,这些情况可能会导致 this 的值出现一些问题。因此,在本节中,我们将讨论 this 的实现细节,包括以下几点。

2.1、嵌套函数

在嵌套函数中,this 的值可能会出现一些问题。当嵌套函数作为对象的方法调用时,this 的值会指向该对象;但是如果嵌套函数单独调用时,this 的值会指向全局对象。

例如:

-- -------------------- ---- -------
--- - - --

--- --- - -
  -- --
  -- ---------- -
    -------------------- -- -

    -------- --- -
      -------------------- -- -
    -

    ----
  --
--

--------

在上面的例子中,因为嵌套函数 c 是单独调用的,所以 this 的值指向全局对象 window,因此第二个输出结果为 1,而不是 2。为了避免这种问题,我们可以在外层函数中将 this 保存到一个名为 that 或 self 的变量中,从而在嵌套函数中使用。

例如:

-- -------------------- ---- -------
--- --- - -
  -- --
  -- ---------- -
    --- ---- - -----

    -------------------- -- -

    -------- --- -
      -------------------- -- -
    -

    ----
  --
--

--------

在上面的例子中,我们使用了 that 变量来保存 this 的值,从而在嵌套函数中使用。

2.2、箭头函数

在箭头函数中,this 的值不是动态绑定的,而是继承自父级作用域中的 this。

例如:

-- -------------------- ---- -------
--- - - --

--- --- - -
  -- --
  -- ---------- -
    -------------------- -- -

    --- - - -- -- -
      -------------------- -- -
    --

    ----
  --
--

--------

在上面的例子中,箭头函数 c 中的 this 指向的是外层函数 b 的 this,因此输出结果都为 2。正是因为这种特殊的 this 绑定规则,使得箭头函数在处理回调函数时非常方便和简洁,可以避免使用 that 或 self 变量来保存 this。

2.3、构造函数和原型方法

在构造函数中和原型方法中,this 的值的取决关系比较复杂,取决于具体的代码实现。

在使用构造函数创建对象时,我们可以使用 this 来定义该对象的属性和方法,同时也可以在构造函数的作用域链中使用 this 来引用其他变量和函数。

例如:

-- -------------------- ---- -------
-------- ------------ ---- -
  --------- - -----
  -------- - ----

  ------------- - ---------- -
    ------------------- -- ---- -- - - --------- - -- --- - - -------- - - ----- -------
  --
-

--- - - --- ------------- ----

------------- -- -- ------- -- ---- -- ---- --- -- ----- -----

在上面的例子中,我们使用构造函数 Person 创建了一个对象 p,使用 this 关键字来实现其属性和方法,而在 sayHello 方法中,我们又使用了 this 来引用 Person 对象的属性 name 和 age。

在原型中,this 的值可以引用该对象的属性和方法,也可以引用该对象的构造函数的属性和方法。我们可以使用 this 关键字来定义或访问原型方法,使用 this.constructor 来访问该对象的构造函数。

例如:

-- -------------------- ---- -------
-------- ------------ ---- -
  --------- - -----
  -------- - ----
-

------------------------- - ---------- -
  ------------------- -- ---- -- - - --------- - -- --- - - -------- - - ----- -------
--

--------------------- - ---------

--- - - --- ------------- ----

------------- -- -- ------- -- ---- -- ---- --- -- ----- -----
-------------------- -- -- --------

在上面的例子中,我们将 sayHello 方法和 type 属性定义到 Person 构造函数的原型中,使用 this 来引用该对象的属性和方法,同时也使用 this.constructor 来访问该对象的构造函数。当我们使用 p.sayHello() 调用对象 p 的方法时,this 的值指向对象 p,因此输出结果正确。当我们使用 p.type 来访问对象 p 的 type 属性时,this 的值也指向对象 p,但是该属性并不存在于对象 p 中,而是存在于其原型对象中,因此该输出结果也是正确的。

3、避免常见问题

虽然使用 this 关键字可以方便地访问对象的属性和方法,但是很多时候也容易出现一些常见问题,例如嵌套函数的 this 绑定、setTimeout 函数中的 this 绑定等等。为了避免这些问题,我们可以使用以下几种方法。

3.1、使用 apply 和 call 明确指定 this 的值

我们可以使用 apply 和 call 方法明确指定要绑定的 this 的值,从而避免 this 绑定规则的复杂性。例如:

-- -------------------- ---- -------
--- --- - -
  -- --
  -- ---------- -
    --------------------
  --
--

--- - - --

----------------- -- -
----------------- -- -

在上面的例子中,我们分别使用了 call 和 apply 方法来明确指定函数 b 的 this 的值,从而避免了嵌套函数的 this 绑定问题。

3.2、使用箭头函数

使用箭头函数可以避免在嵌套函数中使用 that 或 self 变量来保存目标对象的引用。例如:

-- -------------------- ---- -------
--- - - --

--- --- - -
  -- --
  -- ---------- -
    -------------------- -- -

    --- - - -- -- -
      -------------------- -- -
    --

    ----
  --
--

--------

在上面的例子中,我们使用箭头函数 c 来避免了嵌套函数的 this 绑定问题,从而使得代码更加简洁和易读。

3.3、使用闭包

使用闭包可以在函数外部保存目标对象的引用,从而避免嵌套函数的 this 绑定问题。例如:

-- -------------------- ---- -------
--- - - --

--- --- - -
  -- --
  -- ---------- -
    -------------------- -- -

    --- ---- - -----

    -------- --- -
      -------------------- -- -
    -

    ------ --
  --
--

--- - - --------
----

在上面的例子中,我们使用了闭包来保存了函数 b 的 this 指向对象 obj 的引用,从而在嵌套函数 c 中可以使用该引用访问该对象的属性和方法。当我们在外部调用 f 函数时,打印结果也是正确的。这种方法尽管有些复杂,但是可以避免 this 带来的一些不必要的问题。

4、总结

在 JavaScript 中,this 是一个非常重要的概念,其绑定规则决定了该关键字的值取决于函数的调用方式和函数的执行上下文。在实际开发中,我们经常会遇到一些特殊情况,例如嵌套函数、箭头函数、闭包等,这些情况可能会导致 this 的值出现一些问题。为了避免这些问题,我们需要了解 this 的动态绑定和实现细节,并使用 apply 和 call 方法、箭头函数以及闭包等方法来明确指定 this 的值或保存目标对象的引用,从而写出具有效率和可读性的 JavaScript 代码。

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/64a92b7d48841e9894573cac

纠错
反馈