JavaScript执行顺序

JavaScript执行顺序

JavaScript(以下简称js) 是一种描述型脚本语言,由浏览器进行动态的解析与执行。由于js编写位置比较灵活,所以处在不同位置的js代码执行顺序也是不同的。js和其他编程语言相比比较随意,所以js代码中充满各种奇葩的写法。苦逼的公司造就苦逼程序员全栈工程师,工作中总是经常自己写些简单的前端页面,经常被js的执行顺序袭扰,索性找个大块儿时间好好学习总结下,理解各型各色的写法,希望能对js的语言特性有深入的理解。

函数的声明和调用

函数的定义方式大体有以下两种,浏览器对于不同的方式有不同的解析顺序。

  1. 使用function关键字声明一个函数,再指定一个函数名,叫函数声明
    1
    2
    3
    function Fn1(){ 
    alert("Hello World!");
    }
  2. 使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。
    1
    2
    3
    var Fn2 = function(){ 
    alert("Hello World!");
    }
    如果遇到函数声明,则进行预处理(类似C语言的编译);如果遇到函数表达式,则只是将函数赋值给一个变量,不进行预处理,待调用的时候再进行处理。
    @(#函数声明)
    1
    2
    3
    4
    Fn1();
    function Fn1(){
    alert("Hello World!");
    }
    👆↑ 这段代码()可以正常运行,弹出”Hello World!”警告对话框。浏览器对Fn1()进行了预处理,再从Fn1();开始运行。

再如:
@(#函数表达式)

1
2
3
4
Fn2();
var Fn2 = function(){
alert("Hello World!");
}

👆↑ 这段代码会在Chrome浏览器报错,DevTools会提示 Uncaught TypeError: Fn2 is not a function。浏览器未对Fn2进行预处理,依序执行,所以报错Fn2不是函数。

js代码块及引用的外部js文件的处理

这里js代码块指的是一对<script type="text/javascript" charset="utf-8">some js code</script>标签中间包裹着的js代码;外部引用的文件指的是<script src="/javascripts/application.js" type="text/javascript" charset="utf-8" async defer></script>中src属性中路径下的js文件中的代码。

页面加载过程中,浏览器会对每个js代码块或文件进行独立扫描。所以在一个块或文件中,函数可以先调用,再进行函数声明(#函数声明);但两个块中,函数声明所在的块必须在函数调用所在的块之前。略绕,,看例子👇↓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script type="text/javascript">
Fn();
</script>

<script type="text/javascript">
function Fn(){
alert('Hello World!');
}
</script>
---------------------
//Uncaught ReferenceError: Fn is not defined
//报错 Fn 未被定义
//两个script代码块换下位置就可以正常运行了~
---------------------

需要注意的是,外部引用的js文件也存在这种情况,,js文件中定义的变量和函数只能在之后引用。即js.js文件中以函数声明的方式定义了fn..👇↓

1
2
3
4
5
6
7
8
9
<script type='text/javascript'>
fn();
</script>
<script scr='tesTjs.js' type='text/javascript'></script>
---------------------
//Uncaught ReferenceError: fn is not defined
//报错 fn 未被定义
//两个script代码块换下位置就可以正常运行了
---------------------

重复定义函数会覆盖前面的定义

这和变量的重复定义是一样的,,👇↓

1
2
3
4
5
6
7
8
function fn(){ 
alert(1);
}
function fn(){
alert(2);
}
fn();
// 弹出:“2”

但如果是这样呢,,👇↓

1
2
3
4
5
6
7
8
fn(); 
function fn(){
alert(1);
}
function fn(){
alert(2);
}
// 还是弹出:“2”

body标签内的onload属性与body标签之间函数的执行

body内部的函数会先于onload的函数执行,测试代码:👇↓

1
2
3
4
5
6
7
8
9
10
11
12
13
//html head... 
<script type="text/javascript">
function fnOnLoad(){
alert("I am outside the Wall!");
}
</script>
<body onload="fnOnLoad();">
<script type="text/javascript">
alert("I am inside the Wall..");
</script>
</body>
//先弹出“I am inside the Wall..”;
//后弹出“I am outside the Wall!”

bodyonload事件触发条件是body内容加载完成,,所以在加载body内容的过程中执行了<script>标签中的alert("I am inside the Wall..");。至此body标签中的内容加载完成,触发body标签中onload函数,执行fnOnLoad方法。

script标签放在什么位置

scripts会引发阻塞并行下载的问题。HTTP/1.1 specification建议每次不要同时从一个hostname下载超过两个的组件。如果你把网站上引用的图片放在不同hostname上,那么每次加载该网页时,可以下载同时两个以上的图片资源。然而,当引用的外部js文件正在下载时,无论其他资源是否与该js文件处于同一hostnamme,都不能(并行)下载。所以yahoo建议将script放在尾部,这样能加速网页加载。^1

将script放在尾部的缺点,是浏览器只能先解析完整个HTML页面,再下载JS。而对于一些高度依赖于JS的网页,就会显得慢了。所以将script放在尾部也不是最优解,最优解是一边解析页面,一边下载JS。
所以,出现了一种更modern的方式:使用async和defer。80%的现代浏览器都认识async和defer属性[3],这两个属性能让浏览器做到一边下载JS(还是只能同时下载两个JS),一边解析HTML。他的优点不是增加JS的并发下载数量,而是做到下载时不block解析HTML。^2

1
2
<script type="text/javascript" src="path/to/script1.js" async></script>  
<script type="text/javascript" src="path/to/script2.js" async></script>

async属性的script会异步执行,只要下载完就执行,这会导致script2.js可能先于script1.js执行(如果script2.js比较大,下载慢)。defer就能保证script有序执行,script1.js先执行,script2.js后执行。


参考链接:
http://www.jb51.net/article/36755.htm
https://developers.google.com/speed/docs/insights/BlockingJS
https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery
https://developers.google.com/speed/docs/insights/PrioritizeVisibleContent