为什么JavaScript不需要main()函数?

mic*_*mia 31 javascript

许多编程语言都需要一个特殊的用户编写函数来标记执行的开始.例如,在C中,此函数必须始终具有名称main().但是,在JavaScript中,不需要这样的功能.

在JavaScript中缺少这种专用顶级函数的逻辑原因是什么?我知道这是某种理论问题,但我无法在网上找到答案.

T.J*_*der 45

因为整个代码块实际上是一个大的main.在JavaScript中,全局代码可以具有函数代码可以具有的所有构造,并且具有逐步执行,就像函数一样.实际上,当JS引擎整体处理代码块时,它与处理函数调用时几乎完全相同.请参阅规范的10.4.1节("输入全局代码")和10.4.3("输入功能代码"),并注意它们的相似程度.

C不允许在全局级别使用逐步代码(您可以使用各种初始化程序,并且它们可以逐步进行,但这是一个不同的主题).所以C需要一个明确的入口点(main).在JavaScript中,入口点是代码的开头.


关于您的问题,关于全球代码是否是连续的.答案是肯定的,它就像函数中的代码一样.所以:

var x, y, z;
x = 1;
y = 2;
z = x + y;
alert("z is " + z);
Run Code Online (Sandbox Code Playgroud)

...会提醒"z is 3".代码从上到下依次运行.

然而,在执行逐步代码之前会发生一些事情,这对于了解很有用.最重要的是,在逐步代码开始之前处理输入范围的源文本中的任何声明.JavaScript有两种主要类型的声明:变量声明和函数声明:

  1. 在执行任何逐步代码之前var,将声明的任何变量的名称添加到作用域(带有值undefined).(更多:穷人,被误解var)

  2. 在执行任何逐步代码之前,将处理函数声明并将函数名称添加到作用域.(JavaScript还有其他东西,称为函数表达式,它是逐步代码.更多内容如下.)

例如,在此源文本中:

var x;
x = 1;
foo();

function foo() {
}
Run Code Online (Sandbox Code Playgroud)

声明是

var x;
function foo() {
}
Run Code Online (Sandbox Code Playgroud)

而逐步代码是

x = 1;
foo();
Run Code Online (Sandbox Code Playgroud)

首先处理声明.这就是致电的原因foo.(这些相同的规则适用于函数中的源文本.)这种声明处理在其他任何事情之前有时被称为"提升",因为声明在某种意义上从它们在源文本中的位置被提升并移动到最开始.我更喜欢将它视为两个遍历源:第一遍传递声明,第二传递执行逐步代码.

(旁注:在同一范围内多次声明一个变量是完全合法的[虽然没有意义].声明两个具有相同名称的函数也是合法的;后一个声明会覆盖前一个函数.)

(附注2:ES2015 [ES6]介绍letconst变量声明,其行为有点不同var.你不能用它们声明变量两次,它们有块作用域,你不能在语句之前使用它的变量声明.所以他们大都没有悬挂[有一些稍微像冲顶,他们阻止访问阴影变量中包含作用域甚至前let x或任何线.)


更多细节,可能有点技术性:

var

如果var在逐步代码运行之前发生,您可能想知道:

var x = 1;
Run Code Online (Sandbox Code Playgroud)

这是在逐步代码之前发生还是作为其中的一部分?答案是,实际上,这只是两种截然不同的简写:

var x;
x = 1;
Run Code Online (Sandbox Code Playgroud)

var x;部分在逐步代码之前发生,该x = 1;部分是逐步代码,并且当我们在序列中到达它时执行.所以:

alert(x); // "undefined" -- there **is** a variable `x`; it has the value `undefined`
var x = 1;
alert(x); // "1" -- now `x` has the value `1`
Run Code Online (Sandbox Code Playgroud)

函数声明

JavaScript有两种不同但非常相似的东西:函数声明和函数表达式.您可以根据是否将结果函数用作定义它的表达式的一部分来判断哪个是哪个.

这是一个函数声明:

function foo() {
}
Run Code Online (Sandbox Code Playgroud)

这些都是函数表达式(我们使用结果函数值作为表达式的一部分;在计算机科学术语中,函数用作右手值):

// 1: Assigning the result to something
var x = function() {
};

// 2: Passing the result into a function
bar(function() {
});

// 3: Calling the function immediately
(function(){
})();

// 4: Also calling the function immediately (parens at end are different)
(function(){
}());

// 5: Also calling the function immediately
!function(){
}();

// 6: Syntax error, the parser needs *something* (parens, an operator like ! or
// + or -, whatever) to know that the `function` keyword is starting an *expression*,
// because otherwise it starts a *declaration* and the parens at the end don't make
// any sense (and function declarations are required to have names).
function(){
}();
Run Code Online (Sandbox Code Playgroud)

规则是在逐步代码开始之前处理函数声明.与所有其他表达式一样,函数表达式在遇到它们时进行处理.

最后一点注意:这是一个命名函数表达式:

var f = function foo() {
};
Run Code Online (Sandbox Code Playgroud)

我们用它作为右手值,所以我们知道它是一个表达; 但它有一个像函数声明一样的名称.这是完全有效且合法的JavaScript,它的意图是创建一个具有正确名称(foo)的函数作为逐步代码的一部分.函数的名称添加到范围中(如果它是函数声明的话).

但是,你不会在很多地方看到命名函数表达式,因为JScript(微软的JavaScript引擎)会让它们变得非常糟糕,在两个不同的时间创建两个独立的函数.

  • @Boanerghes:是的,“逐步”代码是顺序的。我已经扩展了答案以解决该问题(相当彻底)。 (2认同)
  • 谢谢@crowder!您的回答很棒。 (2认同)
  • @mplungjan:1. 递归。2. 它曾经对调试很重要(当然早在 2012 年编写上述内容时),因此您的函数在堆栈跟踪等中具有名称。由于 ES5 在对象初始值设定项中的方法语法、ES2015 的推断函数名称以及超出推断函数名称规则的现代开发工具,现在这不是一个问题。 (2认同)

Tom*_*icz 5

JavaScript 是事件驱动的,用 JavaScript 编写的程序没有开始和结束。您可以将其与任何桌面 UI 工具包进行比较,在其中处理按钮单击和按键操作,但main一旦程序初始化,就没有明显的变化。

例如,window.onload当页面加载时会触发一个事件 - 您可以处理该事件。

  • **JavaScript** 本身并不是本质上是事件驱动的。浏览器上以及 NodeJS 等环境中的 JavaScript 是事件驱动的,但请注意,“语言”本身就是某种东西,独立于环境。 (7认同)
  • 严格来说这并不正确。JavaScript 在许多环境中确实有开始和结束。它唯一保持活动状态的时间是在浏览器中使用时。此外,像“window.onload”这样的事件不是 JavaScript 的一部分,而是 DOM 的一部分。 (3认同)

Dan*_* D. 5

在脚本语言中,代码从文件的第一行到最后执行,就好像它被输入到解释器中一样。(这并不排除解析和编译代码,只要这些过程不影响所描述的指称语义。)