首页 > web前端 > js教程 > 定义和重写自己的JavaScript函数

定义和重写自己的JavaScript函数

Christopher Nolan
发布: 2025-02-16 08:41:09
原创
480 人浏览过

JavaScript 函数的动态特性:自我定义与重写

JavaScript Functions That Define and Rewrite Themselves

关键要点:

  • JavaScript 的动态特性允许函数自我定义甚至重写自身。这通过将匿名函数赋值给与函数同名的变量来实现。这个概念被称为惰性定义模式 (Lazy Definition Pattern),可用于仅在第一次函数调用时需要的初始化代码。
  • 如果在函数第一次调用和后续重定义之前将其赋值给另一个变量,则新变量将保留原始函数定义,不会被重写。但是,在函数重定义之前设置的任何属性都将丢失。
  • 函数重写技术可以与特性检测相结合,以创建更高效的函数,这个概念称为初始化时分支 (init-time branching)。这允许函数针对正在使用的浏览器进行优化,仅在第一次函数调用期间检查特性支持。

JavaScript Functions That Define and Rewrite Themselves

JavaScript 的动态特性意味着函数不仅可以调用自身,还可以定义自身,甚至重写自身。这是通过将匿名函数赋值给与函数同名的变量来实现的。

考虑以下函数:

function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}
登录后复制
登录后复制
登录后复制
登录后复制

此函数首先向控制台输出一条消息,然后重写自身以向控制台输出不同的消息。当函数被调用一次后,它就像这样定义:

function party() {
  console.log('Been there, got the T-Shirt');
}
登录后复制
登录后复制
登录后复制

第一次调用之后,每次调用该函数都会输出消息“Been there, got the T-Shirt”:

party();
party();
party();
登录后复制
登录后复制
登录后复制

如果函数也赋值给另一个变量,则此变量将保留原始函数定义,不会被重写。这是因为原始函数被赋值给一个变量,然后在函数内部,与函数同名的变量被赋值给另一个函数。如果我们在第一次调用和重定义 之前 创建一个名为 beachParty 的变量并将其赋值给 party() 函数,则可以看到此示例:

function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}

const beachParty = party; // 注意,party 函数尚未被调用

beachParty(); // party() 函数现在已被重定义,即使它没有被显式调用

party();

beachParty(); // 但此函数尚未被重定义

beachParty(); // 无论调用多少次,它都将保持不变
登录后复制
登录后复制

属性丢失

小心:如果之前在函数上设置了任何属性,则当函数重写自身时,这些属性将丢失。在前面的示例中,我们可以设置一个 music 属性,并看到在函数被调用和重定义后它不再存在:

function party() {
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}

party.music = 'Classical Jazz'; // 设置函数的属性

party();

party.music; // 函数现在已被重定义,因此属性不存在
登录后复制
登录后复制

这被称为惰性定义模式 (Lazy Definition Pattern),通常在第一次调用时需要一些初始化代码时使用。这意味着可以在第一次调用时完成初始化,然后可以将函数重定义为您希望在每次后续调用时使用的函数。

初始化时分支 (Init-Time Branching)

此技术可以与我们在上一章中讨论的特性检测一起使用,以创建重写自身的函数,称为初始化时分支 (init-time branching)。这使函数能够在浏览器中更有效地工作,并避免每次调用时都检查特性。

让我们以我们虚构的独角兽对象为例,该对象尚未在所有浏览器中获得完全支持。在上一章中,我们研究了如何使用特性检测来检查是否支持此功能。现在我们可以更进一步:我们可以根据是否支持某些方法来定义函数。这意味着我们只需要在第一次调用函数时检查支持情况:

function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}
登录后复制
登录后复制
登录后复制
登录后复制

在检查了 window.unicorn 对象是否存在(通过检查它是否为真值)之后,我们根据结果重写了 ride() 函数。在函数的最后,我们再次调用它,以便现在调用重写的函数,并返回相关值。需要注意的是,函数第一次调用时会调用两次,尽管它在每次后续调用时都会变得更高效。让我们看看它是如何工作的:

function party() {
  console.log('Been there, got the T-Shirt');
}
登录后复制
登录后复制
登录后复制

一旦函数被调用,它就会根据浏览器的功能进行重写。我们可以通过检查函数而不调用它来检查这一点:

这可以是一个有用的模式,用于在第一次调用函数时初始化它们,并针对正在使用的浏览器对其进行优化。

递归函数

递归函数是指调用自身直到满足某个条件的函数。当涉及迭代过程时,它是一个有用的工具。一个常见的例子是计算数字阶乘的函数:

party();
party();
party();
登录后复制
登录后复制
登录后复制

如果提供 0 作为参数(0 的阶乘为 1),则此函数将返回 1,否则它将把参数乘以使用比它少一个的参数调用自身的结果。该函数将继续调用自身,直到最终参数为 0 并返回 1。这将导致 1、2、3 和所有直到原始参数的所有数字相乘。

来自数学领域的另一个例子是 Collatz 猜想。这是一个陈述起来很简单的问题,但到目前为止尚未解决。它涉及取任何正整数并遵循以下规则:

  • 如果数字是偶数,则将其除以二
  • 如果数字是奇数,则将其乘以三再加一

例如,如果我们从数字 18 开始,我们将得到以下序列:

18, 9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, 4, 2, 1, …

如您所见,序列最终陷入循环,循环遍历“4,2,1”。Collatz 猜想指出,每个正整数都会创建一个最终以该循环结束的序列。这已针对高达 5 × 2⁶⁰ 的所有数字进行了验证,但没有证据表明它将继续对高于此的所有整数都成立。为了测试该猜想,我们可以编写一个使用递归来不断调用函数直到达到值 1 的函数(因为我们希望我们的函数避免最终陷入递归循环!):

function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}
登录后复制
登录后复制
登录后复制
登录后复制

此函数将一个数字作为参数,以及另一个名为 sequence 的参数,其默认值为包含第一个参数的数组。第二个参数仅在函数递归调用自身时使用。

函数首先做的是测试 n 的值是否为 1。如果是,则函数返回一条消息来说明它花了多少步。如果它没有达到 1,它会检查 n 的值是偶数(在这种情况下,它将其除以 2),还是奇数,在这种情况下,它乘以 3 再加 1。然后函数调用自身,提供新的 n 值和新的序列作为参数。新序列是通过将旧序列和 n 的值放在一个新数组中并将展开运算符应用于旧序列来构建的。

让我们看看数字 18 会发生什么:

function party() {
  console.log('Been there, got the T-Shirt');
}
登录后复制
登录后复制
登录后复制

如您所见,它需要 21 步,但最终它会结束于 1。

尝试使用该函数,看看您能否找到一个大于 5 × 2⁶⁰ 的值不会结束于 1——如果您这样做,您将成名!

关于自我定义和重写 JavaScript 函数的常见问题解答 (FAQ)

如何在 JavaScript 中动态创建函数?

在 JavaScript 中,您可以使用 Function 构造函数动态创建函数。此构造函数采用两个参数:一个包含逗号分隔的参数名称列表的字符串,以及一个包含函数体的字符串。例如,您可以创建一个添加两个数字的函数,如下所示:

party();
party();
party();
登录后复制
登录后复制
登录后复制

此方法允许您动态定义函数,但通常不推荐这样做,因为它不如正常声明函数那样高效且不易出错。

JavaScript 函数自我定义是什么意思?

在 JavaScript 中,函数可以自我定义,这意味着它可以在运行时修改自己的代码。这是可能的,因为 JavaScript 中的函数是一等对象,这意味着它们可以传递、从其他函数返回,甚至可以修改。这是一个函数重写自身的示例:

function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}

const beachParty = party; // 注意,party 函数尚未被调用

beachParty(); // party() 函数现在已被重定义,即使它没有被显式调用

party();

beachParty(); // 但此函数尚未被重定义

beachParty(); // 无论调用多少次,它都将保持不变
登录后复制
登录后复制

第一次调用 foo() 时,它会重写自身。下次调用 foo() 时,它将执行新代码。

如何重写 JavaScript 函数?

在 JavaScript 中,您可以通过简单地将新函数赋值给同一个变量来重写函数。这是一个例子:

function party() {
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}

party.music = 'Classical Jazz'; // 设置函数的属性

party();

party.music; // 函数现在已被重定义,因此属性不存在
登录后复制
登录后复制

当您调用 foo() 时,它将执行新函数,而不是原始函数。这是因为变量 foo 现在指向新函数。

动态定义 JavaScript 函数的优缺点是什么?

动态定义 JavaScript 函数可以提供灵活性,因为您可以根据程序的需求动态创建和修改函数。但是,它也有一些缺点。它不如正常声明函数那样高效,因为 JavaScript 引擎无法提前优化函数。它也更容易出错,因为函数体字符串中的任何错误都不会在函数执行之前被捕获。

我可以使用箭头函数来定义和重写 JavaScript 函数吗?

是的,您可以使用箭头函数来定义和重写 JavaScript 函数。箭头函数提供了更简洁的语法,并且在处理 this 和其他特殊关键字方面有一些区别。这是一个定义和重写箭头函数的示例:

function party(){
  console.log('Wow this is amazing!');
  party = function(){
    console.log('Been there, got the T-Shirt');
  }
}
登录后复制
登录后复制
登录后复制
登录后复制

当您调用 foo() 时,它将执行新函数,而不是原始函数。

以上是定义和重写自己的JavaScript函数的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板