为什么在JavaScript中定义函数之前就可以使用它?


167

即使在不同的浏览器中,此代码也始终有效:

function fooCheck() {
  alert(internalFoo()); // We are using internalFoo() here...

  return internalFoo(); // And here, even though it has not been defined...

  function internalFoo() { return true; } //...until here!
}

fooCheck();

但是,我找不到关于为什么它应该起作用的单一参考。我首先在John Resig的演示文稿中看到了这一点,但仅被提及。那里或任何地方都没有解释。

有人可以启发我吗?


3
在较新版本的Firefox中,如果代码在try / catch中,则此功能将无效。看到这个小提琴:jsfiddle.net/qzzc1evt
Joshua Smith

Answers:


217

function声明是魔术,使它的标识符在代码块*中的任何内容执行之前就被绑定了。

这与带有function表达式的赋值不同,后者以正常的自上而下的顺序求值。

如果将示例更改为说:

var internalFoo = function() { return true; };

它将停止工作。

函数声明在语法上与函数表达式完全分开,即使它们看起来几乎相同并且在某些情况下可能是模棱两可的。

这在ECMAScript标准的10.1.3节中有说明。不幸的是,即使按照标准标准,ECMA-262也不是一个易于阅读的文档!

*:包含的功能,块,模块或脚本。


我猜它真的不可读。我只读了您指出的10.1.3节,却不明白为什么那里的规定会导致这种行为。感谢您的信息。
Edu Felipe

2
@bobince好的,当我在页面上找不到“提升”一词时,我开始怀疑自己。希望这些评论有足够的Google Juice™可以解决问题:)
Mathias Bynens 2011年

2
这是一个受欢迎的问题/答案组合。考虑使用带有ES5注释规范的链接/摘录进行更新。(这更易于访问。)

1
本文提供了一些示例:JavaScript的作用域和提升
-Lombas

我发现很多库在定义之前就使用了该函数,甚至某些语言也正式允许它使用。哈斯克尔。老实说,这可能不是一件坏事,因为在某些情况下您可以写得更具表现力。
windmaomao

27

这称为HOISTING-在定义函数之前调用(调用)函数。

我要编写的两种不同类型的函数是:

表达式函数和声明函数

  1. 表达式功能:

    函数表达式可以存储在变量中,因此它们不需要函数名称。它们还将被命名为匿名函数(没有名称的函数)。

    要调用(调用)这些函数,它们始终需要一个变量名。如果在定义之前调用此函数,它将无法正常工作,这意味着此处未发生吊装。我们必须始终先定义表达式函数,然后再调用它。

    let lastName = function (family) {
     console.log("My last name is " + family);
    };
    let x = lastName("Lopez");

    这是您如何在ECMAScript 6中编写它:

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
  2. 声明功能:

    用以下语法声明的函数不会立即执行。它们被“保存以备后用”,并在调用(被调用)时稍后执行。如果在定义位置之前或之后调用此功能,则此类型的功能有效。如果在定义之前调用声明函数,则吊装工作正常。

    function Name(name) {
      console.log("My cat's name is " + name);
    }
    Name("Chloe");

    吊装示例:

    Name("Chloe");
    function Name(name) {
       console.log("My cat's name is " + name);
    }

let fun = theFunction; fun(); function theFunction() {}也将起作用(节点和浏览器)
fider

14

浏览器从头到尾读取您的HTML,并可以在读取并解析为可执行块(变量声明,函数定义等)时执行它,但是在任何时候都只能使用该点之前脚本中定义的内容。

这与处理(编译)所有源代码,可能将其与解析引用所需的任何库链接在一起,并构造可执行模块(此后开始执行)的其他编程上下文不同。

您的代码可以引用进一步定义的命名对象(变量,其他函数等),但是直到所有部分都可用后,您才能执行引用代码。

随着您对JavaScript的熟悉,您将充分意识到您需要按正确的顺序编写东西。

修订:要确认接受的答案(上述),请使用Firebug逐步浏览网页的脚本部分。您会看到它在函数实际执行任何代码之前从一个函数跳到另一个函数,仅访问第一行。


3

一些语言要求必须在使用前定义标识符。原因是编译器对源代码使用了一次传递。

但是,如果有多次通过(或推迟了一些检查),那么您可以完美地生活而无需那项要求。在这种情况下,可能首先读取(解释)代码,然后设置链接。



1

函数“ internalFoo”的主体需要在解析时移到某个地方,因此,当JS解释器读取(也称为解析)代码时,将创建该函数的数据结构并分配名称。

直到稍后,然后运行代码,JavaScript实际上试图找出“ internalFoo”是否存在,它是什么以及是否可以调用等。


-4

出于相同的原因,以下内容将始终放在foo全局名称空间中:

if (test condition) {
    var foo;
}

8
实际上,这是出于非常不同的原因。该if块不创建范围,而function()块总是创建一个。真正的原因是全局javascript名称的定义发生在编译阶段,因此即使代码未运行,也已定义了该名称。(对不起,花了这么长时间才发表评论)
Edu Felipe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.