Json、异步加载、JS加载时间线
json
JSON是一种传输数据的格式
(以对象为样板,本质上就是对象,但用途有区别,对象就是本地用的,json是用来传输的)
早期前后端以xml的格式发送数据
之前讲过xml可以自定义标签(标签名就是属性名 内容就是属性值) 但是html不行
<student>
<name>deng</name>
<age>40</age>
</student>
现在传的数据格式就是对象 运用到数据传输里面换了个名字叫json
为了区分对象和json写法 json里面强制规定属性名必须加双引号
传送对象给后端会识别不了 因为只能识别二进制文本 所以传送的对象是文本形式 也就是字符串 后端会有特殊的语法把字符串的json转换成正常的json
'{
"name" : "Jason";
"age" : 123;
}'
前端把json变成字符串的json的方法
json是一种静态类
JSON.stringify(); json — > string
数据传输给后台json — > string
// 注意 属性名要加引号 虽然不加stringify转换时也会帮我们加上
var obj = {
"name" : "abc",
"age" : 123
}
var str = JSON.stringify(obj);
JSON.parse(); string—>json
数据后台传输给前台string—>json
JSON.parse(str) // 把字符串形式的对象转变成正常的对象
renderTree生成的过程
这个过程是同步加载 也就是domTree生成之后才会生成cssTree 然后两者再组成renderTree
domTree
浏览器中的渲染引擎会根据html结构和css样式一行一行的绘制页面
div {
width:100px;
height:100px;
background-color:red;
}
<div></div>
<span></span>
<strong>
<em></em>
</strong>
系统内核的内部是不指代div和div的样式之间的关系,不会让他俩产生连接,所以怎么产生联系呢?
浏览器的内核会对页面进行一步步检索,先识别html代码,会形成domTree
domTree符合深度优先原则 就是有下级就会就会把下级都先遍历完再遍历别的标签 (广度优先就是按照层级结构一层一层的遍历)
注意:img等这种带文件的标签,只有系统看到了这个标签,就会先挂到domTree上 同时开启一个异步线程来下载对应的文件。
<div></div>
<img src="xxx">
<iframe src="xxx"></iframe>
<img src="yyy">
<span></span>
<strong></ strong>
domTreed的完成代表的是dom节点的解析完毕,不代表dom节点的加载完毕,下载完对应的文件才算加载完毕。
解析完一定比加载完更快
cssTree
domTree解析完毕后等cssTree解析
cssTree和domTree结构类似,也是深度优先。
renderTree
dmTree和cssTree都加载完毕之后会拼在一起会生成新的树renderTree
domTree + cssTree = renderTree
浏览器绘制页面
只有当renderTree真正形成完了之后,渲染引擎才会真正的开始绘制页面
reflow 重排(重构)
重构产生原因
js可以动态的改变domTree和cssTree的一些东西,标签和样式都能间接修改。但是如果改了这两棵树的节点,renderTree就需要重新构建,renderTree重新构建就意味着页面会从第一行开始重新绘制,很浪费效率。
dom优化就是避免做一些无效的事儿,所以尽量减少dom节点的改动,非要做也尽量一次改完,重新再加载一次就好。
触发重构的情况(尽量避免)
- dom节点的删除,添加
- dom节点的宽高变化,位置变化,display none ——> block
- offsetWidth,offsetLeft (这是因为查看的时候 系统会重构一遍 确保查出来的宽高是实时的)
repaint 重绘
重绘的只是一部分,不是一整个页面。
给dom节点改变背景颜色,如果是基于css的颜色重新构建的话,只会把cssTree上的那一小部分改掉,然后改rennderTree上的对应那一小部分,影响比较小。
小知识
JS的下载过程和执行过程为什么不能和html和css并行去做(异步)
因为js会动态修改html和css,还没等你页面绘制完呢,js动态把html和css给改了,那不就又得重新绘制,所以js的下载过程和执行过程不能和html和css并行去做,所以要同步加载,要么执行完了再绘制,要么绘制完再执行。
JS为什么是单线程(同步加载)
因为JS可以修改页面,如果多线程(异步)的话,一个线程去删除节点,一个线程去添加一个节点,那无法决定到底听哪个线程的。
JS异步加载
js加载的缺点︰
加载工具方法没必要阻塞文档,因为js加载会影响页面效率,一旦网速不好,那么整个网站将等待js加载而不进行后续渲染等工作。
有些工具方法需要按需加载,用到再加载,不用不加载。
javascript异步加载的三种方案
defer 异步加载,但要等到dom文档全部解析完才会被执行。
js会和html和css一起并行的下载,各下载各的。
// 写上defer这个js就变成了异步加载
<script type="text/javascript" src="tools.js" defer="defer">
// defer内部可以写代码 async不可以
<div></div>
</script>
async异步加载,加载完就执行
,async只能加载外部脚本
,不能把js写在script标签里。
// 写上async这个js就变成了异步加载
<script type="text/javascript" src="tools.js" async="async">
// 不能在这内部写东西 只能加载外部脚本
</script>
asychronous javascript and xml ---> ajax(阿贾克斯的缩写)
上面两种方法执行时也不阻塞页面
创建script,插入到DOM中,加载完毕后callBack
由于想异步加载,又想把js写在script标签中,所以产生了这种方法。
// 不插入到DOM中也能看到network下载的html文件和demo.js(下载了 但是不展示 插入到dom中 也就是出现效果的时候会展示)
<script type="text/javascript">
var script = document.createElement('script');
script.type = "text/javascript";
script.src="demo.js";
// document.head.appendChild(script);
</script>
// demo.js
alert('张杰很帅')
中文名会乱码
<script type="text/javascript">
var script = document.createElement('script');
script.type = "text/javascript";
script.src="demo.js";
// 插入到dom中就能看到效果
document.head.appendChild(script);
</script>
onload
提示我们外部js下载完了,可以调用了
没下载完就执行会报错
// demo.js
function test() {
console.log('张杰');
}
<script type="text/javascript">
var script = document.createElement('script');
script.type = "text/javascript";
script.src="demo.js";
document.head.appendChild(script);
test();
</script>
报错是因为程序执行是非常快的,src还在请求demo.js资源的过程就已经执行到test()了,所以报错,因为demo.js还没下载完呢。
onload兼容性非常好
<script type="text/javascript">
var script = document.createElement('script');
script.type = "text/javascript";
script.src="demo.js";
// 监听下载完成之后才会执行test();
script.onload = function () {
test();
}
document.head.appendChild(script);
</script>
兼容IE的方法readyState的写法
<script type="text/javascript">
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "demo. js";
if (script.readyState) { // //Ie独有的方法readyState
script.onreadystatechange = function () {
if (script.readyState == "complete" || script.readyState == "loaded" ) {
test();
}
}
} else { // onload是非IE的方法
script.onload = function () {//Safari chrome firefoxopera
test();
}
}
</script>
封装兼容IE的函数
// demo.js
function test () {
console.log('张杰');
}
<script type="text/javascript">
// src等着用户去传 callback回调函数 用户传了就会执行
function loadScript(url,callback) {
var script = document.createElement('script');
script.type = "text/javascript";
if (script.readyState) { // Ie独有的方法readyState
script.onreadystatechange = function () {
if (script.readyState == "complete" || script.readyState == "loaded") {
callback();
}
}
} else { // onload是非IE的方法
script.onload = function () {
callback();
}
}
// src写在这儿是防止文件下载过快 所以先绑定事件 再加载文件
// 防止IE的onreadystatechange无法监听到change 而不执行循环
script.src = url;
document.head.appendChild(script);
}
// 下载demo.js这个文件 执行test函数 但是会报错
// 因为代码解析执行到loadScript的时候不会看函数具体代码
// 会继续往下执行loadScript ('demo.js',test);
// 但是文件还没加载过来 所以test未定义 然后报错
// loadScript ('demo.js',test);
// 通过函数引用解决 解析时也不会读函数体内的代码 只有执行时才会读
loadScript ('demo.js',function () {
test(); // 解析时不读 执行时才读
});
</script>
上面报错解决办法二
// demo.js
var tools = {
test : function () {
console.log('a');
},
demo : function () {
console.log('a');
}
}
<script type="text/javascript">
function loadScript(url,callback) {
var script = document.createElement('script');
script.type = "text/javascript";
if (script.readyState) {
script.onreadystatechange = function () {
if (script.readyState == "complete" || script.readyState == "loaded") {
// 调用tools的test方法(callback是传入的参数)
tools[callback]();
}
}
} else {
script.onload = function ()
// 调用tools的test方法
tools[callback]();
}
}
script.src = url;
document.head.appendChild(script);
}
// 以字符串形式传入
loadScript ('demo.js',"test");
</script>
JS加载时间线
浏览器运行页面的时候会初始化js的功能,当初始化初始完js功能开始,js发生它的作用的那一刻开始,开始记载着一系列浏览器接下来要发生的过程,每一步的顺序的事儿,这局势js加载时间线。
记录的就是执行顺序,顺序的起始点就是创建Document对象,有了Document才意味着js初始化完了
js时间线
-
创建Document对象,开始解析web页面。
解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中。这个阶段document.readyState = "loading'。
-
遇到link外部css,创建异步线程加载,并继续解析文档。
-
遇到script外部js,并且
没有设置async,defer
,浏览器加载,并阻塞,等待js加载完成并执行该脚本,然后继续解析文档。
没有设置异步加载,所以是同步解析,需要外部js加载完再继续解析。
-
遇到script外部js,并且
设置有async,defer
,浏览器创建异步线程加载,并继续解析文档。对于async属性的脚本,脚本加载完成后立即执行。(defer需要解析完才能执行)
(异步禁止使用document.write() 不然清空所有)document.write()
<div style="width:100px;height:100px;background-color:red;"> </div> <script type="text/javascript"> document.write('a'); // 这么写会把a文档流输入到页面中 </script>
<div style="width:100px;height:100px;background-color:red;"> </div> <script type="text/javascript"> window.onload = function () { // 页面加载完执行 // 加载完之后再执行会把之前的文档流全清空 换上自己的内容 狠到把script都删了 document.write('a'); } </script>
-
遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档。
-
当文档解析完成
(domTree形成)
,document.readyState = “interactive”(interactive 活跃)
。<script type="text/javascript"> // 打印的时候domTree还没构建完毕 所以打印最原始的loading console.log(document.readyState); // 监听readyState改变 document.onreadystatechange = function () { // 打印 interactive 和 complete console.log(document.readyState); } </script>
-
文档解析完成后
(domTree形成)
,所有设置有defer的脚本会按照顺序执行。(注意与async的不同,但同样禁止使用document.write(); -
document对象触发DOMContentLoaded事件
,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段。<script type="text/javascript"> console.log(document.readyState); document.onreadystatechange = function () { console.log(document.readyState); } document.onDOMContentLoaded = function () { console.log('a'); } document.addEventListener('DOMContentLoaded', function () { // DOMContentLoaded只在addEventListener上绑定有效果 console.log('a'); }, false); </script>
-
当所有async的脚本加载完成并执行后、img等加载完成后,
也就是所有都加载完了之后
,document.readyState = ‘complete’,window对象触发load事件
。<script type="text/javascript"> window.onload = function () { console.log(document.readyState); } </script>
-
从此,以异步响应方式处理用户输入、网络事件等。