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及以下不兼容)
- 对于设置私有属性,可以使用新增属性:"data-xxxx"。js中用dataset来获取此类属性的集合。
- 如果data-后面是一个单词,比如:data-one,那么使用:Ele.dataset.one来获取属性值;
- 如果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>