HTML解析之DOMContentLoaded和onload

HTML解析之DOMContentLoaded和onload说在前面在很久很久以前,我在封装自己的JQuery库时就使用过DOMContentLoaded,觉得这个知识点看看别的文章就行了,不过现在我想把它记下来。JS代码与body标签的位置关系一个HTML初学时会遇到的问题,一个html页面中js代码应该放到哪里?<!–如果script标签在body前面–><head>…&lt…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

说在前面

在很久很久以前,我在封装自己的JQuery库时就使用过DOMContentLoaded,觉得这个知识点看看别的文章就行了,不过现在我想把它记下来。

 

JS代码与body标签的位置关系

一个HTML初学时会遇到的问题,一个html页面中js代码应该放到哪里?

<!--如果script标签在body前面-->
<head>
    ...
    <script>
        var box = document.getElementById('box');
        console.log(box)  //null
    </script>
</head>
<body>
    <div id="box"></div>
</body>
---------
<!--如果script标签在body后面(里面的最后也可以)-->
<head></head>
<body>
    <div id="box"></div>
    <script>
        var box = document.getElementById('box');
        console.log(box)  //div#box
    </script>
</body>

Jetbrains全家桶1年46,售后保障稳定

上面代码可以看出,如果js代码写在body标签前面,而且没用其他事件而直接获取dom的话是无法获取的。而js代码写在body标签的后面(里面的最后也可以)就可以获取dom。

实际上如果了解浏览器解析HTML规则就很清楚原因了,浏览器解析HTML由上往下依次执行,如果遇到<script>会阻塞解析,先执行该JS脚本(如果是外部JS文件还要先加载),执行结束后再接着往下解析,所以上面获取不到dom的原因是当时JS代码执行时页面DOM树尚未构建完成。具体分析往下看。

 

script标签的defer和async

从上面知道,浏览器解析HTML遇到script标签会阻塞。上面举例的JS代码都是内嵌在HTML中的,这样再解析到script时直接执行就行。但如果是引入外部JS文件的话会有一点不同,要先加载该JS文件,然后执行,然后在往下解析HTML。但script标签上还有两个常见属性defer和async

一般情况<script src=”xxx.js”>

当浏览器遇到 script 标签时,文档的解析将停止,并立即下载并执行脚本,脚本执行完毕后将继续解析文档。

defer <script defer src=”xxx.js”>

当浏览器遇到 script 标签时,文档的解析不会停止,JS文件的加载与文档解析并行(异步),待到文档解析DOM构建完成,脚本才会执行(在DOMContentLoaded事件触发之前)。

async <script async src=”xxx.js”>

当浏览器遇到 script 标签时,文档的解析不会停止,JS文件的加载与文档解析并行(异步),脚本下载完成后开始执行脚本,脚本执行时文档会停止解析。

看图(图片来源于网络)

HTML解析之DOMContentLoaded和onload

 

蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。

总结defer和async的区别:

  1. 加载时是一样的,相对于HTML解析是异步的。
  2. 不同的是执行时机,async在代码加载完之后会马上执行,并且执行时会阻塞HTML解析。而defer则要等到文档解析DOM构建完成,DOMContentLoaded事件触发之前执行
  3. async执行时机不确定性,要注意使用场景。

所以script标签加上defer属性,即使不用DOMContentLoaded或window.onload也可以获取操作DOM。

//index.js
var box = document.getElementById('box');
//可以获取到div
console.log(box);  //div#box
--------
<head>
    ...
    <script defer src="./index.js"></script>
</head>
<body>
    <div id="box"></div>
</body>

 

DOMContentLoaded和window.onload

DomContentLoaded

MDN解释当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。另一个不同的事件 load 应该仅用于检测一个完全加载的页面。 在使用 DOMContentLoaded 更加合适的情况下使用 load 是一个令人难以置信的流行的错误,所以要谨慎。

注意:DOMContentLoaded 事件必须等待其所属script之前的样式表加载解析完成才会触发

//index.css
#box{
    width:100px;
    height:100px;
    background: pink;
}
---------
//DOMContentLoaded前面引入样式表
<head>
    <link rel="stylesheet" href="./index.css"/>
    <script>
        function getStyle(el, attr) {
            return el.currentStyle ? el.currentStyle[attr] : getComputedStyle(el)[attr];
        }
        // DOMContentLoaded
        document.addEventListener('DOMContentLoaded', function(){
            var box = document.getElementById('box');
            //可以获取到样式表的样式
            console.log(getStyle(box, 'width'));    //100px
        })
    </script>
</head>
<body>
    <div id="box"></div>
</body>
--------
//DOMContentLoaded后面引入样式表
<head>
    <script>
        function getStyle(el, attr) {
            return el.currentStyle ? el.currentStyle[attr] : getComputedStyle(el)[attr];
        }
        // DOMContentLoaded
        document.addEventListener('DOMContentLoaded', function(){
            var box = document.getElementById('box');
            //可能无法获取到样式表的样式
            console.log(getStyle(box, 'width'));    //1350px或100px
        })
    </script>
    <link rel="stylesheet" href="./index.css"/>
</head>
<body>
    <div id="box"></div>
</body>

 

上面代码看出,在DOMContentLoaded后面引入样式表,DOMContentLoaded可能无法获取样式表里的样式,因为此时HTML解析完成,DOM树构建完成,但外部css文件可能还没加载完成

暂时得出结论:js代码应该放在样式表之后

window.onload

这个就没什么好说的,此时HTML文档解析完成,其他依赖资源也全部加载完成。

用document.readyState看一下各种情况下的HTML文档状态:

<script>
    window.onload = function(){
        console.log('window.onload', document.readyState);
    }
    document.addEventListener('DOMContentLoaded', function(){
        console.log('DOMCOntentLoaded', document.readyState);
    }, false);
    console.log(document.readyState);
</script>
//输出
//loading    文档加载中
//DOMCOntentLoaded interactive    文档与用户可以开始交互,可以操作DOM
//window.onload complete    一切完成

 

ready()简单实现

JQuery中有个$(document).ready(),下面就简单实现一下。

document.ready = function(fn){
    if(document.addEventListener){  //现代浏览器
        document.addEventListener('DOMContentLoaded', function(){
            document.removeEventListener('DOMContentLoaded', arguments.callee, false);
            fn();
        }, false);
    }else if(document.attachEvent){  //低版本IE
        document.attachEvent('onreadystatechange', function(){
            if(document.readyState === 'complete'){
                document.detachEvent('onreadystatechange', arguments.callee);   
                fn();
            }
        })
    }else{
        window.onload = fn;
    }
}

 

 

总结

HTML文档加载步骤:

  1. 由上往下解析HTML结构。
  2. 遇到src属性则发起请求加载资源,只有script会阻塞HTML解析,其他(css、img等)都不会影响HTML解析。
  3. script资源加载完,执行JS脚本。
  4. DOM树构建完成,触发DOMContentLoaded
  5. 其他css、img、iframe等资源如果还未加载完成继续加载。
  6. 页面加载完毕,触发window.onload

为什么要强调css放头部,js放尾部

因为css样式表是浏览器渲染页面的重要一环,应该尽早发起请求加载,毕竟也不会阻塞HTML解析。

而HTML解析遇到script会阻塞,所以应该放到后面,而不阻塞其他资源请求。虽然说还是要等script加载执行完成之后才会触发DOMContentLoaded,但现在很多现代浏览器为了更好地用户体验,能够渲染不完整的dom树和cssom,尽快的减少白屏的时间。

参考文章    参考文章

 

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/234712.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 字典序输出_按姓名字典序排序

    字典序输出_按姓名字典序排序这一类的题目在面试中的算法是比较常见的,这里也自己做一个总结1.输入一个数字n,输出从1~n组成的数字的全排列,每个排列占一行,输出按照数值升序排列https://blog.csdn.net/desirepath/article/details/50447712从数组的末尾开始,首先找到第一个升序的数字对,然后交换这个数字对,然后从这个数字对开始,按照生序交换后面的所有数字。2…将1~…

    2022年10月28日
  • vue filters 是什么?怎么使用?

    vue filters 是什么?怎么使用?vuefilters是什么?首先:filter翻译成中文是过滤器的意思。而在Vue中作用其实就是对字段进行预处理怎么使用呢?就用一个小Demo,讲解一下!目的:对字段进行处理,打印出“Helloworld!!!”首先,打印出“Helle”<template> <view> {{demo}} </view></template…

  • ODPS double类型转型精度问题总结

    ODPS double类型转型精度问题总结简介:ODPSdouble类型转型精度问题总结从相差0.0000000000001说起,本文主要是对odps的Double和Decimal的精度使用问题做一个总结。1.问题描述客户开发人员在使用maxcompute对double数据类型求和时出现错误(数据表由oracle数据库抽取到maxcompute,对应字段类型为number到double),正确的结果是1943.38,但求和结果为1943.3799999999999,结果相差了0.0000000000001,这个…

  • docker部署vue项目_docker部署java

    docker部署vue项目_docker部署java第一步:vue项目打包成dist。npmrunbuild第二步:构建Dockerfile#设置基础镜像,这里使用最新的nginx镜像,前面已经拉取过了FROMnginx#将dist文件中的内容复制到/usr/share/nginx/html/这个目录下面COPYdist//usr/share/nginx/html/第三步:安装nginxdockerpullnginxdockerrun–namenginx-d-p4030:8.

    2022年10月19日
  • 集合及运算_集合的概念及运算

    集合及运算_集合的概念及运算[TOC]数据结构与算法_Python_C完整教程目录:https://www.cnblogs.com/nickchen121/p/11407287.html更新、更全的《数据结构与算法》的更新网

  • INSERT INTO SELECT语句与SELECT INTO FROM语句区别

    INSERT INTO SELECT语句与SELECT INTO FROM语句区别1.INSERTINTOSELECT语句语句形式为:InsertintoTable2(field1,field2,…)selectvalue1,value2,…fromTable1或者:InsertintoTable2select*fromTable1注意:(1)要求目标表Table2必须存在,并且字段field,field2…也必须存在(2)注意Table2的主

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号