JavaScript 函数的动态特性:自我定义与重写
关键要点:
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)。这使函数能够在浏览器中更有效地工作,并避免每次调用时都检查特性。
让我们以我们虚构的独角兽对象为例,该对象尚未在所有浏览器中获得完全支持。在上一章中,我们研究了如何使用特性检测来检查是否支持此功能。现在我们可以更进一步:我们可以根据是否支持某些方法来定义函数。这意味着我们只需要在第一次调用函数时检查支持情况:
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 中,您可以使用 Function
构造函数动态创建函数。此构造函数采用两个参数:一个包含逗号分隔的参数名称列表的字符串,以及一个包含函数体的字符串。例如,您可以创建一个添加两个数字的函数,如下所示:
party(); party(); party();
此方法允许您动态定义函数,但通常不推荐这样做,因为它不如正常声明函数那样高效且不易出错。
在 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 中,您可以通过简单地将新函数赋值给同一个变量来重写函数。这是一个例子:
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 函数。箭头函数提供了更简洁的语法,并且在处理 this 和其他特殊关键字方面有一些区别。这是一个定义和重写箭头函数的示例:
function party(){ console.log('Wow this is amazing!'); party = function(){ console.log('Been there, got the T-Shirt'); } }
当您调用 foo()
时,它将执行新函数,而不是原始函数。
以上是定义和重写自己的JavaScript函数的详细内容。更多信息请关注PHP中文网其他相关文章!