js操作dom

139 阅读6分钟

vue自定义指令(当需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令)

注意vue3和vue2的自定义指令的钩子函数有变化了,具体可以查看官网,下面用vue3的语法实现

需求1 v-fbind 实现input框绑定值并自动获取焦点

<input type="text" v-fbind="inputValue">

directives:{
  fbind:{
            mounted(el,binding) {
               el.value=binding.value
               el.focus()
            }
       }
}

需求2 v-pin 实现把元素固定在页面上(动态指令参数)

    <p>Scroll down the page</p>
    <input type="range" min="0" max="500" v-model="pinPadding">
    <p v-pin[direction]="pinPadding">Stick me {{`${pinPadding}px`}} from the {{direction || 'top'}} of the page
    </p>
    
    
directives:{
  pin(el,binding){
        el.style.position='fixed'
        const s = (binding.arg === 'left' ? 'left' : 'top')
        el.style[s] = binding.value + 'px'
    }
}

js中常见的dom操作

一 元素节点增删查

1 查

1.1 获取节点自身
1.document.getElementById("test")
根据元素id返回元素,如果不存在该元素,则返回null
2.document.getElementsByTagName("div") , 返回数组
返回指定 标签名 的节点数组, 没有找到返回空数组
3.document.getElementsByClassName("test"), 返回数组
返回指定 类名 的节点数组,没有找到返回空数组
4.document.querySelector()
返回匹配指定的CSS选择器的元素节点。如果有多个节点满足匹配条件,则返回第一个匹配的节点。如果没有发现匹配的节点,则返回null
5.document.querySelectorAll(), 返回数组
返回指定 CSS选择器 的元素节点数组,没有找到返回空数组
1.2 获取关系节点
1.获取子元素
<body>
  <div class="test">
    333
    <p>test3----1</p>
    <p>test3----2</p>
    <p>test3----3</p>
  </div>
</body>
<script>
  let dos = document.getElementsByClassName("test")[0];
  // firstChild  childNodes  lastChild 会包含文本(空文本等); 
  // 而firstElementChild  children  lastElementChild 返回非纯文本的标签元素
  let firstChild = dos.firstChild; // "  333   "
  let firstElementChild = dos.firstElementChild; // <p>test3----1</p>
  let childNodes = dos.childNodes; // NodeList [text, p, text, p, text, p, text]
  let children = dos.children; // HTMLCollection [p,p,p]
  console.log(firstChild, firstElementChild, childNodes, children)

  let lastChild =  dos.lastChild
  let  lastElementChild = dos.lastElementChild
</script>
2.获取父元素
 元素.parentElement   元素.parentNode
3.获取兄弟元素
// previousElementSibling  nextElementSibling 前后兄弟标签元素
// nextSibling previousSibling 前后兄弟节点包含纯文本

2 增加( 创建、插入节点)

2.1.createElement
document.createElement("input"); 
2.2.createComment
//创建一个注释节点
document.createComment("auth")

2.3.insertBefore
// 在指定子节点前插入一个节点
parentNode.insertBefore(newNode, refNode);
2.4.appendChild
// 就是将指定的节点添加到调用该方法的节点的子元素的末尾
parent.appendChild(child);

3 删除

3.1 removeChild
// 移除某个指定的子节点
element.removeChild(child);
3.2 remove
// 移除某个节点本身以及其子节点
element.remove();

3 替换节点

//用新节点替换老节点,和remove的区别是老节点还存在
replaceChild(newNode,oldNode)

使用实例,在切换按钮权限时,有权限展示该按钮,没有权限时不能直接删除,否则加不回来了,所以可以这样写

const removeEl = (el:any) => {
    // 在绑定元素上存储父级元素
    el._parentNode = el.parentNode
    // 在绑定元素上存储一个注释节点
    el._placeholderNode = document.createComment("auth")
    // 使用注释节点来占位
    el.parentNode?.replaceChild(el._placeholderNode, el)
}

const addEl = (el:any) => {
    // 替换掉给自己占位的注释节点
    el._parentNode?.replaceChild(el, el._placeholderNode)
}

二 元素属性操作Api

1.修改设置属性

// setAttribute(属性名,属性值)  元素.属性 = 属性值
let pp = document.getElementById("pp");
pp.setAttribute("t1","t1")
pp.t2 = "t222"

2.读取属性

<body>
  <input id="inp">
</body>
<script>
  let input = document.getElementById("inp");

  // 自定义属性 读取应和操作是同一套,否则读取不到
  input.setAttribute("t1","t11")
  console.log(input.getAttribute("t1")) //t11
  console.log(input.t1) //undefined

  input.t2= 't2'
  console.log(input.getAttribute("t2")) //null
  console.log(input.t2)//t2

  // 固有页面显示属性 读取应和操作可以随便,建议最好同一套操作
  input.setAttribute("title","t11")
  console.log(input.getAttribute("title")) //t11
  console.log(input.title) //t11

  input.type= 'text'
  console.log(input.getAttribute("type")) //text
  console.log(input.type)//text
</script>

3.移除属性

元素.removeAttribute(属性名)

三 元素常见位置属性

1.布尔属性

// checked ( 默认选中 , 一般用于checkbox)
// selected ( 默认选中 ,一般用于option)
// readOnly(O必须大写) (不能修改数据 , 但不影响数据传输 , 后端仍然可以获得该数据)
// disabled (提交后后端不会获得该数据)
// multiple ( 多选 , 按住ctrl和shift可以多选)可以让下拉菜单变成列表菜单
// hidden ( 隐藏 );Html5新增属性

手机号:<input type="hidden">
<input type="checkbox" checked="checked">北京
<input type="checkbox" >上海
<select name="" id="">
  <option value="0" disabled>请选择</option>
  <option value="1" >北京</option>
  <option value="2" selected>上海</option>
</select>

2.字符串属性

// id:表示元素的唯一标识;一个页面上不能出现相同id值的两个元素
// title:可见元素在鼠标掠过时出现的提示信息
// href:一般用于a和link这两个元素,表示超链接
// src:用于image、script、object等等,表示数据的来源
// name:一般用于表单元素,表示控件的名字
// value:一般用于表单元素,是控件要传到后端的值
// class:定义在元素上引用的样式类名
// style :可以定义某个元素的样式 
// nodeName: 节点名称,大写字符串
// className:类名称

<body>
<button id="btn"  onclick="f()" class="btn active">
 我是parent并列的另外一个button
</button>
<script type="text/javascript">
   let btn = document.getElementById("btn");
   console.log(btn.nodeName) //BUTTON
   console.log(btn.className) // "btn active"
</script>
</body>

3. data属性 (IE8及以下不兼容)

  1. 对于设置私有属性,可以使用新增属性:"data-xxxx"。js中用dataset来获取此类属性的集合。
  2. 如果data-后面是一个单词,比如:data-one,那么使用:Ele.dataset.one来获取属性值;
  3. 如果data-后面不是一个单词,比如:data-one-two,那么使用:Ele.dataset.oneTwo(驼峰形式)来获取属性值
<body>
<ul>
  <li data-id="1">1111</li>
  <li data-id="2">2222</li>
</ul>
</body>
<script>
 let dom = document.getElementsByTagName("li")[1];
 console.log(dom.dataset.id);
</script>

4. classList属性(IE11以下不支持。)

这个属性中有add、remove、contains、toggle等方法。
 box1.classList //返回类名数组
 box1.classList.add("box-box")  // 添加类名
 box1.classList.remove("box2") // 移除某个类名
 box1.classList.contains("box1")) //是否含有类名

5. 位置属性

// style.width  .style.xxx 等都是获取元素的 `行内样式` ,若行内样式没有显示添加,则返回空字符串, 读写都是字符串格式。

// offsetWidth offsetHeight  offsetWidth = padding + width + border, 返回数值整型,只可以读,不可以设置(注意默认box-sizing属性是content-box),如果修改盒子的box-sizing属性为border-box,则offsetWidth = width

// offsetTop offsetLeft 返回的是该盒子上边(左边)距离其最近的非静态定位的父元素的上边(左边)边界距离,注意是直接父元素。返回数值整型,只可以读,不可以设置
//scrollTop:当前元素顶端距离窗口顶端距离,鼠标滚轮会影响其数值.

四 元素身上的事件

1. 绑定方式

// 1.行内式  on+事件类型 = " 执行函数() "
<div id="btn" onclick="clickone()"></div> 
<script>
    function clickone() {console.log("clickone",this);} 
    // this指向window
</script>

// 2.js代码中绑定
 document.getElementById("btn").onclick = function(){ 
     console.log(this) 
    //this指向当前绑定dom事件对象,若是子元素触发该事件,this依然是当前btn对应的dom对象
 } 

// 3.监听方式
 document.getElementById("btn").addeventlistener("click",clickone);  
//this指向当前绑定dom事件对象,若是子元素触发该事件,this依然是当前btn对应的dom对象

2.on绑定和addeventlistener绑定区别

// on绑定的事件若绑定同一类型的事件,譬如都是click事件,js代码后面的会覆盖前面的
document.getElementById("btn").onclick = function () {
        console.log("btn11", this)
    }
document.getElementById("btn").onclick = function () {
      console.log("btn22", this)
  }
  // 控制台只会输出  btn22
  
  
 //addEventListener可以同时绑定多个同类型事件。
document.getElementById("btn").addEventListener('click',function()  {console.log(123)}
);
document.getElementById("btn").addEventListener('click',function(){console.log(456)}
);
// 控制台会输出123,456. 说明addEventListener可以同时绑定多个事件。

3.addeventlistener的第3个参数

<style>
    * {
        padding: 0;
        margin: 0;
    }

    #parent {
        background-color: pink;
    }

    #child {
        position: absolute;
        top: 50px;
        width: 100px;
        height: 30px;
        background-color: purple;
    }

    #btn {
        position: absolute;
        right: 0;
        top: 0;
    }

</style>
<body>

<div id="parent">
    <p>parent-----parent</p>
    <p id="child">child</p>
</div>
<button id="btn">我是parent并列的另外一个button</button>
<script type="text/javascript">

    let parent = document.getElementById('parent');
    let child = document.getElementById('child');
    let btn = document.getElementById('btn');
    // 父元素addEventListener第3个参数:
    
    // 没写或者值为false,都表示若点击子元素,先触发子元素的事件,然后再触发父元素的事件。即冒泡顺序执行;
    // 值为 true, 若点击子元素,先触发父元素同类型的事件,再触发子元素的事件。即捕获顺序执行;
    parent.addEventListener('click', function () {
        console.log("parent")
    });
    // 虽然child定位出去了,但是点击子元素依然会触发父元素同类型事件
    child.addEventListener('click', function () {
        console.log("child")
        setTimeout(()=>{
            console.log('time out')
        },3000)
    })
    // 虽然页面显示btn元素在parent元素内部,但是点击btn,依然不会触发parent元素。
    btn.addEventListener('click', function () {
        console.log("btn")
    })
</script>
</body>

4.事件执行的另外一种方式 btn.事件类型()

<body>
<button id="btn"  onclick="f()">我是parent并列的另外一个button</button>
<script type="text/javascript">
    function f(){
        console.log('fff',this)  // this都指向window
    }
    let btn = document.getElementById("btn")
    btn.click() // 自执行
</script>
</body>

5.出于性能考虑,建议动态内容上绑定事件时采用事件代理模式

js
  //1.事件代理,即事件委托,是利用了事件冒泡的机制,将原本需要绑定在自身的事件处理函数代理在父元素身上(因为事件冒泡,点击子元素,父元素身上的事件也会触发)。
 // 2好处:这样对于动态的元素就不用担心找不到dom而导致事件绑定不成功;也可以减少为多个dom元素重复绑定事件而提高性能。关键点在于点击父元素通过e.target判断作用在哪个子元素身上,再写对应的事件处理函数。
  <script>
  //点击li,打印对应的index
  
  //给li自身绑定事件
    for (var i = 0; i < lis.length; i++) {
        lis[i].index = i
            // onclick是小写啊
        lis[i].onclick = function() {
            console.log(this.index)
        }
    }
    
    
    for (let i = 0; i < lis.length; i++) {
        lis[i].addEventListener('click', function() {
            console.log(i)
        })
    }
    
    // 事件代理到父级身上
    ul.onclick = function(e) {
        if (e.target.nodeName.toLowerCase() == "li") {
            console.log(e.target.innerHTML)
        }
    }
 </script>
 
 补充:var与let的比较,来自es6官网
   var a = [];
   for (var i = 0; i < 10; i++) {
        a[i] = function() {
            console.log(i);
        };
        // a[i](); //for循环里面调用 0 1 2 3 4 5 6 7 8 9
    }
    a[6](); // for 循环外面调用 10 
    // 1.for循环中var定义的变量是全局变量
    // 2.js事件循环机制,先执行同步任务再渲染dom,微任务在dom渲染之前执行,宏任务在dom渲染之后执行(dom事件属于宏任务)
 
 
 
    var a = [];
    for (let i = 0; i < 10; i++) {
        a[i] = function() {
            console.log(i);
        };
    }
    a[6](); // for 循环外面调用 6
    // 变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6
    // JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
    
    
for (let i = 0; i < 3; i++) {
    let i = 'abc'; //这里如果没有定义i,则会找父级作用域,依次打印0 1 2
    console.log(i);
}
// 另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

6.node.contains(e.target) 判断一个节点是否是他的子节点以及自身,返回值为布尔值

// 场景: 点击盒子之外区域关闭某个盒子
<body>
<div id="box">
    <p>我是一个p</p>
    <button>我是一个button</button>
</div>
<script type="text/javascript">
let node = document.getElementById("box");
window.addEventListener("click", function (e) {
    if (node.contains(e.target)) {
        console.log("点击的是box盒子")
        return
    }
    console.log("点击的是box盒子之外的区域")
})
</script>
</body>