一、JSON
json是一种传输数据的格式(以对象为样板,本质上就是对象,但用途有区别,对象就是本地用的,json是用来传输的)
JSON.stringify(); // json => string 通过这个方法,可以将json格式的数据转换成字符串格式。【传给后端使用】
JSON.parse(); // string => json 通过这个方法,可以将字符串格式的数据转换成json(对象)格式。【传给前端使用】
二、异步加载
js加载的缺点:
1、加载工具方法没必要阻塞文档,过多js加载会影响页面效率,一旦网速不好,那么整个网站将等待js加载,而不进行后续渲染等工作。
2、有些工具需要按需加载,用到在加载,不用不加载。
js异步加载的3种方案
// 1、defer异步加载
// defer异步加载,但要等到dom文档全部解析完才会被执行。只有IE能用,也可以将代码写到内部。加载时不会阻塞文档解析
// 2、async异步加载
// 加载完就执行,async只能加载外部脚本,不能把js写在script标签里。加载时也不阻塞文档解析。
// 3、创建script,插入到DOM中,加载完毕后callBack 【常用】
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<!-- 加上defer或者async属性后,该js变成异步加载的方式进行加载,不会阻塞文档继续解析 -->
<!-- <script src="js/Tools.js" type="text/javascript" charset="utf-8" defer ></script> -->
<!-- <script src="js/Tools.js" type="text/javascript" charset="utf-8" async ></script> -->
</head>
<body>
<script type="text/javascript">
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "js/Tools.js"; // 执行到这一步,会异步下载脚本,不会执行
document.head.appendChild(script); // 当追加到DOM中才开始执行(解析脚本)
</script>
</body>
</html>
为了验证tools.js脚本异步下载,我们在tools.js中添加一个test函数。
// Tools.js
function test(){
console.log("hello world");
}
紧接着在将Tools.js添加至DOM中后执行
<script type="text/javascript">
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "js/Tools.js"; // 执行到这一步,会异步下载脚本,不会执行
document.head.appendChild(script); // 当追加到DOM中才开始执行(解析脚本)
test(); // 报错 Uncaught ReferenceError: test is not defined
</script>
为什么会出现报错?倘若延迟1秒test()执行会发现并不会报错。
<script type="text/javascript">
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "js/Tools.js"; // 执行到这一步,会异步下载脚本,不会执行
document.head.appendChild(script); // 当追加到DOM中才开始执行(解析脚本)
setTimeout(function(){ // 1秒之后执行
test(); // hello world
},1000);
</script>
综上代码可以得知,下载需要发送请求,响应后返回资源。在这个过程中,又因为是异步下载,不会阻塞页面解析,test()执行时,js还未下载完毕。
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "js/Tools.js"; // 执行到这一步,会异步下载脚本,不会执行
// 【解决方法】添加onload事件,等待脚本完全下载后执行
// IE
if(script.readyState){
script.onreadystatechange = function() {
if (script.readyState == "complete" || script.readyState == "loaded") {
test();
}
}
}else {
script.onload = function() { // safari、chrome、firefox、opera
test();
}
}
document.head.appendChild(script); // 当追加到DOM中才开始执行(解析脚本)
接下来简化代码,把第三种异步加载方式封装成一个函数:loadScript();
function loadScript(url, callBack) {
var script = document.createElement("script");
script.type = "text/javascript";
if (script.readyState) {
script.onreadystatechange = function() { // Ie
if (script.readyState == "complete" || script.readyState == "loaded") {
callBack();
}
}
} else {
script.onload = function() {
callBack();
}
}
script.src = url; // 执行到这一步,会异步下载脚本,不会执行
document.head.appendChild(script); // 当追加到DOM中才开始执行(解析脚本)
}
// loadScript("js/Tools.js", test); // 报错 解析:函数里边异步加载的代码等到函数执行完时,开始调用,而此时解析到test时,函数loadScript()还没有开始执行,发生在文件加载之前。因此报错
loadScript("js/Tools.js", function() { // 这里启用一个匿名回调函数引用,在函数内部来执行该功能函数
test();
});
倘如把外部工具库写成一个对象。例如:
var Tools = {
addEvent: function(elem, eventType, handler) {
// 函数体
},
removeEvent: function(element, eventType, handler) {
// 函数体
},
stopBubble: function(event) {
// 函数体
},
loadScript(url, callBack){
// 函数体
},
test(){
// 函数体
}
// ...
}
当使用异步加载时:
function loadScript(url, callBack) {
var script = document.createElement("script");
script.type = "text/javascript";
if (script.readyState) {
script.onreadystatechange = function() { // Ie
if (script.readyState == "complete" || script.readyState == "loaded") {
Tools[callBack]();
}
}
} else {
script.onload = function() {
Tools[callBack]();
}
}
script.src = url; // 执行到这一步,会异步下载脚本,不会执行
document.head.appendChild(script); // 当追加到DOM中才开始执行(解析脚本)
}
loadScript("js/Tools.js", "test"); // 解析: 这里可以以对象属性(字符串)的方式进行传递
三、浏览器加载时间线(浏览器页面的加载过程)
1、创建Document对象,开始解析web页面。解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中。这个阶段document.readyState = "loading"
2、遇到link外部css,创建线程加载,并继续解析文档
3、遇到script外部js,并且没有设置async、defer,浏览器加载并阻塞,等待js加载完成并执行该脚本,然后继续解析文档
4、遇到script外部js,并且设置有async、defer,浏览器创建线程加载,并继续解析文档。对于async属性的脚本,脚本加载完成后立即执行。(异步禁止使用document.write())
5、遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档。
6、当文档解析完成,document.readyState = "interactive"
7、文档解析完成后,所有设置有defer的脚本会按照顺序执行。(注意:async的不同,但同样禁止使用document.write())
8、document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转换为事件驱动阶段
9、当所有async的脚本加载完成并执行后、img等加载完成后,document.readyState = "complete",window对象触发load事件
10、从此,以异步响应方式处理用户输入、网络事件等。
浏览器在加载页面的时候回用到GUI渲染线程和javascript引擎线程。其中GUI渲染线程负责渲染浏览器界面的HTML元素,Javascript引擎线程主要负责处理javascript脚本程序。由于javascript在执行过程中还可能改动界面结构和样式,因此它们之间被设计为互斥关系。也就是说,当javascript引擎执行时,GUI线程会被挂起。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
// 当文档解析完成 DOMContentLoaded 事件就触发,比onload事件等到文档加载并执行完成后触发效率更高。注意:DOMContentLoaded 事件只在addEventListener绑定事件身上生效
document.addEventListener("DOMContentLoaded", function() {
var div = document.getElementsByTagName("div")[0];
console.log(div);
})
// 当页面所有脚本图片加载完成并执行后才触发,效率低 (不建议用)
// window.onload = function(){
// var div = document.getElementsByTagName("div")[0];
// console.log(div);
// }
</script>
</head>
<body>
<div></div>
<script type="text/javascript">
console.log(document.readyState);
document.onreadystatechange = function() {
console.log(document.readyState);
}
</script>
</body>
</html>
控制台打印的顺序结果:
四、总结
对于性能要求较高,需要快速将内容呈现给用户的网页,常常会将 javascript 脚本放在<body>的最后面。这样可以避免资源阻塞,页面得以迅速展示。我们还可以使用defer、async、preload等属性来标记<script>标签,来控制 javascript 的加载顺序。