文章目录

  • JSON传输格式
  • 浏览器渲染页面过程
  • JS异步加载
  • 时间线


JSON传输格式

json是一种传输数据的格式(以对象为样板,本质上即使对象,但是用途有区别,对象就是本地用的,json是用来传输的)

对象的属性加不加双引号都可以,但是json规定 json格式的属性名必须加上双引号

JSON是一个静态类(类似于Math),不用去构造它,它自己就有这些方法。

  • JSON.stringifg(); json—>string
  • JSON.parse(); string—>json
var obj = {
	   "name" : "abc",
       "age"  : 123
  }
var str = JSON.stringify(obj);
console.log(str);//"{"name":"abc","age":123}"

console.log(JSON.parse(str));//Object{name: "abc", age: 123}

前后端传输的时候传的都是json格式的字符串

浏览器渲染页面过程

第一步,首先是建立一个dom树,它是解析所有的dom节点(深度遍历的方法)

解析dom节点就是解析到哪个dom节点就把它挂到dom树上

像碰到< img src = “XXX”/>这样的还是只把img节点挂到dom树上,意思就是说dom树的构建完成就是所有dom节点的解析完毕,并不代表所有dom节点加载完毕(dom的解析完毕一定在dom加载完毕之前)。

java参数中传json为什么给我分段了 传输json对象_javascript

第二步生成cssTree

浏览器解析CSS代码,计算出最终的样式数据。解析CSS的时候会按照如下顺序来定义优先级:浏览器默认设置,用户设置,外联样式,内联样式,html中的style(嵌在标签中的行间样式)。

java参数中传json为什么给我分段了 传输json对象_javascript_02

第三步生成randertree,也可以说是渲染树
渲染树中每一个节点都存储着对应的CSS属性。
因为js可以操作dom,所以当渲染树建立后,如果动态的操作了dom,机会导致页面重新渲染,叫做reflow重排(重构)

reflow重排:dom节点的删除,添加。dom节点的宽高变化,位置变化,displaynone–>block,offsetwidth,offsetLeft等一系列都会导致重排。

repaint重绘:js改变了节点 的css,会导致那一部分的重绘,这样影响比较小。

java参数中传json为什么给我分段了 传输json对象_异步加载_03

第四步:当渲染树创建完成之后,浏览器就可以根据渲染树直接把页面绘制到屏幕上。
DOM树完全和html标签一一对应,而渲染树会忽略不需要渲染的元素(head、display:none的元素)。

JS异步加载

js加载的缺点

js加载本身是属于同步加载的,加载js文件会阻塞文档,比如有多个js文件,在加载到一个文件处有错后面的就加载不了

或者一旦网速不好,那么整个网站将等待js加载而不进行后续渲染等工作

但是有些工具方法需要按需加载,有一些工具js文件它是不会改变页面的,用到再加载,不用不加载。

javascript异步加载的三种的方案

  1. . defer异步加载

defer异步加载,但要等到dom文档全部解析完才会被执行,也可以将代码写到内部。
defer 属性是一个布尔属性。

<script src="demo_defer.js" defer></script>
  1. async 异步加载

async 异步加载,加载完就执行,async 只能加载外部脚本(只有在使用 src 属性时),不能把js写在script标签里。
Internet Explorer 9 及之前的版本不支持 script 标签的 async 属性。
异步脚本一定会在页面 load 事件前执行。

<script src="demo_async.js"  async></script>

defer和async异步加载时不阻塞页面,但是兼容性不好,不能控制加载的顺序

3. 创建script,插入到DOM中,加载完毕后callBack

<script>
       var script = document.createElement('script');
       script.type = "text/javascript";
       script.src = "demo.js";//到了这一步之后就会异步的去下载demo.js文件
       document.head.appendChild(script);//当把标签插入到页面的时候才会去执行这个js脚本
 </script>

接下来我么创建一个名为demo.js文件写入test函数

function test(){
condole.log("a“);
}

执行他

<script>
       var script = document.createElement('script');
       script.type = "text/javascript";
       script.src = "demo.js";//到了这一步之后就会异步的去下载demo.js文件

       document.head.appendChild(script);//当把标签插入到页面的时候才会去执行这个js脚本
       test();
  
    </script>

执行到test()时候会报错
Uncaught ReferenceError: test is not defined

这是因为文件还没有下载完,读程序是以微秒计的。
有可能异步的下载demo.js文件还没有下载完,程序就读到test执行这了
还没有下载完demo.js,当然没有test函数了

接着我们 添加一个setTimeout() 方法,让test()在1000毫秒后再执行时不时就不报错了

<script>
       var script = document.createElement('script');
       script.type = "text/javascript";
       script.src = "demo.js";//到了这一步之后就会异步的去下载demo.js文件

       document.head.appendChild(script);//当把标签插入到页面的时候才会去执行这个js脚本
       setTimeout(function () {
            test();
        }, 1000)
  </script>

但是这样并不能从根本上解决问题,我们需要知道程序什么时候异步把外部js文件下载完成

下面介绍几种方法让我们知道什么时候加载完成

  • load事件监听

Safari,chrome,firefox,opera都可以用这个来监听什么时候这个文件异步下载完毕

<script>
       var script = document.createElement('script');
       script.type = "text/javascript";
       script.src = "demo.js";
       script.onload = function(){
             test();
       }
       document.head.appendChild(script);
    </script>

当文件下载完之后就会执行onload方法,这样就知道什么时候demo.js加载完了。

  • 如果是IE浏览器 就用·状态码的改变·来知道什么时候完成文件的下载
<script>
       var script = document.createElement('script');
       script.type = "text/javascript";
       script.src = "demo.js";
	   script.onreadystatechange = function(){
           if(script.readyState == "complete" || script.readyState == "loaded"){
               test();
           }
       }
	   document.head.appendChild(script);
    </script>

综合以上两钟方法实现所有浏览器兼容

<script>
       var script = document.createElement('script');
       script.type = "text/javascript";
       script.src = "demo.js";//到了这一步之后就会异步的去下载demo.js文件
       
       if(script.readyState){
            script.onreadystatechange = function(){
            if(script.readyState == "complete" || script.readyState == "loaded"){
                test();
            }
         }
       }else{
            script.onload = function(){
                test();
            }
       }
   
       document.head.appendChild(script);
      
    </script>

案例:封装一个函数兼容性的异步加载js文件并且可以按需执行该文件里面的函数

callback:回调函数
在事件里面的绑定的事件处理函数就是回调函数,回调函数其实就是当满足一定条件才调用的函数。

function loadScript (url, callback){
           // url是异步下载的js文件
           //callback是异步下载的js文件中的某一个函数
        var script = document.createElement('script');
        script.type = "text/javascript";
        script.src = url;//异步下载的js文件
        if(script.readyState){
            script.onreadystatechange = function(){
                if(script.readyState == "complete" || script.readyState == "loaded"){
                    callback();
                }
           }
        }else{
            script.onload = function(){
                callback();
            }
        }
        
        document.head.appendChild(script);
       }
</script>

在IE上如果下载太快(比读程序还快),IE的readystatechange 事件检测状态码的时候,它早已经从loading变成complete或者loaded(以极快的速度加载完了文件,你还没来得及检测),
那你再检测它就不会变了,它一直都是complete或者loaded,这个时候检测也没用了。

<script>
function loadScript (url, callback){
           // url是异步下载的js文件
           //callback是异步下载的js文件中的某一个函数
        var script = document.createElement('script');
        script.type = "text/javascript";
  
        if(script.readyState){
            script.onreadystatechange = function(){
                if(script.readyState == "complete" || script.readyState == "loaded"){
                    callback();
                }
           }
        }else{
            script.onload = function(){
                callback();
            }
        }
        script.src = url;//开始异步下载的js文件
        document.head.appendChild(script);
       }
</script>

先绑定事件,然后再script.src = url开始异步下载js文件,那么这个时候下载,肯定有一个状态码的转换,这样就解决了来不及检测状态码的尴尬。

时间线
  1. 创建Documen对象开始解析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.wite()) ;
  8. document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段。
  9. 当所有async的脚本加载完成井执行后。img等加载完成后,document.readyState = ‘complete’, window对象触发load事件。
  10. 从此,以异步响应方式处理用户输入,网络事件等。

document.write()会清除文档流

<body>
    <div style="width:100px;height:100px;background-color:red;"></div>
    <script>
        window.onload = function () {
            document.write('a');
        }
    </script>
</body>

这个时候页面就只会输出a,div就被清除了

创建Documen对象开始解析web页面,script标签也是dom元素,所以它也要识别,这个阶段document.readyState= ‘loading’ 。

<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
 <script>
        console.log(document.readyState);//loading  
<script>
</body>
</html>

用状态码改变才能检测文档解析到哪个状态了

<body>
<script>
      console.log(document.readyState);//loading  
       document.onreadystatechange = function (){
          console.log(document.readyState);// interactive   complete
      }
<script>
</body>

DOMContentLoaded事件只有用addEventListener绑定才有效

document.addEventListener('DOMContentLoaded',function(){ console.log('a')},false);

正常的情况下,当文档加载(解析文档,就是构成dom树,然后再加载dom元素)完毕后,才执行js代码,
但是document对象触发DOMContentLoaded事件可以当dom解析完了(dom树构建完成,还没加载完毕的时候)就去执行js代码

/就像jquery里面的
当dom解析完就执行的部分
$(document).ready(function (){

})

现在用document对象触发DOMContentLoaded事件放在head里面,照样可以拿到div
因为它是在dom树构建完成之后就调用事件的那个处理函数,不用等到文档全部加载完毕
而window.onload()就是要等到文档全部加载完毕了(像img的src的图片加载完成了),它才会执行window.onload()里面的js。

<!DOCTYPE html>
<html lang="en">
<head>
    <script>
        document.addEventListener('DOMContentLoaded',function(){
            var div = document.getElementsByTagName('div')[0];
            console.log(div);
        },false);
    </script>
</head>
<body>
    <div style="width:100px;height:100px;background-color:red"></div>
</body>
</html>