Javascript学习(四)

[TOC]

DOM

DOM(Document Object Model),文档对象模型,属于Web API的一部分,其中定义了很多的对象,用来对网页进行操作。

在DOM标准下,网页的每部分都会转化为对象,称之为节点。一个页面由多个节点构成,节点有着不同的类型:

  • 文档节点 --- 整个网页

  • 元素节点 --- 某个元素

  • 文本节点 --- 网页中的文本

  • 属性节点 --- 元素的某属性

  • ...

元素之间的关系:

  • 祖先 --- 包含后代元素的元素

  • 后代 --- 被祖先元素包含的元素

  • 父 --- 直接包含子元素的元素

  • 子 --- 被父元素直接包含的元素

  • 兄弟 --- 拥有相同父元素的元素

使用JS操作网页时,第一步需要先获取对象,然后在进行操作。浏览器已经提供了Document对象。

  • const 对象名 = document.getElementById("id名"); --- 通过id获取元素节点对象

  • 对象名.innerText = "需要修改的文本"; --- 修改元素的文本内容

<body>
    <button id="btn">点我一下</button>
    <script>
        const btn = document.getElementById("btn");
        btn.innerText = "Hello";
    </script>
</body>

Document对象表示的是整个网页。Document对象的原型链:

  • HTMLDocument ---> Document ---> Node ---> EventTarget ---> Object.prototype --->null

  • 凡是在原型链上存在的对象的属性和方法都可以通过Document对象调用。

部分属性:

  • document.documentElement --- html根元素

  • document.head --- head元素

  • document.title --- title元素

  • document.body --- body元素

  • document.links --- 获取页面所有超链接

  • ......

通过document对象获取已有元素节点:

  • document.getElementById(); --- 通过id获取元素节点对象。

  • document.getElementsByClassName(); --- 根据元素的class属性值获取一组元素节点对象,返回的是一个类数组。

  • document.getElementsByTagtName(); --- 通过标签名获取一组元素节点对象,返回结果是实时更新的集合,当参数是“*”时,代表获取页面内所有元素。

  • document.getElementByName(); --- 根据name属性值来获取元素对象节点,主要运用于表单,返回实时更新的集合。

  • document.querySelectorAll(); --- 根据选择器来获取元素节点对象,如(div,.name,#btn),返回的是不会实时更新的类数组。

  • document.querySelector(); --- 根据选择器查询第一个符合条件的元素。

通过元素节点对象获取其他节点的方法:

  • element.childNodes --- 获取当前元素的子节点

  • element.children --- 获取当前元素的子元素

  • element.firstElementChild --- 获取当前元素的第一个子元素

  • element.firstElementChild --- 获取当前元素的最后一个子元素

  • element.nextElementSibling --- 获取当前元素的下一个兄弟元素

  • element.perviousElementSibling --- 获取当前元素的前一个兄弟元素

  • element.parentNode --- 获取当前元素的父节点

  • element.tagName --- 获取当前元素的标签名

文本节点对象:页面中所有文本内容都是文本节点对象。修改文本的三个属性:

  1. element.textContent

    • 获取或修改元素中的文本内容。

    • 获取的是标签中的内容,不会考虑CSS样式。

  2. element.innerText

    • 获取或修改元素中的内容。

    • 会考虑CSS样式,通过该属性读取CSS样式,会触发网页的重排。

    • 当修改的文本内容中有标签时,会自动将其转义。

  3. element.innerHTML

    • 获取或修改元素中的html代码。

    • 可以直接向元素中插入html代码,但是插入内容时有被xss注入的风险。

属性节点,也是DOM的一个对象,通常不需要获取对象而是直接通过元素即可对其进行各种操作。操作属性节点:

  1. 方式一:

    • 读取:元素.属性名(class属性要通过className来读取),读取一个布尔值时返回true或false。

    • 修改:元素.属性名 = 属性值;

  2. 方式二:

    • 读取:元素.getAttribute(属性名);

    • 修改:元素.setAttribute(属性名,属性值);

    • 删除:元素.removeAttribute(属性名);

<body>
    <input type="text" name="username" value="admin">
    <script>
        // const input = document.getElementsByName("username")[0];
        const input = document.querySelector("[name = username]");
        console.log(input.type);
        input.value = "用户名";
        console.log(input.getAttribute("name"));
        input.setAttribute("name", "tim");
        input.removeAttribute("value");
        console.log(input);
    </script>
</body>

事件: 事件就是用户和浏览器之间发生交互行为。

可以通过为事件绑定响应函数(回调函数)来完成与用户之间的交互。

绑定响应函数的方式:

  1. 直接在元素属性中设置。

  2. 通过为元素的指定属性设置响应函数来绑定事件。(一个事件只能绑定一个响应函数)

  3. 通过元素的addEventListener()方法来绑定事件。(可以绑定多个事件,依次触发,不会覆盖)

<body>
    <button id="btn">按钮</button>
    <script>
        const btn = document.getElementById("btn");
        btn.onclick = function(){
        //     alert("点击事件");
        // };
        btn.addEventListener("click", () => {
            alert("点击事件1");
        });
        btn.addEventListener("click", () => {
            alert("点击事件2");
        });
    </script>
</body>

解决页面加载前JS代码先执行导致的无法获取DOM对象的情况的方法:

  1. 将script标签写在body的最后。

  2. 将代码写在window.onload回调函数中。

    • window.onload = function(){ JS代码 };

    • window.addEventListener("load",function(){ JS代码 });

  3. 将代码编写到Document对象的DOMContentLoaded的回调函数中。

    • document.addEVentListener("DOMContentLoaded",function(){ JS代码 });

  4. 将代码编写在外部JS文件中,然后以defer的形式引入。

    • <script defer src="xxx"> JS代码 </script>

//练习一:点击切换图片
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .outer {
            width: 400px;
            margin: 60px auto;
            text-align: center;
        }

        #pervious {
            margin-top: 15px;
            margin-right: 25px;
        }
    </style>
</head>

<body>
    <div class="outer">
        <p id="info">总共m张图片,这是第n张</p>
        <div class="img-wrapper">
            <img src="./1.webp" alt="按钮图片">
        </div>
        <button id="pervious">前一张</button>
        <button id="next">后一张</button>
    </div>
    <script>
        const img = document.getElementsByTagName("img")[0];
        const info = document.getElementById("info");
        const perv = document.getElementById("pervious");
        const next = document.getElementById("next");
        const imgArr = ["./1.webp", "./2.webp", "./3.webp"];
        let count = 0;
        info.textContent = `总共${imgArr.length}张,这是第${count + 1}张`;
        perv.onclick = function () {
            count--;
            if (count < 0) {
                count = imgArr.length - 1;
            };
            img.src = imgArr[count];
            info.textContent = `总共${imgArr.length}张,这是第${count + 1}张`;
        };
        next.onclick = function () {
            count++;
            if (count > imgArr.length - 1) {
                count = 0;
            };
            img.src = imgArr[count];
            info.textContent = `总共${imgArr.length}张,这是第${count + 1}张`;
        };
    </script>
</body>

Alt

//练习二:学生兴趣选择
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #choose,
        #items,
        #button {
            margin-top: 20px;
        }

        #all,
        #no,
        #reverse,
        #submit {
            margin-right: 15px;
        }
    </style>
    <script>
        window.onload = function () {
            const allBtn = document.getElementById("all");
            const noBtn = document.getElementById("no");
            const reverse = document.getElementById("reverse");
            const submit = document.getElementById("submit");
            const hobbies = document.getElementsByName("hobby");
            const checkAll = document.getElementById("check-all");
            allBtn.onclick = function () {
                for (let i = 0; i < hobbies.length; i++) {
                    hobbies[i].checked = true;
                };
                checkAll.checked = true;
            };
            noBtn.onclick = function () {
                for (let i = 0; i < hobbies.length; i++) {
                    hobbies[i].checked = false;
                };
                checkAll.checked = false;
            };
            reverse.onclick = function () {
                for (let i = 0; i < hobbies.length; i++) {
                    hobbies[i].checked = !hobbies[i].checked;
                };
                const checkedBox = document.querySelectorAll('[name = hobby]:checked');
                if (checkedBox.length === hobbies.length) {
                    checkAll.checked = true;
                } else {
                    checkAll.checked = false;
                };
            };
            submit.onclick = function () {
                for (let i = 0; i < hobbies.length; i++) {
                    if (hobbies[i].checked) {
                        alert(hobbies[i].value);
                    };
                };
            };
            checkAll.onchange = function () {
                for (let i = 0; i < hobbies.length; i++) {
                    hobbies[i].checked = checkAll.checked;
                };
            };
            for (let i = 0; i < hobbies.length; i++) {
                hobbies[i].onchange = function () {
                    const checkedBox = document.querySelectorAll('[name = hobby]:checked');
                    if (checkedBox.length === hobbies.length) {
                        checkAll.checked = true;
                    } else {
                        checkAll.checked = false;
                    };
                };
            };
        };
    </script>
</head>

<body>
    <form action="#">
        <div id="choose">
            请选择你喜欢的运动
            <input type="checkbox" id="check-all">全选
        </div>
        <div id="items">
            <input type="checkbox" name="hobby" value="篮球">篮球
            <input type="checkbox" name="hobby" value="足球">足球
            <input type="checkbox" name="hobby" value="羽毛球">羽毛球
            <input type="checkBox" name="hobby" value="乒乓球">乒乓球
            <input type="checkbox" name="hobby" value="跑步">跑步
        </div>
        <div id="button">
            <button type="button" id="all">全选</button>
            <button type="button" id="no">取消</button>
            <button type="button" id="reverse">反选</button>
            <button type="button" id="submit">提交</button>
        </div>
    </form>
</body>

Alt 元素的添加、删除和修改:

  • 添加:

    1. createElement(); --- 创建元素

    2. 元素.textContent --- 添加文本内容

    3. 元素.id --- 添加id属性

    4. 添加节点

      • appendChild(); --- 用于给一个节点添加子节点

      • insertAdjacentElement(); --- 添加元素,两个参数:参数一:添加元素的位置;参数二:添加的元素

      • insertAdjacentHTML(); --- 添加标签,参数一:添加的位置;参数二:添加的标签 //这种添加方法容易有xss注入风险

  • 替换:

    • 元素.replaceWith(替换元素); --- 用一个元素替换另一个元素

  • 删除:

    • 元素.remove(); --- 删除元素 如果要阻止超链接的默认跳转行为,可以在事件最后加 return false; 但是只针对 xxx.xxx = function(){}; 方式添加事件时。

节点复制: 通过 cloneNode() 方法可以实现对节点的复制,包括节点的属性,同时可以将 true 作为参数传递来判断是否复制子节点。

const l1 = document.getElementById("l1");
const newLi = l1.cloneNode(true);

修改样式: 通过 元素.style.样式名 = 样式值; 来修改元素的样式,修改添加的是内联样式,当样式名中有 - 时,需要用驼峰命名法。

获取CSS样式: 通过 getComputedStyle(); 方法,它会返回该元素所有生效的样式,第一个参数为要获取的对象,第二个参数为要获取的伪元素。(该方法是只读的)

const boxStyle = getComputedStyle(box);
console.log(boxStyle.width);
const boxBeforeStyle = getComputedStyle(box,"::before");
console.log(boxBeforeStyle.backgroundColor);

注意:样式对象中返回的样式值不一定是可以用来计算的值,所以在计算前要加以区分,确保是可计算的值再去加以计算。

const newWidth = parseInt(boxStyle.width) + 100 + "px";

通过属性读取样式:

  • 元素.clientWidth

  • 元素.clientHeight

    • 获取元素内部的宽度和高度,包括内容区和内边距。

  • 元素.offsetWidth

  • 元素.offsetHeight

    • 获取元素可见框的大小,包括内容区,内边距,和边框。

  • 元素.scrollWidth

  • 元素.scrollHeight

    • 获取元素滚动区域的大小

  • 元素.offsetParent

    • 获取离元素最近的开启定位的祖先元素,如果没有找到便返回body

  • 元素.offsetTop

  • 元素.offsetLeft

    • 获取元素相对于定位父元素的上边和左边的偏移量

  • 元素.scrollTop

  • 元素.scrollLeft

    • 获取或设置滚动条相对于顶部和左侧的距离

通过属性读取的样式返回的都是可以直接计算的数值,也就是没有 px 后缀。

通过class属性修改样式: 除了直接修改样式外,也可以通过class属性来间接修改样式。

通过class修改样式的好处:

  1. 可以一次性修改多个元素的样式。

  2. 可以与CSS解耦。

元素.classList 是一个对象,对象中提供了当前元素的类的各种操作方法。

  • 元素.classList.add(); --- 向元素中添加一个或多个class

  • 元素.classList.remove(); --- 删除元素中一个或多个class

  • 元素.classList.replace(); --- 替换元素的class,第一个参数被替换class,第二个参数为新class

  • 元素.classList.toggle(); --- 切换元素中的一个class,有则删,没有则添加

  • 元素.classList.contains(); --- 检查元素中是否有该class属性,有则返回true,无则返回false

事件对象(Event): 事件对象是浏览器在事件触发时创建的对象,其中包含了事件的所有信息。

通过事件对象可以获取到事件的详细信息,如鼠标的位置、偏移量,键盘的输入。

浏览器在创建事件对象后会将事件对象作为响应函数的参数传递,所以可以在事件的响应函数中定义一个形参来接受事件对象。

DOM中存在着不同类型的事件对象,所有事件对象都有一个共同的祖先:Event。

  • event.target(); --- 触发事件的对象

  • event.currentTarget(); --- 绑定事件的对象(同this)

  • event.stopPropagation(); --- 停止事件的传导,也就是停止该事件对象的冒泡

  • event.preventDefault(); --- 取消默认行为,如超链接的跳转,可以在箭头函数作为响应函数的事件中使用

事件的冒泡是指事件的向上传导,当事件触发时,其祖先元素上的相同事件也会依次触发,事件的冒泡与样式无关,只与结构有关,事件的冒泡并非坏事。

事件的委派: 将本该绑给多个元素的事件,统一绑给document对象。以降低代码复杂度方便维护。

事件的传播机制: 在DOM中,事件的传播可以分为三个阶段:

  • 捕获阶段:由祖先元素向目标元素进行事件的捕获(默认情况下事件不会在捕获阶段触发,如若有此需求,只需将addEventListener()中的第三个参数设置为true即可

  • 目标阶段:触发事件的对象

  • 冒泡阶段:由目标元素向祖先元素进行事件的冒泡

BOM

BOM: 浏览器对象模型,BOM为我们提供了一组对象,可以对浏览器进行操作。

BOM对象:

  • Window --- 代表浏览器窗口(全局对象)

  • Navigator --- 浏览器的对象,可以用来识别浏览器

    • navigator.userAgent --- 返回一个用来描述浏览器信息的字符串

    • ...

  • Location --- 浏览器的地址栏信息

    • 可以直接将location的值修改为新的网址,使网页发生跳转

    • location.href --- 获取当前网页的网址

    • location.assign() --- 跳转到新的地址

    • location.replace() --- 用新地址替代当前地址,发生跳转后无法后退

    • location.reload --- 刷新页面,可以传递true作为参数来强制清除缓存刷新

  • History --- 浏览器的历史记录,控制浏览器的前进后退

    • history.back(); --- 后退

    • history.forward(); --- 前进

    • history.go(); --- 可前进可后退,参数为正数前进,参数为负数后退

  • Screen --- 浏览器屏幕的信息

BOM对象都是作为Window对象的属性保存,可以直接在JS中访问这些对象。

定时器: 可以间隔一段时间再执行部分代码。

  • const timer = setTimeout(() => {},间隔时间); --- 参数一:要执行的函数;参数二:间隔时间(毫秒),只执行一次

  • clearTimeout(timer); --- 关闭定时器

  • const timer = setInterval(() => {},间隔时间); --- 参数一:要执行的函数;参数二:间隔时间(毫秒),每间隔一段时间就执行一次

  • clearInterval(timer); --- 关闭定时器

事件循环(event loop): 函数在每次执行时都会产生一个执行环境,执行环境负责存储函数执行时的一切数据。

调用栈(call stack): 负责存储函数调用时的执行环境。

  • 当一个函数被调用时,它的执行环境会作为一个栈帧插入到调用栈顶部,执行完毕后会从调用栈中自动弹出,后进先出。

消息队列: 负责存储将要执行的函数。

  • 当触发一个事件时,其响应函数并不是直接插入到调用栈中,因为此时调用栈中可能还存在着未执行完的函数。

  • 事件触发后,JS引擎是将事件的响应函数存储在消息队列中排队。

定时器的本质就是在指定间隔时间后将函数存储进消息队列中。

  • setInterval每间隔一段时间就将函数添加到消息队列中,如果函数执行速度慢,时间多于间隔时间,就会导致后面函数执行间隔时间的不一致。

  • 所以定时器常用setTimeout,为解决该方法只执行一次的缺点,可以采用回调函数进行递归操作。

<body>
    <script>
        console.time("间隔");
        const timer = setTimeout(function fn() {
            console.timeEnd("间隔");
            console.log("函数执行");
            console.time("间隔");
            setTimeout(fn, 3000);
        }, 3000);
    </script>
</body>

可以将函数在调用栈中停几秒的代码:

const time = Date.now();
while(Date.now() - time < 5){};

本文章使用limfx的vscode插件快速发布