【问题标题】:Why doesn't JavaScript need a main() function?为什么 JavaScript 不需要 main() 函数?
【发布时间】:2012-01-26 09:05:22
【问题描述】:

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

JavaScript 中没有这样一个专用的顶级函数的逻辑原因是什么?我知道这是某种理论问题,但我在网上找不到答案。

【问题讨论】:

    标签: javascript


    【解决方案1】:

    因为整个代码块实际上是一大块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);
    

    ...将提醒"z is 3"。代码顺序运行,从上到下。

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

    1. 在执行任何逐步代码之前,使用var 声明的任何变量的名称都会添加到范围(值为undefined)。 (更多:Poor, misunderstood var

    2. 函数声明在执行任何逐步代码之前被处理并且函数名称被添加到作用域中。 (JavaScript 还有其他东西,称为函数表达式,它是逐步代码。下面会详细介绍。)

    例如,在这个源文本中:

    var x;
    x = 1;
    foo();
    
    function foo() {
    }
    

    声明是

    var x;
    function foo() {
    }
    

    逐步代码是

    x = 1;
    foo();
    

    首先处理声明。这就是调用foo 有效的原因。 (这些相同的规则也适用于函数中的源文本。)这种在其他任何事情之前对声明的处理有时被称为“提升”,因为在某种意义上,声明从它们在源文本中的位置被提升并移到了最开始。我更喜欢将其视为两次通过源代码:第一次执行声明,第二次执行逐步代码。

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

    (旁注 2:ES2015 [ES6] 引入了 letconst 变量声明,它们的行为与 var 略有不同。你不能用它们声明一个变量两次,它们有块作用域,你不能在声明它的语句之前使用变量。所以它们大多没有被提升[有一些类似提升的轻微,因为它们甚至在之前就阻止了对包含范围内的阴影变量的访问let x 或任何行]。)


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

    var

    如果var 发生在逐步代码运行之前,您可能对此感到疑惑:

    var x = 1;
    

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

    var x;
    x = 1;
    

    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`
    

    函数声明

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

    这是一个函数声明

    function foo() {
    }
    

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

    // 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(){
    }();
    

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

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

    var f = function foo() {
    };
    

    我们用它作为右手值,所以我们知道它是一个表达式;但它的名称类似于函数声明。这是完全有效且合法的 JavaScript,它的目的是创建一个具有正确名称 (foo) 的函数作为逐步代码的一部分。函数的名称​​没有添加到作用域中(就像它是一个函数声明一样)。

    但是,您不会在很多地方看到命名函数表达式,因为 JScript(Microsoft 的 JavaScript 引擎)获取它们 horribly and utterly wrong,在两个不同的时间创建两个单独的函数。

    【讨论】:

    • 这是否也意味着全局代码的执行是顺序的?自然,函数内部的代码不会被执行,除非函数被调用并且异步代码被异步调用(!!)。但是JavaScript程序是从上到下顺序执行的吗?
    • @Boanerghes:是的,“逐步”代码是顺序的。我已经扩展了答案以解决这个问题(呃,相当彻底)。
    • 你什么时候想要一个命名函数表达式?
    • @mplungjan:1. 递归。 2. 它曾经对调试很重要(肯定是在 2012 年编写上述内容时),因此您的函数在堆栈跟踪等中有名称。由于 ES5 在对象初始化器中的方法语法、ES2015 的推断函数名称以及超越推断函数名称规则的现代开发工具,这现在不再是一个问题。
    • 2: bar(function() { }) 传入的是函数语句,而不是函数语句的结果,不是吗?
    【解决方案2】:

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

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

    【讨论】:

    • 严格来说并非如此。 JavaScript 在许多环境中确实有起点和终点。只有在浏览器中使用时才会保持活动状态。此外,像 window.onload 这样的事件不是 JavaScript 的一部分,它们是 DOM 的一部分。
    • JavaScript 本身并不是事件驱动的。浏览器和 NodeJS 等环境中的 JavaScript 是事件驱动的,但请注意,语言 本身就是某种东西,独立于环境。
    • @AndyE:没错,我实际上赞成 thg435 的回答,以降低我自己的水平。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      你已经知道答案了

      然而,在 JavaScript 中,不需要这样的函数!

      JavaScript 是脚本语言,而 C 需要编译。

      【讨论】:

      • 你的理由是错误的。编译语言没有理由比脚本语言更需要显式入口点。事实上,存在没有明确入口点的编译语言和没有明确入口点的脚本语言。此外,这种区别甚至没有意义,因为大多数现代 JavaScript 执行引擎在执行之前编译代码(并且有 C 代码解释器)。
      猜你喜欢
      • 1970-01-01
      • 2010-10-30
      • 2010-10-14
      • 1970-01-01
      • 2013-10-05
      • 2016-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多