javascript的装载和执行
前言
为什么要采用js来create一个script标签,设置src然后append到head中,而不是直接使用script标签,这样不是更简单点吗?
javascript的装载和执行
首先,我想说一下Javascript的装载和执行。通常来说,浏览器对于Javascript的运行有两大特性:
1)载入后马上执行
2)执行时会阻塞页面后续的内容(包括页面的渲染、其它资源的下载)
于是,如果有多个js文件被引入,那么对于浏览器来说,这些js文件被被串行地载入,并依次执行。因为javascript可能会来操作HTML文档的DOM树,所以,浏览器一般都不会像并行下载css文件并行下载js文件,因为这是js文件的特殊性造成的。所以,如果你的javascript想操作后面的DOM元素,基本上来说,浏览器都会报错说对象找不到。因为Javascript执行时,后面的HTML被阻塞住了,DOM树时还没有后面的DOM结点。所以程序也就报错了。
【小结】:默认情况下,浏览器对js串行加载、css并行加载
Script 的堵塞(block)特性
Scripts without async or defer attributes, as well as inline scripts, are fetched and executed immediately, before the browser continues to parse the page. - MDN
the blocking nature of JavaScript, which is to say that nothing else can happen while JavaScript code is being executed. In fact, most browsers use a single process for both user interface (UI) updates and JavaScript execution, so only one can happen at any given moment in time. The longer JavaScript takes to execute, the longer it takes before the browser is free to respond to user input. - Nicholas C. Zakas「High Performance JavaScript 」
上面引用两段话的意思大致是,当浏览器解析DOM文档时,一旦遇到 script 标签(没有defer 和 async 属性)就会立即下载并执行,与此同时浏览器对文档的解析将会停止,直到 script 代码执行完成。出现这种堵塞行为一方面是因为浏览器的UI渲染,交互行为等都是单线程操作,另一方是因为 script 里面的代码可能会影响到后面文档的解析,比如下面的代码:
<script type="text/javascript"> document.write("The date is " + (new Date()).toDateString()); </script>
|
这个堵塞特性会严重的影响用户体验,下面是几种优化方案:
-
尽量把脚本往文档的后面放,以减少对文档的堵塞,最好放在
</body>
前面。 - 尽量把脚本按照它们的依赖关系放在一个文件中
另外,因为绝大多数的Javascript代码并不需要等页面,所以,我们可以异步载入,那么我们怎么异步载入呢?不过更好的方法是下面的非堵塞加载脚本(Nonblocking Scripts)的方法,下面的方法其实都是异步加载:
1. script的defer和async属性(延迟脚本/异步脚本)
IE自从IE6就支持defer标签,如:
< script defer type = "text/javascript" src = "./alert.js" > </ script > |
对于IE来说,这个标签会让IE并行下载js文件,并且把其执行hold到了整个DOM装载完毕(DOMContentLoaded),多个defer的<script>在执行时也会按照其出现的顺序来运行。最重要的是<script>被加上defer后,其不会阻塞后续DOM的的渲染。但是因为这个defer只是IE专用,所以一般用得比较少。
而我们标准的的HTML5也加入了一个异步载入javascript的属性:async,无论你对它赋什么样的值,只要它出现,它就开始异步加载js文件。但是, async的异步加载会有一个比较严重的问题,那就是它忠实地践行着“载入后马上执行”这条军规,所以,虽然它并不阻塞页面的渲染,但是你也无法控制他执行的次序和时机。你可以看看这个示例去感受一下。
支持 async标签的浏览器是:Firefox3.6+,Chrome 8.0+,Safari 5.0+,IE 10+,Opera还不支持(来自这里)所以这个方法也不是太好。因为并不是所有的浏览器你都能行。
2. Dynamic Script Elements (动态脚本)
方法一:动态创建DOM方式
这种方式可能是用得最多的了,原理就是使用脚本创建 script 元素,设置 src 需为要动态添加脚本的 URL,再把这个 script 添加到DOM中。有时我们需要动态脚本加载完成后再执行某些操作,这就需要我们在脚本加载完成后添加一个回调函数,这个可以通过 script 的 onload 事件实现。下面的实现代码:
实现一:
function loadjs(script_filename) { var script = document.createElement( 'script' ); script.setAttribute( 'type' , 'text/javascript' ); script.setAttribute( 'src' , script_filename); script.setAttribute( 'id' , 'coolshell_script_id' ); script_id = document.getElementById( 'coolshell_script_id' ); if (script_id){ document.getElementsByTagName( 'head' )[0].removeChild(script_id); } document.getElementsByTagName( 'head' )[0].appendChild(script); } var script = 'https://coolshell.cn/asyncjs/alert.js' ; loadjs(script); |
实现二:
function loadJS(url, callback){ var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; if(script.readyState){ // 兼容IE的旧版本 script.onreadystatechange = function(){ if(script.readyState == 'loaded' || script.readyState == 'complete'){ script.onreadystatechange = null; callback(); } } } else{ script.onload = function(){ callback(); } } document.getElementsByTagName('head')[0].appendChild(script); }
|
有时我们需要我们动态加载的脚本按照我们加载的顺序执行,但上面的实现并不能保证这一点,加载的脚本在下载完成后就会立即执行,而不会按照我们定义的顺序。要解决这个问题也不难,可以参照下面的代码:
loadJS('a.js', function(){ loadJS('b.js', function(){ loadJS('c.js', function(){ app.init(); }) }) }) |
当有大量的脚本需要动态添加时,这样写也会遇到问题;另外的解决方案是利用一些现成的库,比如 LABjs
这个方式几乎成了标准的异步载入js文件的方式,这个方式的演示请参看:示例三。这方式还被玩出了JSONP的东东,也就是我可以为script的src指定某个后台的脚本(如PHP),而这个PHP返回一个javascript函数,其参数是一个json的字符串,返回来调用我们的预先定义好的javascript的函数
方法二:按需异步载入js
上面那个DOM方式的例子解决了异步载入Javascript的问题,但是没有解决我们想让他按我们指定的时机运行的问题。所以,我们只需要把上面那个DOM方式绑到某个事件上来就可以了。
实现一:
绑在window.load事件上——示例四
你一定要比较一下示例四和示例三在执行上有什么不同,我在这两个示例中都专门用了个代码高亮的javascript,看看那个代码高亮的的脚本的执行和我的alert.js的执行的情况,你就知道不同了)
window.load = loadjs( "https://coolshell.cn/asyncjs/alert.js" ) |
实现二:
绑在特定的事件上——示例五
< p style = "cursor: pointer" onclick = "LoadJS()" >Click to load alert.js </ p > |
这个示例很简单了。当你点击某个DOM元素,才会真正载入我们的alert.js。
3. XMLHttpRequest Script Injection (XHR动态插入)
原理是利用XMLHttpReques(XHR)对象,动态获取一段JS代码,然后插入文档。
相对其他方法来说的一个优点是可以“懒执行”,也就是JS代码已经先下载好了并没有执行,可以在需要的来执行(?)(之前的动态脚本在下载后会立即执行)。实现代码:
function xhrLoadJS (url, callback){ var xhr = new XMLHttpRequest(); xhr.open('get', url, true); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ var script = document.createElement('script'); script.type = 'text/script'; script.text = xhr.responseText; eval(xhr.responseText); // 执行代码 document.body.appendChild(script); callback(); } } } xhr.send(null); }
|
缺点是不能跨域请求
参考
原文地址:https://www.cnblogs.com/kunmomo/p/12048816.html
- 糖大夫--测量流程性能监控自动化方案设计
- ReactNative调用Android原生模块
- jvm调优的工具介绍
- Python时间序列预测案例研究:巴尔的摩年度用水量
- [一对一课程] 之 设计并实现第一个JS模块?
- React Native 使用react-native-image-picker库实现图片上传功能
- 再谈Android动态链接库
- React Native之Permissions权限适配
- React Native项目实战之fetch请求并填充界面
- CocoaPods使用详解
- 2018年伊始,系统编程语言Rust为何令程序员感到兴奋?
- React Native项目实战之搭建美团个人中心界面
- React Native动画Animated详解
- iOS动画总结
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- php-7.3.6 编译安装过程
- PHP查找一列有序数组是否包含某值的方法
- python 从文件夹抽取图片另存的方法
- laravel框架使用极光推送消息操作示例
- 对pycharm 修改程序运行所需内存详解
- Python小工具之消耗系统指定大小内存的方法
- Thinkphp框架+Layui实现图片/文件上传功能分析
- PHP实现单例模式建立数据库连接的方法分析
- 解决Python2.7中IDLE启动没有反应的问题
- PHP中mysqli_get_server_version()的实例用法
- 在unittest中使用 logging 模块记录测试数据的方法
- 分享8个Laravel模型时间戳使用技巧小结
- 基于python实现名片管理系统
- 记一个OLED编程中文显示函数的坑(留意变量数据类型的范围)
- tp5框架基于Ajax实现列表无刷新排序功能示例