JavaScript因为互联网而生,紧随着浏览器的出现而问世。回顾它的历史,就要从浏览器的历史讲起。
1990年底,欧洲核能研究组织(CERN)科学家Tim Berners-Lee,在全世界最大的电脑网络——互联网的基础上,发明了万维网(World Wide Web),从此可以在网上浏览网页文件。最早的网页只能在操作系统的终端里浏览,也就是说只能使用命令行操作,网页都是在字符窗口中显示,这当然非常不方便。
1992年底,美国国家超级电脑应用中心(NCSA)开始开发一个独立的浏览器,叫做Mosaic。这是人类历史上第一个浏览器,从此网页可以在图形界面的窗口浏览。
1994年10月,NCSA的一个主要程序员Marc Andreessen联合风险投资家Jim Clark,成立了Mosaic通信公司(Mosaic Communications),不久后改名为Netscape。这家公司的方向,就是在Mosaic的基础上,开发面向普通用户的新一代的浏览器Netscape Navigator。
1994年12月,Navigator发布了1.0版,市场份额一举超过90%。Netscape公司很快发现,Navigator浏览器需要一种可以嵌入网页的脚本语言,用来控制浏览器行为。当时,网速很慢而且上网费很贵,有些操作不宜在服务器端完成。比如,如果用户忘记填写“用户名”,就点了“发送”按钮,到服务器再发现这一点就有点太晚了,最好能在用户发出数据之前,就告诉用户“请填写用户名”。这就需要在网页中嵌入小程序,让浏览器检查每一栏是否都填写了。管理层对这种浏览器脚本语言的设想是:功能不需要太强,语法较为简单,容易学习和部署。那一年,正逢Sun公司的Java语言问世,市场推广活动非常成功。Netscape公司决定与Sun公司合作,浏览器支持嵌入Java小程序(后来称为Java applet)。但是,浏览器脚本语言是否就选用Java,则存在争论。后来,还是决定不使用Java,因为网页小程序不需要Java这么“重”的语法。但是,同时也决定脚本语言的语法要接近Java,并且可以支持Java程序。这些设想直接排除了使用现存语言,比如Perl、Python和TCL。
1995年,Netscape公司雇佣了程序员Brendan Eich开发这种网页脚本语言。Brendan Eich有很强的函数式编程背景,希望以Scheme语言(函数式语言鼻祖LISP语言的一种方言)为蓝本,实现这种新语言。
1995年5月,Brendan Eich只用了10天,就设计完成了这种语言的第一版。它是一个大杂烩,语法有多个来源:
- 基本语法:借鉴C语言和Java语言。
- 数据结构:借鉴Java语言,包括将值分成原始值和对象两大类。
- 函数的用法:借鉴Scheme语言和Awk语言,将函数当作第一等公民,并引入闭包。
- 原型继承模型:借鉴Self语言(Smalltalk的一种变种)。
- 正则表达式:借鉴Perl语言。
- 字符串和数组处理:借鉴Python语言。
Netscape公司的这种浏览器脚本语言,最初名字叫做Mocha,1995年9月改为LiveScript。12月,Netscape公司与Sun公司(Java语言的发明者和所有者)达成协议,后者允许将这种语言叫做JavaScript。这样一来,Netscape公司可以借助Java语言的声势,而Sun公司则将自己的影响力扩展到了浏览器。
1995年12月4日,Netscape公司与Sun公司联合发布了JavaScript语言。
1996年3月,Navigator 2.0浏览器正式内置了JavaScript脚本语言。
CSS和JS的放置顺序
当浏览器从服务器接收到了html文档,并把html在内存中转换成DOM树,在转换过程中如果发现某个节点上引用了CSS或者image,就会再发一个request去请求CSS或image,然后继续执行下面的转换,而不需要等待request的返回,当request返回后,只需要把返回的内容放入到DOM树中对应的位置就可以了。但当引用了JS的时候,浏览器发送一个JS request就会一直等待该request的返回。
- CSS应该放在页面顶部的
<head>
标签中
由于Render Tree是由DOM树和CSSOM树组合成的,html页面需要等到CSS解析完后才能完成渲染,所以CSS应放在head标签内,优先下载解析,以避免页面元素由于样式缺失造成瞬间的白页或者给用户闪烁感。
- JS应该放在
<body>
的底部
因为浏览器需要一个稳定的dom树结构,而且js中很有可能有代码直接改变了dom树结构,浏览器为了防止出现js修改dom树,需要重新构建dom树的情况,所以就会阻塞其他的下载和呈现。
将JavaScript放在head内和body底部的区别也在于此,放在head里面,由于浏览器发现head里面有JavaScript标签就会暂时停止其他渲染行为,等待JavaScript下载并执行完成才能接着往下渲染,而这个时候由于在head里面这个时候页面是白的;如果将JavaScript放在页面底部,render Tree已经完成大部分,所以此时页面有内容呈现,即使遇到JavaScript阻塞渲染,也不会有白屏出现
- 如果CSS和JS都在
<head>
标签内,则应将JS放在所有CSS的前面
JS的执行有可能依赖最新样式。比如,可能会有var width=$('#id').width
,这意味着,JS代码在执行前,浏览器必须保证在此JS之前的所有CSS(无论外链还是内嵌)都已下载和解析完成。
把JS放在CSS后会导致页面阻塞,去等待CSS的下载。
另外如果要在head
引入JS尽量将JS内嵌。
-
async
和defer
也可以达到不阻塞渲染的效果
带有 defer
属性的<script>
标签可以放置在文档的任何位置。对应的 JavaScript 文件将在页面解析到<script>
标签时开始下载,但不会执行,直到 DOM 加载完成,即onload事件触发前才会被执行。当一个带有defer
属性的 JavaScript 文件下载时,它不会阻塞浏览器的其他进程,因此这类文件可以与其他资源文件一起并行下载。
白屏和FOUC
- 不同的浏览器对于css和HTML的渲染方式不同,当浏览器先加载css再加载HTML时,会造成白屏;当浏览器先计算HTML,等css加载完重新对样式修改,就会造成FOUC无样式内容闪烁。
- 使用@import时,即使 CSS 放入 link, 并且放在头部,也可能出现白屏
- 把js文件放入页面顶部而未使用defer或async延迟或异步加载js文件,从而阻塞html与css的加载也会导致白屏
repaint reflow
repaint主要是针对某一个DOM元素进行的重绘,reflow则是回流,针对整个页面的重排。
- 在性能优先的前提下,性能消耗 reflow大于repaint。
- repaint是某个DOM元素进行重绘;reflow是整个页面进行重排,也就是页面所有DOM元素渲染。
- 尽量避免多次repaint和reflow
- 尽可能在DOM末梢通过改变class来修改元素的style属性:尽可能的减少受影响的DOM元素。
- 避免设置多项内联样式:使用常用的class的方式进行设置样式,以避免设置样式时访问DOM的低效率。
- 设置动画元素position属性为fixed或者absolute:由于当前元素从DOM流中独立出来,因此受影响的只有当前元素,元素repaint。
- 牺牲平滑度满足性能:动画精度太强,会造成更多次的repaint/reflow,牺牲精度,能满足性能的损耗,获取性能和平滑度的平衡。
- 避免使用table进行布局:table的每个元素的大小以及内容的改动,都会导致整个table进行重新计算,造成大幅度的repaint或者reflow。改用div则可以进行针对性的repaint和避免不必要的reflow。
- 避免在CSS中使用运算式:学习CSS的时候就知道,这个应该避免,不应该加深到这一层再去了解,因为这个的后果确实非常严重,一旦存在动画性的repaint/reflow,那么每一帧动画都会进行计算,性能消耗不容小觑。
异步加载
- defer 属性规定是否对脚本执行进行延迟,直到页面加载为止。
<script type="text/javascript" defer="defer">
alert(document.getElementById("p1").firstChild.nodeValue);
</script>
- async 属性规定一旦脚本可用,则会异步执行。
<script type="text/javascript" src="demo_async.js" async="async"></script>