DOM节点
网页是由DOM树组成的;大概的DOM树
其实document不止html一个节点
window和document的关系
BOM(browser object model):浏览器对象模型,没有相关标准,表示一些和网页无关的浏览器功能,如window、location、navigator、screen、history等
DOM(document object model):文档对象模型,W3C标准,HTML和XML文档的编程接口
window属于BOM,document属于DOM中的核心,但是window引用着document
DOM0级事件
<button onclick="console.log(1)">按钮</button>
特点:
1、效率高
2、节点上onclick属性被Node.cloneNode克隆,通过JS赋值的onclick不可以
3、移除事件简单:dom.onclick=null
4、兼容性好
注意事项:
1、事件处理函数中,this是当前的节点
2、如果调用函数,会在全局作用域查找
3、只能定义一个事件回调函数
<body>
<button onclick="console.log('按钮1被点击了')">按钮1</button>
<div>
<button onclick="onclick2()">按钮2</button>
</div>
<div>
<button onclick="console.log(this)">按钮3</button>
</div>
<div>
<button onclick="onclick4()">按钮4</button>
</div>
<script>
function onclick2() {
console.log("按钮2被点击了")
}
(function() {
function onclick4() {
console.log("按钮4被点击了")
}
})()
</script>
</body>
点击复制区域的btn2事件不会生效
<body>
<div id="sourceZone">
<div>原始区域:</div>
<div id="sourceButtons">
<button onclick="console.log('按钮1被点击了')">按钮1</button>
<div>
<button class="btn2">按钮2</button>
</div>
</div>
</div>
<div id="copyZone">
<div>复制区域:</div>
</div>
<script>
function onclick2() {
console.log("按钮2被点击了")
}
document.querySelector(".btn2").onclick = onclick2;
copyZone.append(sourceButtons.cloneNode(true));
</script>
</body>
DOM2级事件
事件注册:
语法:targe.addEventListener(type, listener, useCapture)或者targe.addEventListener(type, listener, options)
useCapture: true表示捕获节点传播到目标的时候触发,反之为冒泡阶段传到目标的时候触发,默认是false
注意: 如果这个参数相同并且事件回调相同,事件将不会被添加
<body>
<button id="btn">按钮</button>
<script>
function onclick() {
console.log("btn被点击")
}
btn.addEventListener("click",onclick)
btn.addEventListener("click",onclick)
btn.addEventListener("click",onclick,{capture:true})
btn.addEventListener("click",onclick,{capture:true,once:true})
</script>
</body>
options:
once:是否只响应一次,可以在视频播放的时候在document上绑定事件并指定只能触发一次
passive:设置为true时,事件处理成不会调用preventDefault();某些触摸事件的事件监听器在尝试处理滚动时,可能阻止浏览器的主线程,导致滚动事件处理期间性能降低
signal:AbortSignal的abort()方法被调用时,监听器会被移除,AbortSignal可以用于取消fetch事件
按钮只能被点击一次
<body>
<button id="btn">按钮</button>
<script>
const controller = new AbortController();
const signal = controller.signal;
btn.addEventListener("click",function () {
console.log("btn被点击")
controller.abort();
},{
signal
})
</script>
</body>
点击屏幕播放视频
事件冒泡和捕获
<body>
<button class="btn">按钮</button>
<script>
const btn = document.querySelector(".btn")
window.addEventListener("click",function () {
console.log("window捕获")
},true)
document.addEventListener("click",function () {
console.log("document捕获")
},true)
btn.addEventListener("click",function () {
console.log("btn捕获")
},true)
btn.addEventListener("click",function () {
console.log("btn冒泡")
})
document.addEventListener("click",function () {
console.log("document冒泡")
})
window.addEventListener("click",function () {
console.log("window冒泡")
})
</script>
</body>
event.preventDefault:阻止默认行为,比如有href属性的a标签,阻止默认行为之后将不会再跳转;但是并不影响事件继续传播
stopPropagation:阻止捕获和冒泡阶段中当前事件的进一步传播
<body>
<button id="btn">按钮</button>
<script>
const btn = document.querySelector("#btn")
btn.addEventListener("click",function () {
console.log("btn捕获")
},true)
btn.addEventListener("click",function () {
console.log("btn冒泡")
})
document.addEventListener("click",function () {
console.log("document冒泡")
})
document.addEventListener("click",function (e) {
console.log("document捕获")
e.stopPropagation();
},true)
</script>
</body>
stopImmediatePropagation:阻止监听同一事件的其他事件监听器被调用
<body>
<button id="btn">按钮</button>
<script>
const btn = document.querySelector("#btn")
document.addEventListener("click",function (e) {
console.log("document捕获")
},true)
btn.addEventListener("click",function () {
console.log("btn捕获")
},true)
btn.addEventListener("click",function () {
console.log("btn冒泡1")
})
btn.addEventListener("click",function (e) {
console.log("btn冒泡2");
e.stopImmediatePropagation();
})
btn.addEventListener("click",function () {
console.log("btn冒泡3")
})
document.addEventListener("click",function () {
console.log("document冒泡")
})
</script>
</body>
<body>
<button id="btn">按钮</button>
<script>
const btn = document.querySelector("#btn")
document.addEventListener("click",function (e) {
console.log("document捕获")
},true)
btn.addEventListener("click",function () {
console.log("btn捕获")
},true)
btn.addEventListener("click",function () {
console.log("btn冒泡1")
})
btn.addEventListener("click",function (e) {
console.log("btn冒泡2");
e.stopPropagation();
})
btn.addEventListener("click",function () {
console.log("btn冒泡3")
})
document.addEventListener("click",function () {
console.log("document冒泡")
})
</script>
</body>
target和currentTarget
target:触发事件的元素,谁触发的
currentTarget:事件绑定的元素,谁添加的事件监听函数
<body>
<button id="btn">按钮</button>
<script>
const btn = document.querySelector("#btn")
document.addEventListener("click",function (e) {
console.log("target:",e.target)
console.log("currentTarget:",e.currentTarget)
})
btn.addEventListener("click",function () {
console.log("btn被点击了")
})
</script>
</body>
事件委托:
利用事件传播的机制,利用外层节点处理事件的思路
优点:
减少内存消耗
”动态性“更好,比如增加节点的时候,不需要额外添加事件
<body>
<ul id="ulList">
<li>
<div>白菜</div>
<button class="btn-buy" data-id="1">购买</button>
</li>
<li>
<div>萝卜</div>
<button class="btn-buy" data-id="2">购买</button>
</li>
</ul>
<script>
ulList.addEventListener("click",function (e) {
if(e.target.classList.contains("btn-buy")){
console.log("商品id:", e.target.dataset.id)
}
})
</script>
</body>
DOM3级事件
自定义事件
内置事件:click、blur、mousewheel等等;内置事件一般是用户交互触发,也有系统自己调用,比如onload,动画停止等等
代码触发内置事件:element.eventType或者new [Event] + dispatchEvent
<body>
<div>
<button id="btnDownload">下载文件</button>
</div>
<script>
function createBlob(content,type) {
return new Blob([content],{type})
}
function downloadFile(fileName,content,type="text/plain") {
const aEl = document.createElement("a");
// 指定下载文件名
aEl.download = fileName;
const blob = createBlob(content,type);
// 生成url
const url = URL.createObjectURL(blob);
aEl.href = url;
document.body.appendChild(aEl);
// 触发点击事件
aEl.click();
// 删除节点
document.body.removeChild(aEl);
// 释放url
URL.revokeObjectURL(url)
}
btnDownload.addEventListener("click",function () {
downloadFile("测试.txt","测试的文本")
})
</script>
</body>
这里是使用aEl.click()触发的事件;也可以使用dispatch触发
// 触发点击事件
// aEl.click();
const event = new MouseEvent("click");
aEl.dispatchEvent(event)
这里有个小技巧url是自带uuid的,所以可以使用
console.log(URL.createObjectURL(new Blob([''])).split('/').pop())
自定义事件的方法
document.createEvent(已废弃)
语法:const event = document.createEvent(type)
new Event()
语法:const event = new Event(type,eventInit);缺点:不能携带额外参数
<body>
<div class="container">
<button id="btn">开始吧</button>
</div>
<div>
<div>
流程1:
<div id="step1"></div>
</div>
<div>
流程2:
<div id="step2"></div>
</div>
</div>
<script>
function dispatchEE(target, type) {
const event = new Event(type,{
// 这里可以定义参数
});
target.dispatchEvent(event);
}
btn.addEventListener("click", function(){
setTimeout(() => {
dispatchEE(step1,'step-1')
}, 2000);
})
step1.addEventListener("step-1", function(){
step1.textContent = "流程1进行中。。。。";
setTimeout(() => {
dispatchEE(step2,'step-2')
}, 2000);
})
step2.addEventListener("step-2", function(){
step2.textContent = "流程2进行中。。。。";
setTimeout(() => {
dispatchEE(window,'finish');
}, 2000);
})
window.addEventListener("finish",function() {
console.log("finish")
})
</script>
</body>
new CustomEvent()
语法:const event = new CustomEvent(type,eventInit)
<body>
<div class="container">
<button id="btn">开始吧</button>
</div>
<div>
<div>
流程1:
<div id="step1"></div>
</div>
<div>
流程2:
<div id="step2"></div>
</div>
</div>
<script>
function dispatchEE(target, type,data) {
const event = new CustomEvent(type,{
detail:data
});
target.dispatchEvent(event);
}
btn.addEventListener("click", function(){
setTimeout(() => {
dispatchEE(step1,'step-1',{param:"step1的启动参数"})
}, 2000);
})
step1.addEventListener("step-1", function(ev){
step1.textContent = "流程1进行中。。。。,参数有:" + ev.detail.param;
setTimeout(() => {
dispatchEE(step2,'step-2')
}, 2000);
})
step2.addEventListener("step-2", function(){
step2.textContent = "流程2进行中。。。。";
setTimeout(() => {
dispatchEE(window,'finish');
}, 2000);
})
window.addEventListener("finish",function() {
console.log("finish")
})
</script>
</body>
从继承关系来看,CustomEvent是Event的扩展
Event时候简单的自定义事件,CustomEvent支持传递数据的自定义事件
CustomEvent可以在webwork中使用
CustomEvent可能存在兼容性问题,需要使用垫片
JS操作样式
样式来源
浏览器默认样式(用户代理样式)
浏览器用户自定义样式、link引入的外部样式、style标签样式、内联样式
样式优先级
1、内联样式
2、ID选择器
3、类和伪类选择器
4、标签选择器
5、通配符选择器
还有important优先级最高
操作元素节点上的style属性
style属性名是驼峰语法
<body>
<div id="divEl">这是一个div</div>
<script>
divEl.style.backgroundColor="red";
divEl.style.fontSize = "20px"
</script>
</body>
style.cssText批量赋值
<body>
<div id="divEl">这是一个div</div>
<script>
divEl.style.cssText = "background-color:red;font-size:20px"
</script>
</body>
注意: style是自读的,不可以直接使用object修改
操作元素节点classList和className属性
DOMTokenList.toggle:从列表中删除一个给定的标记并返回false,如果标记不存在则添加并返回true
语法:tokenList.toggle(token,force);如果force为真则只添加不删除
<body>
<button id="btnTooggleFalse">btnTooggleFalse</button>
<button id="btnTooggleTrue">btnTooggleTrue</button>
<div id="divEl">这是一个div</div>
<script>
btnTooggleFalse.addEventListener("click", function(){
divEl.classList.toggle("test-div")
})
btnTooggleTrue.addEventListener("click", function(){
divEl.classList.toggle("test-div",true)
})
</script>
</body>
点击btnTooggleFalse按钮,会在添加和删除test-div之前切换
点击btnTooggleTrue按钮,之后添加test-div
操作style节点的内容
style节点本质还是Node节点
<style id="test">
.test-div {
color: red;
}
</style>
<body>
<button id="btnReplace">替换</button>
<div class="test-div">这是一个div</div>
<script>
const ssEl = document.getElementById("test")
btnReplace.addEventListener("click", function(){
ssEl.textContent = ssEl.textContent.replace("color: red","color: green")
})
</script>
</body>
操作已有的style节点
CSSOM:CSS Object Model是一组允许用Javascript操作CSS的API
<body>
<button id="btnUpdate">更改style节点</button>
<div class="test-div">这是一个div</div>
<script>
const ssEl = document.getElementById("test")
btnUpdate.addEventListener("click", function(){
// 找到所有的style节点
const styleSheets = Array.from(document.styleSheets);
const st = styleSheets.find(s=>s.ownerNode.id === "test");
const rule = Array.from(st.rules).find(r=>r.selectorText === ".test-div");
const styleMap = rule.styleMap;
styleMap.set("background-color","green")
})
</script>
</body>
动态创建style节点
<body>
<button id="btnUpdate">更改style节点</button>
<div class="test-div">这是一个div</div>
<script>
const ssEl = document.getElementById("test")
btnUpdate.addEventListener("click", function(){
const styleNode = document.createElement("style");
styleNode.textContent = `.test-div {
background-color:green;
}`;
document.head.appendChild(styleNode)
})
</script>
</body>
更改外部引入的样式
本质也是CSSStyleSheet
动态创建link节点引入样式
function importCSSByUrl(url) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = url;
document.head.appendChild(link);
}
window.getComputeStyle
获取计算后的样式
语法:const style = window.getComputedStyle(element,[pseudoElt])
pseudoElt:表示是否查询伪元素,比如可以传入"before"
该方法会引起重排
订阅发布中心
是一种消息通知机制,也是一种发布订阅模式的实际应用;应用场景比如公众号信息、短信提醒等
基础功能
on:订阅
once:订阅,但是只接受一次通知
off:取消订阅
emit:派发事件,通知订阅者
使用window绑定事件
<script>
window._on = document.addEventListener;
window._off = document.removeEventListener;
window._emit = (type,data)=>window.dispatchEvent(new CustomEvent(type,{detail:data}));
window._once = (type,listener)=>window.addEventListener(type,listener,{capture:true,once:true});
</script>
<script>
function onEvent(e) {
console.log(`event-x收到数据:${e.detail}`)
}
// 订阅消息
window._on("event-x",onEvent);
window._once("event-once",e=>console.log(`once:${e.detail}`));
// 触发两次once只会收到一次信息
window._emit("event-once",{meg:"传递了一次信息"})
window._emit("event-once",{meg:"传递了两次次信息"})
// 给event-x发送信息
window._emit("event-x",{meg:"给event-x发送信息"})
window._off("event-x",onEvent)
window._emit("event-x",{meg:"再给event-x发送信息"})
</script>
原理:
根源是EventTarget
document和元素节点也是继承于EventTarget
window也是继承于EventTarget
XMLHttpRequest、WebSocket也继承于EventTarget
EventTarget:是一个构造函数,可以创建一个新的EventTarget对象实例
EventTarget身上的方法:
EventTarget.addEventListener:在EventTarget上注册特定事件类型的事件处理程序
EventTarget.removeEventListener:EventTarget中删除事件侦听器
EventTarget.dispatchEvent将事件分派到此EventTarget
缺点:
1、不能多实例化
2、挂载window影响全局
3、不能传递多个参数
使用构造函数
参数不能传递多个
<script>
class EventEmitter extends EventTarget {
on = this.addEventListener;
off = this.removeEventListener;
emit = (type,data)=>this.dispatchEvent(new CustomEvent(type,{detail:data}));
once = (type,listener)=>this.addEventListener(type,listener,{capture:true,once:true});
}
</script>
<script>
const emitter = new EventEmitter();
function onEvent(e) {
console.log(`event-x收到数据:${e.detail}`)
}
// 订阅消息
emitter.on("event-x",onEvent);
emitter.once("event-once",e=>console.log(`once:${e.detail}`));
// 触发两次once只会收到一次信息
emitter.emit("event-once",{meg:"传递了一次信息"})
emitter.emit("event-once",{meg:"传递了两次次信息"})
// 给event-x发送信息
emitter.emit("event-x",{meg:"给event-x发送信息"})
emitter.off("event-x",onEvent)
emitter.emit("event-x",{meg:"再给event-x发送信息"})
</script>
升级改造
<script>
class EventEmitter extends EventTarget {
on = (type,listener,options) => this.addEventListener(type,function wrap(e) {
return (listener.__wrap__ = wrap,listener.apply(this,e.detail || []))
},options);
off = (type,listener)=>this.removeEventListener(type,listener.__wrap__);
emit = (type,...args)=>this.dispatchEvent(new CustomEvent(type,{detail:args}));
once = (type,listener)=>this.on(type,listener,{capture:true,once:true});
}
</script>
<script>
const emitter = new EventEmitter();
function onEvent(msg) {
console.log(`event-x收到数据:`,this,msg)
}
// 订阅消息
emitter.on("event-x",onEvent);
emitter.once("event-once",(msg)=>console.log(`once:${msg}`));
// 触发两次once只会收到一次信息
emitter.emit("event-once","传递了一次信息")
emitter.emit("event-once","传递了两次次信息")
// 给event-x发送信息
emitter.emit("event-x","给event-x发送信息")
emitter.off("event-x",onEvent)
emitter.emit("event-x","再给event-x发送信息")
</script>