JS14 - DOM对象 - 密码可视、一键全选、动态渲染、选项卡切换、懒加载

203 阅读18分钟

Document Object Model

  • DOM(Document Object Model 文档对象模型) 是 HTML 和 XML 的应用程序接口(API),将 web 页面与到脚本或编程语言连接起来。
  • 核心对象:浏览器内置的 document 对象,如果页面中的标签被 JavaScript 获取到,那么这个获取到的就是 DOM 对象。
  • 文档逻辑树 - node - object - methods - event:DOM 用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点 (node),每个节点都包含着对象 (objects)。DOM 的方法 (methods) 让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。
  • 最初,JavaScript 和 DOM 是交织在一起的,但它们最终演变成了两个独立的实体。JavaScript 可以访问和操作存储在 DOM 中的内容,因此我们可以写成这个近似的等式:API (web 或 XML 页面) = DOM + JS (脚本语言)。DOM 被设计成与特定编程语言相独立,使文档的结构化表述可以通过单一,一致的 API 获得。尽管我们在本参考文档中会专注于使用 JavaScript,但 DOM 也可以使用其他的语言来实现,例如 Python。

DOM 节点主要类型

DOM 节点一般分为常用的三大类:元素节点文本节点属性节点

  • 元素节点:在页面获取到的元素/标签,称为元素节点或标签节点,通过 getElementBy... 获取到的都是元素节点
  • 文本节点:在页面标签中的文字,称为文本节点,通过 innerText 获取到的是文本节点的文本内容,不是node格式,innerText返回的是string格式
  • 属性节点:页面每一个标签上的属性,称为属性节点,通过 getAttribute 获取到的都是元素的属性节点,不存在父子关系

DOM 节点.png

属性 - nodeType

  • 功能:返回节点类型
  • 语法let type = NodeObj.nodeType;
  • 说明:该属性是常量,只读,不可修改
常量描述
Node.ELEMENT_NODE1元素节点
Node.ATTRIBUTE_NODE2属性节点
Node.TEXT_NODE3文本节点
Node.CDATA_SECTION_NODE4<!CDATA[[ … ]]> 节点
Node.PROCESSING_INSTRUCTION_NODE7<?xml-stylesheet ... ?> 节点
Node.COMMENT_NODE8注释节点
Node.DOCUMENT_NODE9document 节点
Node.DOCUMENT_TYPE_NODE10文档类型节点 <!DOCTYPE html> 
Node.DOCUMENT_FRAGMENT_NODE11DocumentFragment 节点

属性 - nodeValue

  • 功能:返回/设置当前节点的值
  • 语法:返回 str = nodeObj.nodeValue; 设置 nodeObj.nodeValue = str;
  • 说明:返回的是节点本身的value,不是子节点的value
NodeValue of nodeValue
CDATASectionCDATA 的文本内容
Comment注释的文本内容
Documentnull
DocumentFragmentnull
DocumentTypenull
Elementnull
NamedNodeMapnull
EntityReferencenull
Notationnull
ProcessingInstruction (en-US)整个标签的文本内容
Text文本节点的内容
<body>
    <div id="container">
        <!-- This is the content. -->
        <div id="content">content</div>
    </div>
</body>
<script>
    let divContainer = document.getElementById("container");
    let divContent = document.getElementById("content");
    
    /* 节点类型 */
    console.log(divContainer.nodeType); 
    // 1 --> 元素节点
    console.log(divContent.innerText,divContent.innerText.nodeType);  
    // content undefined
    console.log(divContainer.firstChild);  
    // #text --> (其中的key-value) data: "↵        " --> 换行符和空格
    console.log(divContainer.firstChild.nodeType);  
    // 3 --> 换行符和空格是文本节点

    /* 节点值 */
    console.log(divContainer.nodeValue);            //null
    console.log(divContent.nodeValue);              //null --> 元素节点本身的nodeValue
    console.log(divContent.firstChild.nodeValue);   //content --> 文本节点本身的value
</script>

属性 - nodeName

  • 功能:返回当前节点的名称

DOM 操作一般节点

获取一般节点

属性 - childNodes

  • 功能:返回包含指定节点的子节点的集合,没有则返回 NodeList []
  • 语法:返回 var ndList = elementNodeReference.childNodes;
  • 说明:只读
<body>
    <div id="container">
        <!-- This is the content. -->
        <div id="content">content</div>
    </div>
</body>
<script>
    let divContainer = document.getElementById("container");
    let divContent = document.getElementById("content");

    console.log(divContainer.childNodes);           
    /* NodeList(5) [text, comment, text, div#content, text]
        0: text
        1: comment
        2: text
        3: div#content
        4: text
        length: 5
        __proto__: NodeList
     */
    console.log(divContainer.firstChild, divContainer.firstChild.childNodes);
    /* #text ---> ... data: "↵        " ... */
    /* NodeList [] ---> 文本节点没有子节点,返回空 */
</script>

属性 - firstChild 和 lastChild

  • 功能:返回当前节点的第一个/最后一个子节点,没有则返回 null
  • 语法
    • let first_child = nodeObj.firstChild; 
    • let last_child = nodeObj.lastChild;
  • 说明:都为只读,对比于firstElementChild 和 lastElementChild

属性 - previousSibling 和 nextSibling

  • 功能:当前节点的前一个/后一个兄弟节点,没有则返回null
  • 语法
    • let previous_sibling = nodeObj.previousSibling; 
    • let next_sibling = nodeObj.nextSibling;
  • 说明:都为只读,对比于 previousElementSibling 和 nextElementSibling

属性 - parentNode

  • 功能:当前节点的父节点,没有则返回null
  • 语法let parent_node = nodeObj.parentNode; 
  • 说明:只读,对比于 parentElement

添加一般节点

方法 - append

  • 功能:在 Element 的最后一个子节点之后插入一组 Node 对象或 DOMString 对象(元素字符串,等价为 Text 节点)。
  • 差异
    • Element.append()允许追加 DOMString 对象,而 Node.appendChild() 只接受 Node 对象。
    • Element.append() 没有返回值,而 Node.appendChild() 返回追加的 Node 对象。
    • Element.append() 可以追加多个节点和字符串,而 Node.appendChild() 只能追加一个节点。

方法 - appendChild()

  • 功能:附加到指定父节点的子节点列表末尾处
  • 语法nodeObj.appendChild(aChild); 
  • 说明
    • 添加成功,返回添加的节点对象
    • 该方法添加的节点只有添加功能,没有复制功能,如果多个节点要添加,那么最后只能添加到一个节点上,不会每个都添加
    • 如果要解决这个问题,可以通过cloneNode()方法创建多个节点备用
let boxChilds = document.querySelectorAll("#box div");
let newEle = document.createElement("input");
for (let i = 0; i < boxChilds.length; i++) {
    //因为只创建了一个newEle元素节点,加上循环的作用,最后便是添加到最后一个div上了
    boxChilds[i].appendChild(newEle); 
    //解决办法:克隆多个节点备用
    newEle = newEle.cloneNode(false); 
}

appendChild不能复制添加.png

方法 - insertBefore()

  • 功能:附加到指定节点之前
  • 语法let insertedNode = parentNode.insertBefore(newNode, referenceNode); 
  • 说明
    • insertedNode 被插入节点 (newNode)
    • parentNode 新插入节点的父节点
    • newNode 用于插入的节点
    • referenceNode newNode 将要插在这个节点之前
<body>
    <div id="container" style="display: inline-block;"></div>
</body>
<script>
    let divContainer = document.getElementById("container");

    let divAfter = document.createElement("div");
    divAfter.setAttribute("class","after-element");
    let appendDivAfter =  divContainer.appendChild(divAfter);
    console.log(appendDivAfter);    //元素对象 divAfter

    let divBefore = document.createElement("div");
    divBefore.setAttribute("class","before-element");
    let insertDivBefore = divContainer.insertBefore(divBefore,divAfter);
    console.log(insertDivBefore);   //元素对象 divBefore

    /**最终结果
     * <div id="container" style="display: inline-block;">
     *      <div class="before-element"></div>
     *      <div class="after-element"></div>
     * </div>
     */
</script>

修改一般节点

方法 - replaceChild()

  • 功能废长立幼,用指定节点替换当前节点的一个子节点,并返回被替换掉的节点对象
  • 语法let replacedChildNode = parentNode.replace(newChild,oldChild);
  • 说明newChild 是用来替换 oldChild 的新节点。如果该节点已经存在于 DOM 树中,则它首先会被从原始位置删除。

方法 - cloneNode()

  • 功能:返回调用该方法的节点的一个副本,但不显示在也html中
  • 语法let clonedNode = Node.cloneNode(deep);
  • 说明deep 可选,是否采用深度克隆,布尔值,false 不克隆后代 true 克隆后代,新旧规范的默认值不同,因此最好指定取值
<body>
    <div id="container" style="display: inline-block;">
        <div>inner</div>
    </div>
</body>
<script>
    let divContainer = document.getElementById("container");
    let divInner = document.querySelector("#container div");
    let textNode = document.createTextNode("new inner text");
    
    //替换
    let newElementNode = document.createElement("div");
    newElementNode.setAttribute("id","new_element");
    newElementNode.appendChild(textNode);
    let replacedChildNode = divContainer.replaceChild(newElementNode, divInner);
    console.log(replacedChildNode);
    /**返回被替换的节点对象:
     *  <div>inner</div>
     */ 

    //克隆
    let cloneDivContainerShallow = divContainer.cloneNode(false);
    console.log(cloneDivContainerShallow);
    /**浅克隆: 
     *  <div id="container" style="display: inline-block;">  
     **/ 
    let cloneDivContainerDeep = divContainer.cloneNode(true);
    console.log(cloneDivContainerDeep);
    /**深度克隆: 
     *  <div id="container" style="display: inline-block;">
     *     <div id="new_element">new inner text</div>
     *  </div>
     */ 
</script>

删除一般节点

方法 - removeChild()

  • 语法 1(接受返回值)let oldChild = node.removeChild(child);
  • 语法 2(不接受返回值)node.removeChild(child);
  • 参数child 要移除的子节点;node 父节点;oldChild 保存对删除的子节点的引用,oldChild === child
  • 语法 1 返回:被移除的这个子节点仍然存在于内存中,只是没有添加到当前文档的 DOM 树中,因此,还可以把这个节点重新添加回文档中
  • 语法 2 返回:没有使用 oldChild 来保存对这个节点的引用,则认为被移除的节点已经是无用的,在短时间内将会被内存管理回收
  • 说明:如果想要删除多个子元素,可以通过for循环删除

方法 - remove()

  • 功能:删除自身节点(包含其子节点)
  • 语法Node.remove();
  • 说明:无参数,无返回值

判断有无节点

方法 - hasChildNodes()

  • 功能:判断当前节点是否包含有子节点
  • 语法let exit = parentNode.hasChildNodes();
  • 说明
    • 返回true/false,注意:该方法容易受到换行符和空格等文本节点的影响
    • 有三种方法可以判断当前节点是否有子节点:
    • node.firstChild !== null
    • node.firstElementChild !== null
    • node.childNodes.length > 0 所有子节点
    • node.children.length > 0 元素子节点
    • node.hasChildNodes()

DOM 操作元素节点

获取元素节点

属性 - children

  • 功能:返回 Node 的子元素节点,没有则返回 HTMLCollection []
  • 语法:返回 let nodeList = nodeObj.children;
  • 说明:都是只读,childNodes 返回所有子节点,children 返回所有元素子节点
<body>
    <div id="container">
        <!-- This is the content. -->
        <div id="content">content</div>
    </div>
</body>
<script>
    let divContainer = document.getElementById("container");
    let divContent = document.getElementById("content");

    console.log(divContainer.childNodes);           
    //NodeList(5) [text, comment, text, div#content, text]
    console.log(divContainer.firstChild);
    // #text ----> ... data: "↵        " ...
    console.log(divContainer.firstChild.childNodes);
    // NodeList []  ----> 文本节点没有子节点,返回空

    console.log(divContainer.children);
    //HTMLCollection [div#content, content: div#content]
    console.log(divContent.children);
    //HTMLCollection []
</script>

属性 - firstElementChild 和 lastElementChild

  • 功能:返回当前节点的第一个/最后一个元素子节点,没有则返回 null
  • 语法
    • let first_element_child = nodeObj.firstElementChild; 
    • let last_element_child = nodeObj.lastElementChild;
  • 说明:都为只读,对比于 firstChild 和 lastChild

属性 - previousElementSibling 和 nextElementSibling

  • 功能:当前节点的前一个/后一个兄弟元素节点,没有则返回null
  • 语法
    • let previous_element_sibling = nodeObj.previousElementSibling; 
    • let next_element_sibling = nodeObj.nextElementSibling;
  • 说明:都为只读,对比于 previousSibling 和 nextSibling

属性 - parentElement

  • 功能:当前节点的父元素节点,没有则返回null
  • 语法let parent_Element = nodeObj.parentElement; 
  • 说明:只读,对比于 parentElement

属性 - id

  • 功能:通过元素标签的id属性值,可以直接拿到id对应的元素标签
  • 语法
    • var idStr = element.id; // Get the id
    • element.id = idStr; // Set the id
  • 说明
    • 如果要通过id获取,可以直接使用id值,但不能使用 document.id;
    • 对于CSS,ID选择器 #example 不区分大小写,而ID属性 id="example" 区分大小写
    • 使用除 ASCII 字母、数字、_- 和 . 以外的字符可能会造成兼容性问题,因为 HTML 4 中不允许使用它们。虽然这个限制在 HTML5 中被解除了,但为兼容性考虑 ID 应该以字母开头
<body>
    <div id="box"></div>
</body>
<script>
    console.log(box);  //返回:<div id="box"></div
    console.log(box === document.getElementById("box"));  //true
</script>

属性 - documentELement

  • 功能:对于任何非空 HTML 文档,调用 document.documentElement 总是会返回一个 html 元素,且它一定是该文档的根元素
  • 语法var element = document.documentElement;
<!DOCTYPE html>
<html lang="en">
<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>
</head>
<body id="oldBodyElement">
    <div id="childBox">div</div>
</body>
<script>
    //获取到HTML根节点
    var rootElement = document.documentElement;
    console.log(rootElement);   //完整html文档
    //获取HTML根节点下的所有子节点组成的集合NodeList
    var childNodeList = rootElement.childNodes;
    console.log(childNodeList)  //NodeList(3) [head, text, body#oldBodyElement]
    //查询遍历子节点集合中的每个子节点
    for(i=0;i<childNodeList.length;i++){
        console.log(childNodeList[i])
        //包括:head、#text、body
    }
</script>
</html>

属性 - body

  • 功能:当前文档有 body 元素,就返回 body 元素;有 frameset 元素,就返回最外层的 frameset
  • 语法:获取 var objRef = document.body; 覆盖修改 document.body = objRef;
<body id="oldBodyElement">
    <div id="childBox">div</div>
</body>
<script>
    //获取 body 元素的某个属性值
    console.log(document.body.id);  //oldBodyElement
    //修改
    var newBodyElement = document.createElement("body");
    newBodyElement.id = "newBodyElement";
    var newDiv = newBodyElement.appendChild(document.createElement("div"));
    newDiv.innerText = "home";
    newDiv.style = "border:1px solid black;";
    document.body = newBodyElement;
    //报错:注意类型问题
    // document.body = "<div>123</div>";   
    //TypeError: Failed to convert value to 'HTMLElement'.
</script>

属性 - head

  • 功能:返回当前文档中的 head 元素。如果有多个,返回第一个
  • 语法:获取 var objRef = document.head;
  • 说明:只读属性,赋值会静默失败,严格模式则会抛出 TypeError 异常
//获取 head
var rootHead = document.head;
//与querySelector作用相似
var queryHead = document.querySelector("head");
var documHead = document.head;
console.log(queryHead === documHead);   //true

方法 - getElementById();

  • 功能:返回文档中第一个使用id的元素对象,不存在则返回 null
  • 语法:获取 var element = document.getElementById(id);
    • 返回值 elementElement 对象(eg. <div id="box"></div>)
    • 参数 id: 字符串,大小写敏感
  • 说明:只能通过document调用,不能通过指定的某个元素对象调,例如,element.getElementById("myId"); 会报错:Uncaught TypeError: boxChild02.getElementById is not a function
<body>
    <div id="box"></div>
</body>
<script>
    var element;
    element = document.getElementById("box");
    console.log(element);   //元素对象 <div id="box"></div>
</script>

方法 - getElementsByName();

  • 功能:返回包含 name 属性的子节点的类数组对象,没有则返回 NodeList[]
  • 语法:仅能 document 调用 document.getElementsByName(name);
  • 说明表单常用,由于name属性大多存在于表达标签,因方法大多用于表单
<body>
    <div id="box">
        <input type="text" name="username" id="usernameLogin">
        <input type="password" name="pwd" id="pwdLogin">
        <input type="text" name="username" id="usernameClear">
        <input type="password" name="pwd" id="pwdClear">
    </div>
</body>
<script>
    // *  css不识别大写字母,因此写itle和写Title是一样的,所以要写成-的形式;
    // *  在js中dom,class的用-书写的都写成驼峰
    //获取到节点集合(类数组)
    var usernameNodes = document.getElementsByName("username");
    console.log(usernameNodes); //NodeList(2) [input#usernameLogin, input#usernameClear]
    var pwdNodes = document.getElementsByName("pwd");
    console.log(pwdNodes);      //NodeList(2) [input#pwdLogin, input#pwdClear]

    //Array.from() 将类数组转化为数组
    var usernameNodesArray = Array.from(usernameNodes);
    console.log(Array.isArray(usernameNodes))       //false
    console.log(Array.isArray(usernameNodesArray))  //true
</script>

方法 - getElementsByClassName();

  • 功能:包含类名的子元素的类数组对象,没有则返回 HTMLCollection[]
  • 语法
    • document 调用 document.getElementsByClassName("name");
    • 指定元素调用 rootElement.getElementsByClassName("name");
  • 说明
    • 使用 className 的原因:class是一个关键字,避免冲突
    • 根元素 rootElement:调用这个方法的元素将作为本次查找的根元素,可以直接使用元素的ID值。
<body>
    <div id="box" class="box-par">
        <div class="box-child special">
            <div class="box-child">div class child</div>
        </div>
        <div class="box-child">div class child</div>
    </div>
</body>
<script>
    //获取到元素集合(类数组)
    //通过 document 调用
    var elementsBoxPar = document.getElementsByClassName("box-par");
    console.log(elementsBoxPar);  //HTMLCollection [div#box.box-par, box: div#box.box-par]
    //通过 指定元素 调用
    var elementsBoxParChildFirstLevel = document.getElementById("box").getElementsByClassName("box-child");
    console.log(elementsBoxParChildFirstLevel);  //HTMLCollection(3) [div.box-child, div.box-child, div.box-child]
    var elementsBoxParChildSecondLevel01 = elementsBoxParChildFirstLevel[0].getElementsByClassName("box-child");
    console.log(elementsBoxParChildSecondLevel01);  //HTMLCollection [div.box-child]
    var elementsBoxParChildSecondLevel02 = elementsBoxParChildFirstLevel[1].getElementsByClassName("box-child");
    console.log(elementsBoxParChildSecondLevel02);  //HTMLCollection []
    //获取具有两个 class 值的元素
    var elementsDoubleClass = document.getElementsByClassName("box-child special");
    console.log(elementsDoubleClass);   //HTMLCollection [div.box-child.special]

    //Array.from() 将类数组转化为数组
    var elementsBoxParChildFirstLevelArray = Array.from(elementsBoxParChildFirstLevel);
    console.log(Array.isArray(elementsBoxParChildFirstLevel))       //false
    console.log(Array.isArray(elementsBoxParChildFirstLevelArray))  //true
</script>

方法 - getElementsByTagName();

  • 功能:包含标签名的子元素的类数组对象,没有则返回 HTMLCollection[]
  • 语法
    • document 调用 document.getElementsByTagName(name);
    • 指定元素调用 rootElement.getElementsByTagName(name);
  • 说明
    • 通配符:name,字符串,特殊字符 * 代表所有元素
    • rootElement:调用这个方法的元素将作为本次查找的根元素
    • -wenkit- 内核返回 NodeList,其它返回 HTMLCollection
<body>
    <div id="box" class="box-par">
        <div class="box-child">div class child</div>
    </div>
</body>
<script>
    //获取到元素集合(类数组)
    var elementsBoxPar = document.getElementsByTagName("div");
    console.log(elementsBoxPar);  //HTMLCollection(2) [div#box.box-par, div.box-child, box: div#box.box-par]    console.log(elementsBoxParChildFirstLevel);  //HTMLCollection(3) [div.box-child, div.box-child, div.box-child]
    var elementsBoxChild = elementsBoxPar[0].getElementsByTagName("div");
    console.log(elementsBoxChild);  //HTMLCollection [div.box-child]    var elementsBoxParChildSecondLevel02 = elementsBoxParChildFirstLevel[1].getElementsByTagName("box-child");

    //Array.from() 将类数组转化为数组
    var elementsBoxParArray = Array.from(elementsBoxPar);
    console.log(Array.isArray(elementsBoxPar))       //false
    console.log(Array.isArray(elementsBoxParArray))  //true
</script>

方法 - querySelector();

  • 功能:返回指定选择器的元素对象/标签,没有则返回 null
  • 语法
    • document 调用 document.querySelector("css选择器");
    • 指定元素调用 rootElement.querySelector("css选择器");
  • 说明:CSS选择器跟在 css 中的书写一致
<body>
    <div id="box">
        <input class="input-text" type="text" name="username" id="usernameLogin">
        <input class="input-pwd" type="password" name="pwd" id="pwdLogin">
        <input class="input-text" type="text" name="username" id="usernameClear">
        <input class="input-pwd" type="password" name="pwd" id="pwdClear">
    </div>
</body>
<script>
    //document调用:获取 元素/标签 对象
    var inputText = document.querySelector(".input-text");
    console.log(inputText); //返回符合选择器的第一个元素对象
    //元素调用
    var container = document.querySelector("#box");
    var inputPwd = container.querySelector(".input-text");
</script>

方法 - querySelectorAll();

  • 功能:返回指定选择器的子节点的类数组对象,没有则返回 NodeList[]
  • 语法
    • document 调用 document.querySelectorAll("css选择器");
    • 指定元素调用 rootElement.querySelectorAll("css选择器");
  • 说明
    • CSS选择器跟在 css 中的书写一致
    • 必须使用反斜杠字符转义不属于标准 CSS 语法的字符,且包含字符串和querySelectorAll两次转义,例如,console.log('#foo\\bar'); //"#foo\bar"console.log('#foo\\\\bar'); //"#foo\\bar"
<body>
    <div id="box">
        <input class="input-text" type="text" name="username" id="usernameLogin">
        <input class="input-pwd" type="password" name="pwd" id="pwdLogin">
        <input class="input-text" type="text" name="username" id="usernameClear">
        <input class="input-pwd" type="password" name="pwd" id="pwdClear">
    </div>
    <div id="special">
        <div id="foo\bar">1</div>
        <div id="foo\bar">2</div>
        <div id="foo:bar">3</div>        
        <div id="foo:bar">4</div>        
        <div id="foo bar">5</div>
    </div>
</body>
<script>
    // *  css不识别大写字母,因此写itle和写Title是一样的,所以要写成-的形式;
    // *  在js中dom,class的用-书写的都写成驼峰
    //document调用:获取 元素/标签 对象
    var inputText = document.querySelectorAll(".input-text");
    console.log(inputText); //NodeList(2) [input#usernameLogin.input-text, input#usernameClear.input-text]
    //元素调用
    var container = document.querySelector("#box");
    var inputPwd = container.querySelectorAll(".input-text");
    console.log(inputText); //NodeList(2) [input#usernameLogin.input-text, input#usernameClear.input-text]
    //多级CSS选择器
    var inputNodesList = document.querySelectorAll("#box>.input-text,#box>.input-pwd");
    console.log(inputNodesList); 
    //NodeList(4) [input#usernameLogin.input-text, input#pwdLogin.input-pwd, input#usernameClear.input-text, input#pwdClear.input-pwd]

    // Array.from() 将类数组转化为数组
    var inputNodesArray = Array.from(inputNodesList);
    console.log(Array.isArray(inputNodesList))       //false
    console.log(Array.isArray(inputNodesArray))  //true
    inputNodesArray.forEach(element => {
        console.log(element);   //遍历出每个元素对象
    })

    //获取特殊CSS样式的节点集合
    var elementList;
    elementList = document.querySelectorAll("#foo\\\\bar");
    console.log(elementList);   //NodeList(2) [div#foo\bar, div#foo\bar]
    elementList = document.querySelectorAll("#foo\\:bar");
    console.log(elementList);   //NodeList(2) [div#foo:bar, div#foo:bar]
    elementList = document.querySelectorAll("#foo\\\x20bar");
    console.log(elementList);   //NodeList [div#foo bar]
    elementList = special.querySelectorAll("#foo\\\x20bar");
    console.log(elementList);   //NodeList [div#foo bar]
</script>

创建元素节点

方法 - createElement()

  • 功能:创建一个元素节点,单纯创建,没有指定添加到哪里,因此需要借助 appendChild() insertBefore() 等方法
  • 语法let newElement = document.createElement(tagName,[options]); 
  • 说明
    • tagName string格式,为要创建的元素,不能使用限定名称,如 html:a,只能是单纯的标签名
    • options 可选,包含一个属性名为 is 的对象,该对象的值是用 customElements.define() 方法定义过的一个自定义元素的标签名
//创建元素节点
let divBefore = document.createElement("div");
divBefore.setAttribute("class","before-element");
//添加元素节点
let insertDivBefore = divContainer.insertBefore(divBefore,divAfter);
console.log(insertDivBefore);   //元素对象 divBefore

/* 通过此方法创建的元素,需要先添加到HTML中,才能算是DOM对象,才能调用DOM属性和方法 */
document.createElement("div").id = "list_row";
console.log(document.getElementById("list_row").id);
//只创建未添加html,调用属性和方法就会报错:Uncaught TypeError: Cannot read property 'id' of null

获取元素布局尺寸

属性 - offsetWidth 和 offsetHeight

  • 功能:只读属性,返回一个元素的布局宽度/高度
  • 语法
    • let layWidth = element.offsetWidth;
    • let layHeight = element.offsetHeight;
  • 说明
    • 单位:数字
    • 如果元素的css属性是 display:none; 那么就无法获取该属性
    • offsetWidth/offsetHeight = width/height+border+padding+scrollbar 参考CSS属性box-sizing:border-box; //--> border+padding+content
    • 这个属性将会 round(四舍五入) 为一个整数,如果你想要一个 fractional(小数) 值,请使用 element.getBoundingClientRect()

属性 - clientWidth 和 clientHeight

  • 功能:只读属性,取值数字,返回块级元素的内部宽度/高度
  • 语法
    • let width = element.clientWidth;
    • let height = element.clientHeight;
  • 说明
    • clientWidth/clientHeight = width/height+padding
    • HTML5兼容:如果是html元素调用,则是返回不包含滚动条的视口宽高;如果不是HTML5文档,则单纯返回html实际宽高
    • 如果元素的css属性是 display:none; 那么就无法获取该属性
    • 对于内联元素以及没有 CSS 样式的元素为 0
<!DOCTYPE html> <!-- 缺少该声明,clientWidth和clientHeight都只是返回html布局宽高而非视口宽高局宽高而非视口宽高 --> <html lang="en"> -->
<html lang="en">
<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>
</head>
<body>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        html,body{
            width: 2000px;
            height: 2000px;
            background-color: lightgray;
        }
    </style>
    <script>
        //获取可视窗口的尺寸 --> 懒加载的前提
        /**innerHeight/innerWidth:
         * 浏览器窗口的视口(viewport)高度/宽度(以像素为单位);
         * 如果有水平滚动条,也包括滚动条高度,
         * 属性为只读,且没有默认值
         */
        console.log("innerWidth "+innerWidth);
        console.log("innerHeight "+innerHeight);
        console.log("window.innerWidth "+window.innerWidth);
        console.log("window.innerHeight "+window.innerHeight);
        /**clientWidth / clientHeight:
         * 浏览器窗口的视口,不包含滚动条
         */ 
        console.log("clientWidth "+document.documentElement.clientWidth);
        console.log("clientHeight "+document.documentElement.clientHeight)
    </script>
</body>
</html>

属性 - offsetLeft 和 offsetTop

  • 功能:只读属性,返回当前元素相对于 HTMLElement.offsetParent 节点的 左边/顶边 偏移的像素值
  • 语法leftPos/topPos = element.offsetLeft/offsetTop;
  • 说明offsetParent 定位父级:元素遇到的第一个有定位的父级,如果父级元素都没有定位,则偏移量相对于body

属性 - clientLeft 和 clientTop

  • 功能:只读属性,元素的左边/顶边框(border)的宽度,以像素表示
  • 语法leftBor/topBor = element.clentLeft/clientTop;

属性 - scrollTop 和 scrollLeft

  • 功能:只读属性,元素的左边/顶边框(border)的宽度,以像素表示
  • 语法leftBor/topBor = element.clentLeft/clientTop;
  • 说明
    • scrollTop 竖向滚动条已经滚上去的距离,一般用于滚动到一定程度的时候触发显示/吸顶等功能
    • scrollLeft 横向滚动条已经滚过的距离
document.documentElement.scrollTop
document.body.scrollTop  //(适配低版本浏览器)
document.documentElement.scrollLeft
document.body.scrollLeft  //(适配低版本浏览器)

DOM 操作属性节点

原生属性操作

属性 - [属性名]

  • 功能:对元素标签的原生属性通过点语法读写
  • 语法
    • 获取 let attrParam = elementObj.属性名;
    • 修改 elementObj.属性名 = [value];
  • 说明
    • attributeName 是属性名的值
    • class属性需要用className,如果使用class会被当作自定义属性
    • 只能设置原生属性,删除可直接通过赋值空字符串实现属性值为零
    • 获取或设置自定义属性,不会报错,但不会在标签中生效
<body>
    <div id="container">
        <input type="text" name="username" id="usernameLogin">
        <input type="password" name="pwd" id="pwdLogin">
        <input type="checkbox" name="sex" id="male" value="male">
        <input type="checkbox" name="sex" id="female" value="female">
    </div>
</body>
<script>
    /* 内置原生属性 */
    //获取元素的原生属性 name
    console.log(usernameLogin.name);    //username
    console.log(pwdLogin.name);         //pwd
    //获取元素的原生属性 value  
    console.log(male.value);            //male
    console.log(female.value);          //female
    //修改元素的原生属性 value
    male.value = "maleLady";
    console.log(male.value);            //maleLady
    //修改/新增元素的原生属性 class
    male.className = "sexMale";         //class属性需要用className,如果使用class会被当作自定义属性
    console.log(male.className);        //sexMale
    //删除内置属性的属性值 
    male.className = "";
    console.log(male.className);        //"" --> 空字符串
</script>

自定义属性操作

方法 - attributes()

  • 功能:返回该元素所有属性节点的一个实时集合,NameNodeMap 对象,没有定义属性会报错 TypeError: Cannot read property 'attributes' of null
  • 语法let nameNodeMap = elementObj.attributes;
  • 说明
    • 获取的是行内定义的属性和属性值,对于style在css定义的属性值不会获取
    • 能够获取自定义的属性
<style>
    #container{
        width: 200px;
        height: 300px;
        background-color: pink;
    }
</style>
<body>
    <div id="container" style="display: inline-block;" data-link="02"></div>
</body>
<script>
    let divContainer = document.getElementById("container");
    console.log(divContainer.attributes);
    // NamedNodeMap {0: id, 1: style, 2: data-link, id: id, style: style, data-link: data-link, length: 3}
    // attributes --> 获取的是属性,对于style而言,获取的是行内属性值,css定义的属性值没有被获取
    // style节点展开后的节点值 --> nodeValue: "display: inline-block;"
    // data-link: data-link --> 能狗获取自定义属性
</script>

方法 - getAttribute();

  • 功能:返回指定的属性值,不存在则返回 null 或 ""(空字符串)
  • 语法elementObj.getAttribute(attrName);
  • 说明:attrName 为字符串,且不区分大小写

方法 - getAttributeNames();

  • 功能:返回属性名集合,没有定义属性会报错 TypeError: Cannot read property 'getAttributeNames' of null
  • 语法elementObj.getAttributeNames();
  • 说明:返回
// 遍历 elements 的元素
for(let name of element.getAttributeNames())
{
  let value = element.getAttribute(name);
  console.log(name, value);
}

方法 - setAttribute();

  • 功能:新增/修改元素的属性值,包括自定义和内置属性,返回值 undefined

  • 语法elementObj.setAttribute(attrName, attrValue);

  • 说明

    • attrName 为字符串,因此class属性需要用直接用class字符串就可以,不会不会与class关键字冲突
    • 用户自己设定的元素属性,在代码上显示与原生属性没有差别,因此 attrName 一般约定格式为 data-XXX
    • 不能识别大写,会将大写字母直接转换为小写,显示在标签内,例如,data-loginSec 会转换为 data-loginsec element.setAttribute("data-loginSec","loginSecHump");===>data-loginsec="loginSecHump"
  • 注意事项:参数 attrName attrValue 都是作为字符串进行传递的,如果元素中的元素取值仅为布尔值,那么要注意该方法只能设置为true,空字符串会让属性在元素标签中表现为只有一个属性名,而此时依然是true

let btn = document.getElementsByTagName("button")[0];
btn.setAttribute("disabled","good");  
//这里起作用,也不是因为true本身代表布尔值,就算把true换成good也一样起作用,只是要加引号
//      btn.setAttribute("disabled","good");  --> 作用相同

btn.setAttribute("disabled",false);  
//      -->不起作用,因为 把false当作字符串了
//   btn.setAttribute("disabled","");          //-->不行,会直接变成 <button disabled="">...
//   btn.setAttribute("disabled",null);        //-->不行,null会被传输为"null"字符串 -> disabled="null"
//   btn.setAttribute("disabled",undefiend);   //-->不行,undefined会直接报错 -> ReferenceError: undefiend is not defined
//   btn.setAttribute("disabled",0);           //-->不行,disabled="0"
//      -->因此,建议使用 removeAttribute(); 直接移除
//      -->或者,使用 btn.disabled = false; 修改布尔值

方法 - removeAttribute();

  • 功能:删除属性,IE返回boolean,其他返回 undefined
  • 语法elementObj.removeAttribute(attrName);
  • 说明
    • 因为removeAttribute()不会返回任何有效值,你不能使用链式方法(如 document.body.removeAttribute("first").removeAttribute("second")…)连续移除多个属性。
    • 属性不存在,不会生成错误
    • 若要彻底移除一个属性的效果,应当使用 removeAttribute(),而不是使用 setAttribute() 将属性值设置为 null。对于许多属性,如果仅将其值设为 null,这不会造达成和预期一样的效果。

属性 - dataset

  • 功能:返回data-XXX属性组成的 DOMStringMap 对象
  • 语法elementObj.dataset.halfName = [attrValue];
  • 说明
    • data-xxx 自定义的属性名不会识别大写,属性值可以识别大写,data-xxx 如果是驼峰式的命名,直接转换为连字符,例如:container.dataset.loginSec = "loginSecValueNew";===>data-login-sec="loginSecValueNew";
    • halfName 是指 data- 前缀后的内容
<body>
    <div id="container">
        <input type="text" name="username" id="usernameLogin" data-list="01">
        <input type="password" name="pwd" id="pwdLogin" data-list-type="02">
        <input type="checkbox" name="sex" id="male" value="male">
        <input type="checkbox" name="sex" id="female" value="female">
    </div>
</body>
<script>
    /* 自定义属性 - 新建/修改 */    
    //问题:自定义属性不能直接通过 elementObj.attributeName = [value] 创建
    //解决:因此需要借助 setAttribute() 方法,
    //注意:该方法不识别大写,会直接转换为小写
    var updateAttr1 = container.setAttribute("data-loginSec","loginSecHump");
    var updateAttr2 = container.setAttribute("data-loginsec","loginSecHyphen");
    console.log(updateAttr1,updateAttr2);    
    //输出:undefined undefined --> 说明 setAttribute() 无返回值
    
    /* 自定义属性 - 获取 */    
    //==>方法获取: getAttrbute(attrName);
    //注意:该方法不识别大写,会直接转换为小写
    var valueAttr1 = container.getAttribute("data-loginSec");
    var valueAttr2 = container.getAttribute("data-loginsec");
    var valueAttr3 = container.getAttribute("data-login-level");
    console.log(valueAttr1,valueAttr2,valueAttr1 === valueAttr2);     
    //输出:loginSecHyphen loginSecHyphen true --> 自定义属性设置成功,并能获取
    console.log(valueAttr3);    //null
    //==>点语法获取(不建议 ): 如果有连字符 - ,使用会报 指向错误
    // var valueAttr = container.data-loginsec;  //Uncaught ReferenceError: loginSec is not defined

    /* 自定义属性 - dataset获取/修改 - 针对自定义的 data-xxx 属性 */    
    //==>新增:(1)元素标签中,驼峰命名会转换为 连字符;(2)Map对象中,驼峰命名不会转换
    container.dataset.loginSec = "loginSecValueNew";  //(1)loginSec 会转换为login-sec并在开头添加data-,所以属性名就是 data-login-sec;(2)在DOMStringMap中会保留大写存储
    //==>获取
    console.log(container);                     //元素标签行内:data-login-sec="loginSecValueNew"
    console.log(container.dataset);             //DOMStringMap:DOMStringMap {loginsec: "loginSecHyphen", loginSec: "loginSecValueNew"}
    console.log(container.dataset.loginSec);    //loginSecValueNew
    console.log(container.dataset.loginsec);    //loginSecHyphen    -->大小写的结果不同,说明dataset对大写通过连字符进行了处理

    /* 自定义属性 - 删除 */    
    //===> 方法删除:removeAttribute();
    var removeAttr1 = usernameLogin.removeAttribute("name");
    var removeAttr2 = usernameLogin.removeAttribute("type");
    var removeAttr3 = usernameLogin.removeAttribute("data-list");
    console.log(removeAttr1,removeAttr2,removeAttr3);   
    //输出 --> 删除没有返回值:undefined undefined undefined --> 标签属性只剩下 id="usernameLogin"
    //===> dataset 通过 delete 删除:【语法:elementObj.dataset.XXX(俩字符转换为驼峰式)】
    delete female.value;
    console.log(female.value); //female --> 内置属性不能通过delete删除
    delete pwdLogin.dataset.listType;
    console.log(pwdLogin.dataset)                           //DOMStringMap{}
    console.log(pwdLogin.getAttribute("data-list-type"));   //null -->这两个输出都说明,delete能够删除dataset中自定义的属性
</script>

特殊操作 - 元素样式

属性 - element.style

  • 功能:The style read-only property returns the inline style of an element in the form of a CSSStyleDeclaration object that contains a list of all styles properties for that element with values assigned for the attributes that are defined in the element's inline style attribute.
  • 语法:点语法 element.style.styleName; 中括号 element.style["styleName"]
  • 说明
    • 只能修改:行内的设置属性;调用连字符的属性名用驼峰式
    • 返回的结果为 CSSStyleDeclaration 对象,是一种普通引用类型
    • 单个属性:由于返回的style是一个实时的 CSSStyleDeclaration 对象,当元素的样式更改时,它会自动更新本身,修改/读取都可以通过key-value来实现,并且可以将其对象地址值赋值给变量,继而通过该对象读取/修改元素的style样式,实现动态渲染;
    • 全属性:如果要一次性将style全赋值给style,不能通过中间变量,因为会创建出一个style的属性名,而不会将style内的各个属性名和属性值一一地对应上内置key-value
<style>
    #box{
        color: green;
    }
</style>
<body>
    <div id="box" class="parent">
        <div class="box-child">div class child</div>
        <div class="box-child">div class child</div>
        <div class="box-child">div class child</div>
    </div>
</body>
<script>
    //定义存放 style 的中间变量
    var styleDeclaration = document.getElementById("box").style;

    //1-1.新增/修改style样式(全属性)
    /**如果要一次性将style全赋值给style,不能通过中间变量,
     * 因为会创建出一个style的属性名,而不是当作多个属性和属性名,
     * 也就不会将style内的各个属性名和属性值一一地对应上内置key-value
        styleDeclaration.style = `
            background-color: plum;
            border:5px solid pink;
            text-align:center;
        `;
        console.log(styleDeclaration);
        //仅仅添加了 --> style: "↵            background-color: plum;↵            border:5px solid pink;↵            text-align:center;↵        "
        //这种样式是无效的
     */
    document.getElementById("box").style = `
        background-color: plum;
        border:5px solid pink;
        text-align:center;
        hope:lankix;
    `;
    console.log(styleDeclaration);
    //  CSSStyleDeclaration {
    //     0: "background-color"
    //     1: "border-top-width"
    //     2: "border-right-width"
    //     3: "border-bottom-width"
    //     4: "border-left-width"
    //     5: "border-top-style"
    //     6: "border-right-style"
    //     7: "border-bottom-style"
    //     8: "border-left-style"
    //     9: "border-top-color"
    //     10: "border-right-color"
    //     11: "border-bottom-color"
    //     12: "border-left-color"
    //     13: "border-image-source"
    //     14: "border-image-slice"
    //     15: "border-image-width"
    //     16: "border-image-outset"
    //     17: "border-image-repeat"
    //     18: "text-align"
    //     alignContent: ""
    //     ...
    //     color:""  
    //          --> 非行内定义的属性,在css中已经生效,但在 CSSStyleDeclaration 对象中没有值,那么也就不能通过style获取到
    //     ...
    //     border: "5px solid pink" 
    //          --> 简写的属性名和属性值会在内置属性中存在,不在number的key中
    //     ...
    //  }
    //     hope:lankix; --> 不存在这个属性,不会添加


    //1-2.新增/修改style样式(单个属性)
    //单独赋值给style中的某一个指定的属性名和属性值,不能加分号
    styleDeclaration.height = "200px";  //--> 有效
    styleDeclaration.height = "300px;"; //--> 无效
    styleDeclaration.lineHeight = "60px";
    document.getElementById("box").style.color = "white";       //有效
    document.getElementById("box").style.color = "blue;";       //无效
    /* 以下两个属性都能获取到,说明变量styleDeclaration指向同一对象 */
    console.log(document.getElementById("box").style.height)    //200px
    console.log(styleDeclaration.color);                        //white

    //2-1.删除style样式(全属性)
    styleDeclaration.style = "";                //无效,原理同上
    // document.getElementById("box").style = "";  //有效
    console.log(styleDeclaration)               //恢复初始状态
    
    //2-2.删除style样式(单个属性)
    styleDeclaration.color = "";                          //有效
    document.getElementById("box").style.lineHeight = ""; //有效

    //3-1.读取style样式(全属性-遍历单个属性)
    for( key in styleDeclaration) {
        if (!isNaN(Number(key))) console.log(`key: ${key} value(style) ${styleDeclaration[key]}:"${styleDeclaration[styleDeclaration[key]]}"`);
    }
    // key: 0 value(style) background-color:"plum"
    // key: 1 value(style) border-top-width:"5px"
    // key: 2 value(style) border-right-width:"5px"
    // key: 3 value(style) border-bottom-width:"5px"
    // key: 4 value(style) border-left-width:"5px"
    // key: 5 value(style) border-top-style:"solid"
    // key: 6 value(style) border-right-style:"solid"
    // key: 7 value(style) border-bottom-style:"solid"
    // key: 8 value(style) border-left-style:"solid"
    // key: 9 value(style) border-top-color:"pink"
    // key: 10 value(style) border-right-color:"pink"
    // key: 11 value(style) border-bottom-color:"pink"
    // key: 12 value(style) border-left-color:"pink"
    // key: 13 value(style) border-image-source:"initial"
    // key: 14 value(style) border-image-slice:"initial"
    // key: 15 value(style) border-image-width:"initial"
    // key: 16 value(style) border-image-outset:"initial"
    // key: 17 value(style) border-image-repeat:"initial"
    // key: 18 value(style) text-align:"center"
    // key: 19 value(style) height:"200px"

    //3-2.读取style样式(单个属性),只能获取行内属性
    console.log(styleDeclaration[0]);                   //background-color
    console.log(styleDeclaration["backgroundColor"]);   //plum
    console.log(styleDeclaration.backgroundColor);      //plum
    console.log(styleDeclaration["color"]);             //非行内定义,无返回
    console.log(styleDeclaration.color);                //非行内定义,无返回
</script>

方法 - window.getComputedStyle()

  • 功能:返回一个对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有 CSS 属性的值。
  • 语法let style = window.getComputedStyle(element, [pseudoElt]);
  • 说明
    • element:元素对象,获取样式的元素 pseudoElt:string可选,匹配的伪元素。无为元素需要省略或null
    • 只读:该方法只能获取,不能修改
    • 返回的style是一个实时的 CSSStyleDeclaration 对象,当元素的样式更改时,它会自动更新本身。
<style>
    #box{
        background-color: plum;
        color: green;
    }
    #box::after{
        content: " - pseudo - element";
    }
</style>
<body>
    <div id="box" class="parent">something</div>
</body>
<script>
    document.getElementById("box").style = `
        border:5px solid pink;
        text-align:center;
        hope:lankix;
    `;
    console.log(getComputedStyle(box).hope);            
    //undefined --> 自定义,但不满足的style属性
    console.log(getComputedStyle(box).border);          
    //5px solid rgb(255, 192, 203)
    console.log(getComputedStyle(box)["text-align"]);   
    //center
    console.log(getComputedStyle(box)["color"]);   
    //rgb(0, 128, 0) --> 除了行内定义的,内嵌标签的样式也能能获取到
    console.log(getComputedStyle(box)["background-color"]);   
    console.log(getComputedStyle(box)["backgroundColor"]);   
    console.log(getComputedStyle(box).backgroundColor);   
    //rgb(221, 160, 221) --> 点语法调用连字符,需要使用驼峰式
    console.log(getComputedStyle(box,"::after").content);   
    //" - pseudo - element" -->伪元素的content值
    console.log(getComputedStyle(box).content);     
    //normal --> 不加伪元素的值
</script>

特殊操作 - 元素类名

  • 目的:减少操作元素样式的繁琐,直接通过操作类名的方式,快速套用不同class,实现样式快速转换

属性 - className

  • 功能:获取或设置指定元素的 class 属性的值
  • 语法element.className
  • 说明:class属性需要用className,如果使用class会被当作自定义属性
<style>
    .box-style-1{
        background-color: plum;
    }
    .box-style-2{
        color: 2;
    }
    .box-style-3{
        background-color: pink;
    }
</style>
<body>
    <div id="box">something</div>
</body>
<script>
    //设置class属性值:实现样式更改
    box.className = "box-style-1";  
    box.className = "box-style-2 box-style-3";
    //获取class属性
    console.log(box.className); //box-style-2 box-style-3
</script>

集合 - classList

  • 功能:获取或修改指定元素动态 DOMTokenList 集合
  • 语法
    • 获取(只读) let tokenList = element.classList;
    • 添加 tokenList.add("class-name",...[可添加多个类]);
    • 删除 tokenList.remove("class-name",...[可添加多个类]);
    • 置换 tokenList.toggle("class-name",[condition]);
    • 替换 tokenList.replace("been-replaced-class","replace-class");
    • 包含 tokenList.contains("class-name");
  • 说明
    • 置换、替换、包含 返回 true/false
    • 可以使用 展开语法(spread syntax) ==> ...params 添加或移除多个类值
    • DOMTokenList 集合是一个类数组
<body>
    <div id="box" class="box">something</div>
</body>
<script>
    // DOMStokenList 对象 --> 类数组
    console.log(box.classList); 
    /* DOMTokenList ["box", value: "box"]
        0: "box"
        length: 1
        value: "box"
        __proto__: DOMTokenList
     */
    console.log(Array.from(box.classList))
    /* 类数组转换为数组:
       ["box"]
        0: "box"
        length: 1
        __proto__: Array(0)  
     */

    //添加 add --> 无返回值
    box.classList.add("box-com");
    box.classList.add("box-com-pre");
    //删除 remove --> 无返回值
    box.classList.remove("box-com");
    //置换 toggle 根据条件(默认/省略为true),如果是true就执行(添加[若没有]或删除[若已有]) --> 从列表中删除一个给定的标记并返回 `false`。如果标记不存在,则添加并且函数返回 `true`。
    console.log(box.classList.toggle("box-com"));           //返回 true  --> 置换成功
    console.log(box.classList.toggle("box-com-pre",false)); //返回 false --> 置换失败
    //替换 replace
    console.log(box.classList.replace("box","box-main"));   //true
    //查找 contains
    console.log(box.classList.contains("box"));             //false
    console.log(box.classList.contains("box-main"));        //true

    //使用展开语法 添加
    let names = ["box-fir","box-sec","box-thr"]
    box.classList.add(...names);

    //遍历 DOMTokenList
    let classListArray = Array.from(box.classList);
    for (const iterator of classListArray) {
        console.log(`classListArray - ${iterator}`)
    }
    // classListArray - box-main
    // classListArray - box-com
    // classListArray - box-fir
    // classListArray - box-sec
    // classListArray - box-thr
</script>

DOM 操作元素文本

方法 - createTextNode()

  • 功能:创建并返回一个新的文本节点
  • 语法let newTextNode = document.createTextNode(contentStr);
  • 说明contentStr 是一个字符串,包含了要放入文本节点的内容
<style>
    #container{
        width: 200px;
        height: 300px;
        background-color: pink;
    }
</style>
<body>
    <div id="container" style="display: inline-block;">before</div>
</body>
<script>
    console.log("添加文本节点之前:"+ container.innerText); //添加文本节点之前:before

    let newTextNode = document.createTextNode(" after");
    container.appendChild(newTextNode);
    
    console.log("添加文本节点之后:"+container.innerText);  //添加文本节点之后:before after
</script>

属性 - innerText

  • 语法element.innerText = string;
  • 说明
    • 可以设置/获取一个节点及其后代的“渲染”文本内容,但通过该属性返回的不是node对象,而是string类型
    • element.innerText = "<div>content</div>" 标签不能被解析,会当作纯字符串
    • 删除内容:innerText = "",相当于把所有内容赋值为空字符串

属性 - innerHTML

  • 语法element.innerHTML = htmlString;
  • 说明
    • 可以设置/获取HTML元素后代的所有内容(包括标签)
    • element.innerHTML = "<div>content</div>" 标签能够被解析

属性 - outerHTML

  • 语法let content = element.outerHTML; element.outerHTML = content;
  • 说明
    • 可以设置/获取HTML元素本身及其后代的序列化 HTML 片段
    • 通过设置 outerHTML 属性来替换节点
<body>
    <div id="box" class="parent">
        <div class="box-child">div class child</div>
        <div class="box-child">div class child</div>
        <div class="box-child">div class child</div>
    </div>
</body>
<script>
    //获取
    var text = document.getElementById("box").innerText;
    console.log(text)           //返回string类型:文本
    console.log(typeof text)    //string
    var html = document.getElementById("box").innerHTML;
    console.log(html)           //返回string类型:文本和HTML标签
    console.log(typeof html)    //string

    //设置
    document.getElementById("box").innerText = "<div class='child'>disappear</div>";
    //把 <div class="child">disappear</div> 当作文本写入,标签div并未解析
    document.getElementById("box").innerHTML = "<div class='child'>disappear</div>";
    //把 <div class="child">disappear</div> 当作HTMl写入,标签div会被解析
</script>

属性 - value

  • 语法element.value
  • 说明:多用于表单、下拉列表等以value属性为重要参数的标签
<body>
    <input type="password" id="password" name="exitpwd" >
    <select name="cars" id="chosencars" >
        <option value="volvo">volvo</option>
        <option value="marserati" selected>marserati</option>
        <option value="porsche">porsche</option>
    </select>
    <button id="checkCon">confirm</button>
    <hr style="border: 1px solid;">
    <input type="text" disabled id="bannerarea"></div>
    <div id="newarea"></div>
</body>
<script>
    // *  css不识别大写字母,因此写itle和写Title是一样的,所以要写成-的形式;
    // *  在js中dom,class的用-书写的都写成驼峰

    function click(){
        console.log(password.value);      //密码框输入的值就是 value
        console.log(chosencars.value);    //下拉标签option中的 value
        bannerarea.value = `pwd:${password.value} car:${chosencars.value}`
        newarea.innerHTML = `
        <div>pwd:${password.value}</div>
        <div>car:${chosencars.value}</div>
        `;
    }
    checkCon.onclick = click;
</script>

value 操作元素文本.gif

DOM 操作 Cookie

cookie 功能

  • 含义:一段不超过4KB的暂时或永久保存的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成
  • 功能:Cookie,是网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密)
  • 属性
    • Name/Value:Cookie的名称及相对应的值
    • Expires:设置Cookie的生存期,接受 Date 对象。有两种存储类型的Cookie:会话性与持久性。Expires属性缺省时,为会话性Cookie,仅保存在客户端内存中,并在用户关闭浏览器时失效;持久性Cookie会保存在用户的硬盘中,直至生存期到或用户直接在网页中单击“注销”等按钮结束会话时才会失效
    • Path:定义了Web站点上可以访问该Cookie的目录,内层目录可以访问外层的,外层的不能访问内层的
    • Domain:指定了可以访问该 Cookie 的 Web 站点或域
    • Secure:指定是否使用HTTPS安全协议发送Cookie
    • HTTPOnly:用于防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改

cookie 语法

//存 改
//  单值 name=value
document.cookie = "username=James";
//  多值 name=value;name=value
document.cookie = "username=Joker";
document.cookie = "password=123";   //cookie 存值不能使用&,否则会直接当作存值的内容
//  携带属性 - path 路径
document.cookie = "job=developer;path=/01"; //需要进入到 http://127.0.0.1:5500/01 才能访问 job
//  携带属性 - expires 过期时间
let date = new Date("2023-11-18 00:00:00"); //返回的中国标准时间,比 UTC 时间多了8小时
let dataUTC = new Date("2023-11-18 00:00:00").toUTCString(); //转化为UTC时间
// document.cookie = `salary=9000;expires=${date}`; //cookie 默认其为 UTC 时间,因此
document.cookie = `salary=9000;expires=${dataUTC}`; //从中国标准时间转换的UTC时间,刚好对应,注意显示的是UTC时间
let dateExpires = new Date();
dateExpires.setSeconds(dateExpires.getSeconds()+5);
document.cookie = `involve=true;expires=${dateExpires.toUTCString()}`;  //cookie字段5秒后删除

//取
let strings = document.cookie;
console.log(strings);  //返回字符串,name=value之间用 "分号+空格"分割:password=123; salary=9000; username=Joker; involve=true
let obj = {},arr = strings.split("; ");
arr.forEach( item => {
    var keyValue = item.split("=");
    obj[keyValue[0]] = keyValue[1];
});
console.log(obj);   //{password: '123', salary: '9000', username: 'Joker', involve: 'true'}

//删
//将字段的expires有效期设置为比当前时间还要少即可
let dateDelete = new Date();
dateDelete.setMinutes(dateDelete.getMinutes()-1);
document.cookie = `username=Joker;expires=${dateDelete}`;

cookie 特点

  • 格式:只能存文本,对象需要转换为json字符串
  • 大小:数量一般限制在50条,每条大小限制在 4kb
  • 读取:只能由写入cookie的同一域名的页面读取,不可跨域读取
  • 时效:默认session,或者手动指定expires
  • 路径:指定路径后,内层可读外层,外层不可读内层

实例

密码可视

<body>
    <input type="password" id="pwd">
    <img id="check" src="http://127.0.0.1:5500/JS/%E9%97%AD%E7%9C%BC.png">
</body>
<script>
    var canSee = "http://127.0.0.1:5500/JS/%E5%8F%AF%E8%A7%81.png";
    var cannotSee = "http://127.0.0.1:5500/JS/%E9%97%AD%E7%9C%BC.png";
    pwd.style = "width:100px;height:20px;float:left;";
    check
    .style = "widh:20px;height:20px;";
    check.onclick = function(){
        if (check.src == cannotSee) {
            check.src = canSee;
            pwd.type = "text";
        } else {
            check.src = cannotSee;
            pwd.type = "password";
        }
    }
</script>

密码可视功能.gif

购物车全选

<body>
    <input type="checkbox" id="goodsall" name="chosedall">
    <label for="goodsall">全选</label>
    <hr style="border: 2px solid black;border-radius: 2px;">
    <ul id="goodslist">
        <li>
            <input type="checkbox" name="choseditem" id="goodsone">
            <label for="goodsone">商品1</label>
        </li>
        <li>
            <input type="checkbox" name="choseditem" id="goodstwo">
            <label for="goodstwo">商品2</label>
        </li>
        <li>
            <input type="checkbox" name="choseditem" id="goodsthree">
            <label for="goodsthree">商品3</label>
        </li>
        <li>
            <input type="checkbox" name="choseditem" id="goodsfour">
            <label for="goodsfour">商品4</label>
        </li>
    </ul>
</body>
<script>
    let choseItemElements = document.getElementsByName("choseditem");
    //一键勾选/取消全选
    function choseAllGoods(){
        let goodsAllChecked = goodsall.checked;
        if (goodsAllChecked) {
            Array.from(choseItemElements).forEach(element => element.checked = true)            
        } else {
            Array.from(choseItemElements).forEach(element => element.checked = false)            
        }
    }
    goodsall.onclick = choseAllGoods;   //点击之后,执行回调,由于点击让checked已经是true,就没必要再回调函数中再赋值为true
    
    //单选取消任意一个,取消全选;单选全部选中,自动全选
    function choseOneGood(){
        let count = 0; //计算有多少个选中的checkbox,选中就加1
        Array.from(choseItemElements).forEach(element => {
            if (element.checked) {
                count++;
            } //else nothing
        })
        count == choseItemElements.length ? goodsall.checked = true : goodsall.checked = false;
    }
    for (let index = 0; index < choseItemElements.length; index++) {
        choseItemElements[index].onclick = choseOneGood;        
    }
</script>

购物车全选.gif

页面渲染

<!DOCTYPE html>
<html lang="en">

<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>
</head>

<body>
    <table id="personelsheet" class="dataPersonelSheet">
        <tr id="tableheader" style="font-weight: bolder;">
            <td>
                <input id="checkall" type="checkbox">
                <label for="checkall">全选</label>
            </td>
            <td>name</td>
            <td>age</td>
            <td>salary</td>
        </tr>
    </table>
    <input type="button" id="insert" value="添加" style="margin:10px auto;display: block;">
    <input type="button" id="remove" value="删除" style="margin:10px auto;display: block;">
</body>
<script>
    //DOM渲染 - 人员信息表格
    var dataPersonel = [    //-->模拟后台传输的数据
        { name: "张三", age: 30, salary: 25000 },
        { name: "李四", age: 31, salary: 26000 },
        { name: "王五", age: 32, salary: 28000 },
        { name: "赵六", age: 29, salary: 24000 }
    ]
    //通过DOM设置表头样式
    document.documentElement.style.width = "100%";
    document.body.style = "width:100%;margin:0;padding:0;";
    personelsheet.style = ";width:100%;"
    personelsheet.cellPadding = "8px";
    personelsheet.cellSpacing = "5px";
    tableheader.style = "background:plum;height:50px;";
    var headerTds = tableheader.getElementsByTagName("td");
    for (let index = 0; index < headerTds.length; index++) {
        const element = headerTds[index];
        if (index == 0) {
            element.style = "width:15%;text-align:center;"
        } else {
            element.style = "width:29%;text-align:center;"
        }
    }
    //DOM设置表体样式及内容
    let checkNum = 0;       //用于每一行的递增
    let checkRowId = "row"; //用于每一行的标识
    let rememberRowId = [];
    function rowPersonel(){
        var rowPersonelBodySum = "";
        for (const element of dataPersonel) {
            checkNum++;
            checkRowId += checkNum;
            rowPersonelBodySum += `
            <tr id="${checkRowId}" style="text-align:center;background:#EBEBEB;">
                <td><input class="per-box" type="checkbox"/></td>
                <td>${element.name}</td>
                <td>${element.age}</td>
                <td>${element.salary}</td>
            </tr>
            `
            rememberRowId[checkNum-1] = checkRowId; //记住每一行的Id值
            checkRowId = "row";
        }
        personelsheet.innerHTML = personelsheet.innerHTML.concat(rowPersonelBodySum);
        //绑定全选事件
        checkall.onclick = choseAllRow;
        //绑定单选事件
        Array.from(document.querySelectorAll(".per-box")).forEach(perRowItem => {
            perRowItem.onclick = choseOneRow;
        })
    }
    function deleteRowPer(){
        for (let i = 0; i < checkNum; i++) {
            if (rememberRowId[i]) {
                let checkChosen = document.querySelector(`#${rememberRowId[i]} input`).checked;
                if (checkChosen) {
                    document.querySelector(`#${rememberRowId[i]}`).outerHTML = null;
                    rememberRowId[i] = null;
                } // else nothing
            } // else nothing
        }
    }
    //全选
    function choseAllRow(){
        let checkAllChosen = checkall.checked;
        if (checkAllChosen) {
            rememberRowId.forEach(rowIdItem => {
                if (rowIdItem) {
                    document.querySelector(`#${rowIdItem} input`).checked = true;
                } //else none
            });
        } else {
            rememberRowId.forEach(rowIdItem => {
                if (rowIdItem) {
                    document.querySelector(`#${rowIdItem} input`).checked = false;
                }
            });
        }
    }
    //单选
    function choseOneRow(){
        let checkedResult = [];     //存储每次单选的最终结果
        rememberRowId.forEach(rowIdItem => {
            if (rowIdItem) {
                //有一个没有选中就不是全选
                !document.querySelector(`#${rowIdItem} input`).checked ? checkall.checked = false : null
                checkedResult.push(document.querySelector(`#${rowIdItem} input`).checked);
            } //else none
        })
        let isCheckedAll = checkedResult.every(checkItem => checkItem === true)
        !isCheckedAll ? checkall.checked = false : checkall.checked = true;
    }
    insert.onclick = rowPersonel;
    remove.onclick = deleteRowPer;
</script>

</html>

页面渲染-人员信息表.gif

选项卡功能

<style>
    *{
        margin: 0;
        padding: 0;
    }
    html,body{
        width: 100%;
        height: 100%;
    }
    .nav-card-header{
        list-style: none;
        display: flex;
        justify-content: space-evenly;
        align-items: center;
        height: 40px;
        background-color: black;
        color: white;
        cursor: default;
    }
    .nav-card-header li {
        width: 100%;
        height: 100%;
        flex: auto;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    .nav-card-header-active{
        background-color: white;
        color: black;
    }
    .play-card-section{
        min-height: 300px;
        position: relative;
        background-color: rgb(242, 240, 240);
    }
    .play-card-section li{
        width: 100%;
        height: 100%;
        position: absolute;
        background-size:contain;
        background-repeat: no-repeat;
        background-position: center;
    }
    .play-card-section-active{
        display: block;
        z-index: 10;
    }
</style>
<body>
    <ul id="nav_card" class="nav-card-header">
        <li class="nav-card-header-active">HTML</li>
        <li>CSS</li>
        <li>HTML+CSS</li>
        <li>JavaScript</li>
    </ul>
    <ul id="play_card" class="play-card-section">
        <li class="play-card-section-active"></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>
</body>
<script>
    let images = [
        {url:"./html.png"},
        {url:"./css.png"},
        {url:"./html+css.jpg"},
        {url:"./js.jpg"},
    ]

    //获取各选项卡的class属性集合
    let navCardItems = document.querySelectorAll(".nav-card-header li");
    //获取各选项卡内容页的class属性集合
    let playCardItems = document.querySelectorAll(".play-card-section li");

    /* 完善选项卡内容页显示背景:自定义每个标签属性,根据属性不同设置不同背景 */
    for (let m = 0; m < playCardItems.length; m++) {
        playCardItems[m].setAttribute(`data-index`,`${m}`);
        playCardItems[m].style.backgroundImage = `url("${images[m].url}")`;
    }

    /* 选项卡切换 ==> 方式一:使用let定义步长 */ 
    // 对各个选项卡绑定事件:点击时全部取消样式,并同时点击哪个标签,就把样式给哪个标签
    for (let i = 0; i < navCardItems.length; i++) {
    // for (var i = 0; i < navCardItems.length; i++) { --> 使用var就会出现问题
        navCardItems[i].onclick = function(){
            // console.log(i) //--> 使用var就会出现问题,每次点击都是3,而使用let每次点击就是正常的0123顺序
            for(let j = 0; j< navCardItems.length;j++){
                navCardItems[j].classList.remove("nav-card-header-active");
            }
            navCardItems[i].classList.add("nav-card-header-active");
            for (let m = 0; m < playCardItems.length; m++) {
                playCardItems[m].classList.remove("play-card-section-active");
                navCardItems[i].classList.contains("nav-card-header-active") ? playCardItems[i].classList.add("play-card-section-active"):null;
            }
        }
    }

    /* 选项卡切换 ==> 方式二:使用var定义步长,bug原因(var是函数作用域,整个函数范围都会有效,尤其是for循环)解决办法,使用this指向本对象 */ 

    // for (var i = 0; i < navCardItems.length; i++) {
    //     navCardItems[i].onclick = function(){
    //         //bug解决办法:使用this指向navCardItems[i]本身,这样就不会收到步长i影响
    //         for (var j = 0; j < navCardItems.length; j++) {
    //             navCardItems[j].classList.remove("nav-card-header-active");
    //         }
    //         this.classList.add("nav-card-header-active")
    //     }
    // }
</script>

选项卡功能.gif

点击删除待办事项

<body>
    <div id="nav">
        <div data-value="complete">Completed</div>
        <div data-value="thing">Item</div>
        <div data-value="time">Time</div>
    </div>
    <div id="list"></div>
    <button id="confirmList">添加任务</button>
    <button id="deleteList">删除完成</button>
</body>
<script>
    let toDoList = [  //模拟后端传输的数据
        { time: "2018-02-01", thing: "模拟", complete: false },
        { time: "2018-02-05", thing: "考试", complete: false },
        { time: "2018-02-08", thing: "传输", complete: false },
        { time: "2018-04-05", thing: "数据", complete: false },
        { time: "2018-07-25", thing: "任务", complete: false },
        { time: "2018-09-05", thing: "完成", complete: false },
        { time: "2018-12-05", thing: "删除", complete: false },
        { time: "2021-02-05", thing: "考试", complete: false },
        { time: "2021-12-15", thing: "报名", complete: false },
        { time: "2022-11-05", thing: "控制", complete: false },
        { time: "2022-11-15", thing: "原始", complete: false },
        { time: "2022-12-05", thing: "剔除", complete: false },
        { time: "2023-02-05", thing: "货运", complete: false }
    ]

    //DOM 设置基础样式
    nav.style.backgroundColor = "lightgray";
    nav.style.display = "flex";
    nav.style.height = "50px";
    nav.style.justifyContent = "space-evenly";
    nav.style.alignItems = "center";
    for (let i = 0; i < nav.children.length; i++) {
        document.querySelectorAll("#nav div")[i].style.width = "100%";
        document.querySelectorAll("#nav div")[i].style.height = "100%";
        document.querySelectorAll("#nav div")[i].style.textAlign = "center";
        document.querySelectorAll("#nav div")[i].style.display = "flex";
        document.querySelectorAll("#nav div")[i].style.justifyContent = "center";
        document.querySelectorAll("#nav div")[i].style.alignItems = "center";
        document.querySelectorAll("#nav div")[i].style.border = "5px solid lightgray";
        document.querySelectorAll("#nav div")[i].style.borderRight = "1px";
    }
    document.querySelectorAll("#nav div")[0].style.backgroundColor = "white";
    nav.firstElementChild.style.flexShrink = "1"
    nav.firstElementChild.nextElementSibling.style.flexShrink = "1";
    nav.lastElementChild.style.flexShrink = "1"
    list.style.backgroundColor = "lightgray";
    list.style.maxHeight = "60%";
    list.style.overflow = "auto";
    confirmList.style.display = "block";
    confirmList.style.margin = "20px auto";
    confirmList.style.backgroundColor = "white";
    confirmList.style.border = "1px solid black";
    confirmList.style.borderRadius = "6px";
    deleteList.style.margin = "auto";
    deleteList.style.display = "block";

    //新增一个勾选完成的元素节点
    let completeCheck = document.createElement("input");
    completeCheck.setAttribute("type", "checkBox");
    //DOM 新增元素,用于防止数据
    confirmList.onclick = function () {
        for (let i = 0; i < toDoList.length; i++) {
            let list_row = document.createElement("div");
            //有多少个后端数据,就新增多少行
            list.appendChild(list_row).id = `list_row_${i}`;
            //设置新增行的样式
            document.getElementById(`list_row_${i}`).style.backgroundColor = "white";
            document.getElementById(`list_row_${i}`).style.height = "30px";
            document.getElementById(`list_row_${i}`).style.margin = "5px";
            document.getElementById(`list_row_${i}`).style.display = "flex";
            document.getElementById(`list_row_${i}`).style.justifyContent = "space-evenly";
            document.getElementById(`list_row_${i}`).style.alignItems = "center";

            //判断后端数据的key与前端的data属性是否一致,如果一致就添加项目
            let nav_titles = document.querySelectorAll("#nav div");
            for (let j = 0; j < nav_titles.length; j++) {
                //循环,逐个逐个判断
                //创建行内的数据格子(直接复制一个div)
                let list_row_item = document.querySelector("#nav div").cloneNode(false);
                //如果每一行已经添加了3个元素节点,就不用追加了
                if (document.getElementById(`list_row_${i}`).children.length !== 3) {
                    document.getElementById(`list_row_${i}`).appendChild(list_row_item);
                    list_row_item.removeAttribute("data-value");
                    list_row_item.style.border = "";
                    list_row_item.style.borderRight = "1px solid lightgray";
                } //else none
                for (const key in toDoList[i]) {
                    //拿后端的toDoList的每一项,来与nav_title的其中一项对比,如果一致就添加项目
                    if (key == nav_titles[j].dataset.value) {
                        typeof toDoList[j][key] == "boolean" ? list_row_item.appendChild(completeCheck) : list_row_item.innerText = toDoList[i][key];
                        list_row_item = list_row_item.cloneNode(false);
                    } //else none
                }
                //克隆多个节点以备用
                completeCheck = completeCheck.cloneNode(false);
            }
        }
    }
    deleteList.onclick = function(){
        let completeCheckSum = document.getElementsByTagName("input");
        Array.from(completeCheckSum).forEach(completeCheckItem => {
            if (completeCheckItem.checked) {
                let completedRow = completeCheckItem.parentElement.parentElement.parentElement.removeChild(completeCheckItem.parentElement.parentElement);
                document.body.appendChild(completedRow);
            } //else none
        })
    }
</script>

动态添加和删除 - 添加删除待办事项.gif

懒加载

<!DOCTYPE html>
<head>
    <meta name="viewport" content="width=device-width">
</head>
<style>
    *{
        margin: 0;
        padding: 0;
    }
    /* 首尾样式 */
    html,body{
        height: 100%;
        position: relative;
        margin: 0 0.2em;
    }
    .title,.right{
        height: 2.5em;
        background-color: black;
        color: white;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 1em;
    }
    .title{
        position: sticky;
        top: 0;
    }
    .right{
        width: 100%;
        position: absolute;
        bottom: 0;
    }
    /* 列表样式 */
    .list-container{
        height:calc(100% - 2.5em);
        list-style-type: none;
        overflow:auto;
    }
    .list-item{
        display: flex;
        padding: 10px 0 ; /* 尽量设置为padding,再懒加载的时候,便于计算高度 */
        box-sizing: border-box; /* 怪异和模型不包含margin */
        height: 12em;
        overflow: hidden;
    }
    .list-item-img{
        width: 30%;
        height: 100%;
    }
    .list-item-text{
        padding: 0.8em;
        align-self: center;
        font-size:1.2em ;
    }
</style>
<body>
     <div id="title" class="title">RECOMMEND</div>
     <div id="list_container" class="list-container">
         <ul id="list" class="list">
            <li id="list_item" class="list-item">
                <img id="list_item_img" class="list-item-img" src="./materials/material-to_do_hires.jpg">
                <span id="list_item_text" class="list-item-text"></span>
            </li>
         </ul>
     </div>
     <div id="right" class="right">&reg; Copy Right</div>
</body>
<script>
    /* 模拟后端传输数据 */
    let data_1 = [
        {name:"模拟后端传输数据",url:"./materials/material-foodiesfeed.com_yellow-dolphin.jpg"},
        {name:"删除页面多余元素",url:"./materials/material-ice-cream.jpg"},
        {name:"渲染页面同时用于识别",url:"./materials/material-rose-outdoors.jpg"},
        {name:"免费 正版 高清 系列 下载",url:"./materials/material-sundae.jpg"},
    ]
    let data_2 = [
        {name:"免费 正版 高清 系列 下载",url:"./materials/material-to_do_hires.jpg"},
        {name:"免费 正版 高清 系列 下载",url:"./materials/material-foodiesfeed.com_yellow-dolphin.jpg"},
        {name:"免费 正版 高清 系列 下载",url:"./materials/material-foodiesfeed.com_yellow-dolphin.jpg"},
    ]

    /* 渲染页面 */
    list_item.remove(); //删除页面多余元素
    let count = 1; //计数器,同时用于识别id
    function addEle(data){
        for (let i = 0; i < data.length; i++) {
            //创建元素
            let listItemNew = document.createElement("li");
            let listItemNewImg = document.createElement("img");
            let listItemNewText = document.createElement("span");
            //添加li列表盒子
            list.appendChild(listItemNew);
            //添加image元素
            listItemNew.appendChild(listItemNewImg);
            //添加span文本
            listItemNew.appendChild(listItemNewText);
            //设置id和class
            listItemNew.id = `list_item_${count}`;
            listItemNew.className = "list-item";
            listItemNewImg.id = `list_item_img_${count}`;
            listItemNewImg.className = "list-item-img";
            listItemNewImg.src = `${data[i].url}`
            listItemNewText.id = `list_item_img_${count}`;
            listItemNewText.className = "list-item-text"
            let listItemNewTextContent = document.createTextNode(`${data[i].name}`);
            listItemNewText.appendChild(listItemNewTextContent);
            count++;
        }
        
    }
    addEle(data_1);  //渲染第一组数据
    
    /* 设置懒加载条件:当下滑滚动距离本元素底边框100px时,自动加载 */
    list_container.onscroll = function(){
        let listClientHeight = list.clientHeight;
        let listContainerClientHeight = list_container.clientHeight;
        //判断是否已经加载了,因为如果滚动到满足加载条件的时候,只能加载一次,不能反复加载
        let loaded = false; //初始默认还未加载
        if (loaded) return; //--> 如果已经加载就结束这一次,因为加载会添加元素,就不会满足小于50px的条件
        //判断是否距离本元素底边框100px
        let differ = listClientHeight - listContainerClientHeight - Math.ceil(list_container.scrollTop);
        if (differ < 50) {
            addEle(data_2);   //渲染下一组数据
            loaded = true;
        }
    }

    /* 适配移动端 - 计算font-size - 然后在css使用em单位,这样在不同的移动端就可以有相类似的显示效果 */
    document.documentElement.fontSize = document.documentElement.clientWidth/375 * 16 + "px";
</script>

懒加载功能.gif