JS程序的解析过程分为编译和执行两个阶段。
编译也叫做JS预处理,编译器将JS脚本代码转换成字节码,执行期间,解释器借助执行期环境将字节码生成机械码并按顺序执行。

预编译
JS是解释型语言而非编译型语言,所以代码在执行期才被解析器一行一行地动态编译和执行而非在执行之前完成编译。JS边编译边执行。

JS引擎在预编译期对所有声明的变量和函数进行处理。所以JS解释器执行以下脚本的时候不会报错。

alert(a); //返回undefined
     var a = 1;
     alert(a); //返回值1
     //由于变量的声明在预编译期被处理,所以在执行期对所有代码来说都是可见的。
     //而变量的初始化过程发生在执行期而非预编译期,执行期JS解释器按照代码顺序进行解释执行,在第一行代码处a尚未被初始化赋值,
     //所以JS解释器会使用默认值undefined

       同理,在函数声明之前调用函数也是合法的。

f(); //返回值1 
     function f() {
         alert(1);
     }

        但是按照下面方式声明函数,JS解释器会报语法错误。

f(); //返回语法错误
     var f = function() {
         alert(1);
     }

    虽然变量和函数的声明可以放在文档的任意位置,但是良好的编程习惯应该是在JS代码之前声明全局变量和函数并为全局变量初始化赋值。
在函数内部也应该先声明变量然后再引用。

JS代码是按块被引擎预编译和解释执行的,所谓块就是<script>标签分割的代码段。下面两个<script>标签分别代表两个代码块。

<script>
         var a = 1;        
     </script>
     
     <script>
         function f() {
             alert(a);
         }
     </script>

    由于JS按块执行,在一个JS块中调用后面的块中声明的变量或者函数会报语法错误。
    例

<script>
             alert(a); //提示语法错误,变量a没定义,对象f找不到
             f();
         </script>
         
         <script>
             var a = 1;
             function f() {
                 alert(1);
             }
         </script>

虽说JS按块执行,但是不同的块都属于同一个全局作用域,即块之间的变量和函数是可以共享的。

我们可以借助事件机制改变JS执行顺序。
当文档流完全被加载完毕,再次访问就不会出现语法错误,比如将访问第二个代码块中的变量和函数的代码放在页面初始化事件函数中,就不会提示语法错误。、

<script>
         window.onload = function() { //页面初始化(加载)完毕之后才执行页面初始化事件处理函数
             alert(a);
             f();
         }
     </script>
     
     <script>
         var a = 1;
         function f() {
             alert(a+a);
         }
     </script>
     //除了页面初始化事件外,还可以通过各种交互事件如鼠标事件、键盘事件、时钟触发等改变JS代码执行顺序。