对于js各种加载方式我的理解
从开始学习前端到现在也有快两年的时间了,一直有个问题困扰着我,就是js在浏览器的各种加载方式。尤其之前我是在一家A的广告公司工作,对于页面广告的各种js同步异步请求涉及颇多(虽然我不是主要搞这一块),但是总是想找个时间真正弄明白这一块。之前也写过一些这块的blog,但是阐述的让自己都不满意,现在对此有了一些更深的理解,找时间总结下来。
我们常说的js加载实际上是可以分成两个阶段来理解的,一个是js下载,一个是js执行,有一个观念必须明确,就是浏览器在下载完js之后就会立即对其进行解析和执行,这个和加载方式是没有关系的,并且在执行的时候是会阻塞浏览器的其他渲染的。
现在再说js加载方式,同步加载和异步加载,这两种方式重点都在于第一阶段--js下载,下载需要80%左右的时间(见HTTP性能优化)是大头。其中,同步加载(又称阻塞模式)是我们最常使用的方式,js之所以要同步,就是因为js中可能有输出document内容,修改dom,重定向等行为,所以默认同步执行才是安全的,并且建议把script放到页面尾部/body之前,这样可以减少这种阻塞行为。
再说异步加载,即浏览器在下载js的同时,还会继续进行后续页面的处理,这是一种非阻塞的方式,常用方法就是用js动态的去创建script。但是这种方式还是会影响window.onload的事件触发,这个你可以让他在window.onload执行之后处理就好了。现在html5增加了async属性,可以支持脚本的异步下载。但是这里面还是有一个问题的,就是异步加载并不保证顺序执行。
async 和 defer 标注的 script 都不会暂停HTML解析就立刻被下载,两者都支持onload事件回调来解决需要该脚本来执行的初始化。两者的区别在于执行时的不同:async脚本在script文件下载完成后会立即执行,并且其执行时间一定在 window的load事件触发之前。这意味着多个async脚本很可能不会按其在页面中的出现次序顺序执行。与此相对,浏览器确保多个 defer 脚本按其在HTML页面中的出现顺序依次执行,且执行时机为DOM解析完成后,document的DOMContentLoaded 事件触发之前。
如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)如果不使用 async且defer="defer":脚本将在页面完成解析时执行,如果既不使用 async,也不使用defer:在浏览器继续解析页面之前,立即读取并执行脚本
我们为了将js模块化用到类似requireJs之类的插件。摘自requireJs中文网的一段话
"理想状况下,每个加载的脚本都是通过define()来定义的一个模块;但有些"浏览器全局变量注入"型的传统/遗留库并没有使用define()来定义它们的依赖关系,你必须为此使用shim config来指明它们的依赖关系。 如果你没有指明依赖关系,加载可能报错。这是因为基于速度的原因,RequireJS会异步地以无序的形式加载这些库。"总结来说就是RequireJS(在define定义好的情况下)是异步加载,顺序调用。
这里查一个题外话,为了 “模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染。它可以显式地列出其依赖关系,并以函数(定义此模块的那个函数)参数的形式将这些依赖进行注入,而无需引用全局变量。RequireJS的模块是模块模式的一个扩展,其好处是无需全局地引用其他模块。”
最后要再说一下js按需加载的概念,其实这里边按需加载和按需执行是两个不同的概念,他们考虑的阶段是不同的。举个栗子,我们说图片的加载根据scroll bar的位置来不断的加入,而不是一次性加入,这个对页面性能有很大的提高,但是这个是html的按需加载。js是类似的,如果你确定你的一些脚本是在button点击或者类似事件中才用,你就可以在这个时间再加载js,但是你要知道这个下载过程是要有时间延迟的。两者间的平衡是要考虑的。