1. 节点操作
1.1 删除节点
-
node.removeChild()
方法从node
节点中删除一个子节点,返回删除的节点。
<body>
<button>删除</button>
<ul>
<li>熊大</li>
<li>熊二</li>
<li>光头强</li>
</ul>
<script>
let ul = document.querySelector('ul');
let btn = document.querySelector('button');
/**
* 点击一下删除一个ul的节点
*/
btn.onclick = function () {
if (ul.children.length === 0) {
alert('已经删除完了!');
} else {
ul.removeChild(ul.children[0]);
}
}
</script>
</body>
1.2 案例:删除留言
<div class="content">
<div class="edit" style="overflow: hidden;">
<textarea name="" id=""></textarea>
<button>发布</button>
</div>
<ul></ul>
</div>
let button = document.querySelector('button');
let textarea = document.querySelector('textarea');
let ul = document.querySelector('ul');
button.onclick = function () {
if (textarea.value === '') {
alert("您输入的内容为空!");
return false;
}
let li = document.createElement('li');
// 拿到文本域的内容
li.innerHTML = textarea.value + "<a href='javascript:void(0);'>删除</a>";
// 新发布的内容永远是第一个的前面
ul.insertBefore(li, ul.children[0]);
// 发送完成之后清空文本域
textarea.value = '';
/**
* 删除留言操作
* @type {ElementTagNameMap[string] | null}
*/
// 获取 a
let as = ul.querySelector('a');
as.onclick = function () {
ul.removeChild(as.parentNode);
}
};
1.3 复制(克隆)节点
node.cloneNode();
-
node.cloneNode()
方法返回调用该方法的节点的一个副本。也称为克隆节点、拷贝节点。
注意
如果括号参数为空或者为
false
,则是浅拷贝,即只克隆赋值节点本身,不克隆里面的子节点。如果括号参数为
true
,则是深拷贝,会复制节点本身及里面的子节点。
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
let ul = document.querySelector('ul');
// 克隆一个ul的子节点放到最后
// 参数为空或者里面是false则是浅拷贝 只复制标签不复制里面的内容
let li = ul.children[1].cloneNode(true);
ul.appendChild(li);
</script>
</body>
1.4 动态生成表格
因为对象数组中存储的学生信息是动态的,所以需要
Js
动态的生成表格。根据数组中对象的个数循环创建表格的行。
根据每个对象中属性的个数循环创建表格的单元格个数。
table {
width: 500px;
margin: 100px auto;
border-collapse: collapse;
text-align: center;
}
td,
th {
border: 1px solid #333333;
}
thead tr {
height: 40px;
background-color: #ccc;
}
<table>
<thead>
<tr>
<th>姓名</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
let tbody = document.querySelector('tbody');
// 创建需要渲染的数据
let data = [
{
name: '张三',
subject: 'JavaScript',
score: 99
}
];
// 循环数据创建行
for (let i = 0; i < data.length; i++) {
let tr = document.createElement('tr');
tbody.appendChild(tr);
for (let key in data[i]) {
let td = document.createElement('td');
td.innerHTML = data[i][key];
tr.appendChild(td);
}
// 循环遍历对象创建单元格
let optTd = document.createElement('td');
optTd.innerHTML = "<a href='javascript:void(0);'>删除</a>";
// 将删除操作按钮添加到行中
tr.appendChild(optTd);
// 获取到该行中的 a
let as = tr.querySelector('a');
// 为a定义一个删除的单击事件
as.onclick = function () {
console.log(as.parentNode);
tbody.removeChild(as.parentNode.parentNode)
};
}
let trs = tbody.children;
for (let i = 0; i < trs.length; i++) {
trs[i].onmouseover = function () {
this.style.backgroundColor = 'skyblue'
};
trs[i].onmouseout = function () {
this.style.backgroundColor = ''
}
}
1.5 创建元素的三种方式
document.write();
element.innerHtml;
document.createElement();
三种创建方式的区别
document.write();
: 是直接将内容写入页面的内容流,但是文档流执行完毕之后使用,则导致页面重绘。element.innerHtml;
:是将内容写入某个DOM
节点,不会导致页面全部重绘。 创建多个元素效率高(不要使用字符串拼接的方式,因为这样会出现太多的字符串拼接操作,频繁的创建导致内存消耗,而是采用数组拼接的方式 array.push)。document.createElement();
:创建多个元素效率 稍微低一点点,但是结构清晰。总结:不同的浏览器下,innerHtml 效率要比createElement高。
<body>
<button>document.write()导致页面重绘</button>
<div>456</div>
<div class="inner"></div>
<div class="create"></div>
<script>
// 1. document.write() 在文档中写一个标签 如果文档流执行完毕,会导致页面重绘
let button = document.querySelector('button');
/**
* 这样将导致页面重绘
*/
button.onclick = function () {
document.write('123');
}
// 2. innerHtml 创建元素 拼接字符串需要重新开辟内存 比较耗时
let inner = document.querySelector('.inner');
// 使用拼接字符串的方式
for (let i = 0; i < 100; i++) {
inner.innerHTML += ' <a href="javascript:void(0);">hello</a> ';
if (i % 25 === 0) {
inner.innerHTML += '</br>'
}
}
// 3. document.createElement() 在innerHtml使用字符串拼接的时候 该方式创建的效率高于 innerHtml的字符串拼接的方式
let create = document.querySelector('.create');
for (let i = 0; i < 100; i++) {
let div = document.createElement('div');
div.style.width = '20px';
div.style.height = '20px';
div.style.backgroundColor = 'pink';
div.style.marginTop = '2px';
create.appendChild(div);
}
</script>
</body>
1.6 innerTHML和createElement效率对比
innerHtml使用拼接字符串创建元素效率测试
<div class="inner"></div>
<script>
let inner = document.querySelector('.inner');
let startTime = +new Date();
/**
* 使用拼接字符串的方式创建元素
*/
for (let i = 0; i < 1000; i++) {
inner.innerHTML += '<div style="width: 10px; height: 10px;border: 1px solid red;"></div>';
}
let endTime = +new Date();
console.log((endTime - startTime)); // 700 ~ 800ms
</script>
</body>
测试使用createElement()追加元素的方式创建元素
.innerDiv {
width: 10px;
height: 10px;
border: 1px solid red;
}
<body>
<div class="create"></div>
<script>
let create = document.querySelector('.create');
let startTime = +new Date();
for (let i = 0; i < 10000; i++) {
let div = document.createElement('div');
div.setAttribute('class', 'innerDiv');
create.appendChild(div);
}
let endTime = +new Date();
console.log((endTime - startTime)); // 10 ~ 30ms
</script>
</body>
innerHtml使用数组push的方式创建元素
.innerDiv {
width: 10px;
height: 10px;
border: 1px solid red;
}
<body>
<div class="inner"></div>
<script>
let inner = document.querySelector('.inner');
let startTime = +new Date();
let arr = [];
for (let i = 0; i < 10000; i++) {
arr.push('<div class="innerDiv"></div>');
}
inner.innerHTML = arr.join('');
let endTime = +new Date();
console.log((endTime - startTime)); // 9 ~ 30ms
</script>
</body>
2. 事件高级
2.1 注册事件(2种方式)
给元素添加事件,称为注册事件或者绑定事件;
利用on 开头的事件onclick;
// 1. 传统方式注册事件 btns[0].onclick = function () { alert('点我点我!'); };
特点:注册事件的唯一性。
同一个元素同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数。
注册事件有两种方式:传统方式和监听注册方式。
w3c
标准,推荐方式。addEventListener
它是一个注册事件的方法。IE9
之前的IE
不支持此方法,可以使用attachEvent
代替。特点:同一个元素同一个事件可以注册多个监听器。
按照注册顺序依次执行。
2.2 事件监听
addEventListener()事件监听(IE9以后支持)
eventTarget.addEventListener
eventTarget.addEventListener()
方法将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数。该方法接收三个参数 :
type
:事件类型字符串,比如click
、mouseover
,注意这里不要带on
。
listener
:事件处理函数,事件发生时,会调用监听函数
useCaptrue
:可选参数是一个布尔值,默认是false
。确定事件流所在的阶段,默认是事件冒泡。true
代表事件捕获。
attacheEvent()事件监听(IE678支持)
eventTartget.attacheEvent();
事件监听兼容性解决方案
- 封装一个函数,函数中判断浏览器的类型:
2.3 删除事件(解绑事件)
传统注册方式解绑
eventTarget.onclick = null;
方法监听注册方式解绑
eventTarget.removeEventListener();
- 事件解绑代码案例:
<body>
<div>1</div>
<div>2</div>
</body>
let divs = document.querySelectorAll('div');
// 解绑事件
divs[0].onclick = function () {
alert('只能点我一次');
divs[0].onclick = null;
};
divs[1].addEventListener('click', fn);
function fn() {
alert('我也只能点一次');
// 解绑事件
divs[1].removeEventListener('click', fn);
}
2.4 DOM事件流
html
中的标签都是相互嵌套的,我们可以将元素想象成一个盒子装一个盒子,document
是最外面的大盒子。当你单击一个div
时,同时你也单击了div
的父元素,甚至整个页面。- 那么是先执行父元素的单击事件,还是先执行div的单击事件 ???
事件流
描述的是从页面中接收事件的顺序。
事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流。
- 比如:我们给页面中的一个div注册了单击事件,当你单击了div时,也就单击了body,单击了html,单击了document。
事件冒泡:IE最早提出,事件开始时由最具体的元素接收,然后逐级向上传播到DOM 最顶层节点的过程。
事件捕获:网景公司最早提出,由DOM 最顶层节点开始,然后 逐级向下传播到最具体的元素接收过程。
当时的2大浏览器霸主谁也不服谁!IE 提出从目标元素开始,然后一层一层向外接收事件并响应,也就是冒泡型事件流。
Netscape(网景公司)提出从最外层开始,然后一层一层向内接收事件并响应,也就是捕获型事件流。
江湖纷争,武林盟主也脑壳疼!!!
最终,w3c 采用折中的方式,平息了战火,制定了统一的标准 —--— 先捕获再冒泡。
现代浏览器都遵循了此标准,所以当事件发生时,会经历3个阶段。
DOM 事件流会经历3个阶段:
捕获阶段;
当前目标阶段;
冒泡阶段。
举例: 我们向水里面扔一块石头,首先它会有一个下降的过程,这个过程就可以理解为从最顶层向事件发生的最具体元素(目标点)的捕获过程;之后会产生泡泡,会在最低点( 最具体元素)之后漂浮到水面上,这个过程相当于事件冒泡。
事件发生时会在元素节点之间按照特定的传播顺序传播,这个传播过程就是DOM事件流。
注意
JS代码中只能执行捕获或者冒泡其中的一个阶段。
onclick
和addEventListener
只能 得到冒泡阶段。addEventListener第三个参数是
true
表示 事件捕获阶段调用事件处理函数;如果是false(不写默认是false),表示在事件冒泡阶段调用事件处理程序。实际开发中我们很少使用事件捕获,我们更加关注的是事件冒泡。
有些事件 是没有事件冒泡的,比如onblur,onfocus、onmouseover、onmouseleave。
事件冒泡有时候会带来麻烦,有时候又会帮助很巧妙的做某些事情。
- 代码案例 :
.father {
position: relative;
width: 300px;
height: 300px;
background-color: purple;
margin: 100px auto;
}
.son {
position: absolute;
width: 200px;
height: 200px;
line-height: 200px;
text-align: center;
color: #fff;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: pink;
}
<div class="father">
<div class="son">
son盒子
</div>
</div>
// dom事件流的三个阶段
// 1. JS代码中只能执行捕获或者冒泡其中的一个阶段
// 2. onclick 和 attachEvent(ie)只能的到冒泡阶段
// 3. 捕获阶段如果addEventListener第三个参数是true那么则处于捕获阶段
// 事件捕获顺序: document -> html -> body -> father -> son
let father = document.querySelector('.father');
let son = document.querySelector('.father');
father.addEventListener('click', function () {
alert('捕获阶段 : 您点了我!我是father');
}, true);
son.addEventListener('click', function () {
alert('捕获阶段 : 我是son');
});
// 4. 冒泡阶段如果 addEventListener 第三个参数是 false 那么则处于冒泡阶段
// 事件冒泡顺序: son -> father -> body -> html -> document
father.addEventListener('click', function () {
alert('冒泡阶段 : 我是father');
}, false);
son.addEventListener('click', function () {
alert('冒泡阶段 : 我是son');
}, false);
document.body.addEventListener('click', function () {
alert('冒泡阶段 : 我是body');
}, false);
// 有的事件是没有冒泡的: onblur / onfocus / onmouseenter / onmouseleave
2.5 事件对象
什么是事件对象
- 事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象。
比如:
- 谁绑定了这个事件。
- 鼠标触发事件的话,会得到鼠标的相关信息,如鼠标位置。
- 键盘触发事件的话,会得到键盘的相关信息,如按了哪个键。
事件对象的使用
- 事件触发发生时就会产生事件对象,并且系统会以实参的形式传给事件处理函数。所以,在事件处理函数中声明1个形参用来接收事件对象。
<div>123</div>
// 1. event 就是一个事件对象写在我们监听函数的小括号里面当形参来看
// 2. 事件对象只有有了事件才会存在,它是系统给我们创建的,不需要我们传递参数
let div = document.querySelector('div');
div.onclick = function (ev) {
// 兼容性处理
ev = ev || window.event;
console.log(ev);
};
/**
* 默认是事件冒泡阶段
*/
div.addEventListener('click', function (e) {
console.log(e);
});
事件对象的兼容性处理
事件对象本身的获取存在兼容问题:
标准浏览器中是浏览器给方法传递的参数,只需要定义形参 e 就可以获取到。
在 IE6~8 中,浏览器不会给方法传递参数,如果需要的话,需要到 window.event 中获取查找。
解决:
e = e || windows.event;
- 只要“||”前面为false, 不管“||”后面是true 还是 false,都返回 “||” 后面的值。
- 只要“||”前面为true, 不管“||”后面是true 还是 false,都返回 “||” 前面的值。
事件对象的属性和方法
e.target 和 this 的区别
this
是事件绑定的元素(绑定这个事件处理函数的元素);e.target
是事件触发的元素。
常情况下
terget
和this
是一致的,但有一种情况不同,那就是在事件冒泡时(父子元素有相同事件,单击子元素,父元素的事件处理函数也会被触发执行),这时候this指向的是父元素,因为它是绑定事件的元素对象,而target指向的是子元素,因为他是触发事件的那个具体元素对象。
<div>123</div>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
let div = document.querySelector('div');
div.addEventListener('click', function (ev) {
console.log(ev.target);
console.log(this);
// 上面两者的区别
// 1. ev.target 返回的是触发事件的对象 this 返回的是绑定事件的对象
// 有一个和this非常相似的属性
console.log(ev.currentTarget);
});
// 测试 ev.target 和 this的区别
let ul = document.querySelector('ul');
ul.addEventListener('click', function (ev) {
// e.target 执行我们点击的那个对象谁触发这个事件
console.log(ev.target);
console.log(this);
console.log(ev.currentTarget);
});
2.6 阻止默认行为
html
中一些标签有默认行为,例如a
标签被单击后,默认会进行页面跳转。
<a href="https://www.baidu.com">百度</a>
<form action="https://www.baidu.com">
<input type="submit" value="提交">
</form>
// 返回事件类型
let div = document.querySelector('div');
div.addEventListener('click', fn);
function fn(e) {
console.log(e.type);
}
// 2.阻止默认行为事件,让链接不跳转或者让提交按钮不提交
let a = document.querySelector('a');
a.addEventListener('click', function (ev) {
// 阻止默认行为事件
ev.preventDefault();
});
// 3. 阻止默认行为事件兼容性写法
a.onclick = function (ev) {
// 常用
ev.preventDefault();
// 低版本浏览器
ev.returnValue;
// 或者使用 return false
return false; // 没有兼容性问题 但是 return 后面的代码无法执行 而且只限于传统的注册方式。
}
2.7 阻止事件冒泡
事件冒泡本身的特性,会带来的坏处,也会带来的好处。
- 阻止事件冒泡的标准写法:
ev.stopPropagation(); // 停止传播 阻止事件冒泡
- 非标准写法:IE6~8利用事件对象
cancelBubble
属性:
ev.cancelBubble = true;
<div class="father">
<div class="son">son</div>
</div>
let father = document.querySelector('.father');
let son = document.querySelector('.son');
father.addEventListener('click', function (ev) {
alert('我是father');
});
son.addEventListener('click', function (ev) {
alert('我是son');
ev.stopPropagation(); // 停止传播 阻止事件冒泡
ev.cancelBubble = true;
});
// addEventListener 默认是 事件冒泡阶段 son -> father
// 阻止事件冒泡兼容性解决方案
if (e && e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
- 阻止事件冒泡的兼容性写法:
// 阻止事件冒泡兼容性解决方案
if (e && e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
2.8 事件委托
- 事件冒泡本身的特性,会带来的坏处,也会带来的好处。
什么是事件委托
把事情委托给别人,代为处理。
- 事件委托也称为事件代理,在
jQuery
里面称为事件委派。
说白了就是,不给子元素注册事件,给父元素注册事件,把处理代码在父元素的事件中执行。
js事件中的代理:
<ul>
<li>点我点我</li>
<li>点我点我</li>
<li>点我点我</li>
<li>点我点我</li>
<li>点我点我</li>
</ul>
// 事件委托的原理就是利用事件冒泡 ,将子节点的父节点设置事件监听器,由于存在事件冒泡,子盒子的触发事件会冒到父盒子。
let ul = document.querySelector('ul');
ul.addEventListener('click', function (ev) {
for (let i = 0; i < ul.children.length; i++) {
this.children[i].style.backgroundColor = '';
}
ev.target.style.backgroundColor = 'pink';
alert('哈哈哈哈');
});
事件委托的原理
给父元素注册事件,利用事件冒泡,当子元素的事件触发,会冒泡到父元素,然后去控制相应的子元素
事件委托的作用
- 我们只操作了一次 DOM ,提高了程序的性能。
- 动态新创建的子元素,也拥有事件。
3. 常用鼠标事件
3.1 案例:禁止选中文字和禁止右键菜单
- 禁止鼠标右键菜单:
/**
* 禁止右键菜单
*/
document.addEventListener('contextmenu', function (ev) {
// 禁用右键菜单
ev.preventDefault();
});
- 禁止鼠标选中事件:
/**
* 禁止选择
*/
document.addEventListener('selectstart', function (ev) {
ev.preventDefault();
});
3.2 鼠标事件对象
event
事件对象是事件相关的一系列的信息集合。现阶段我们主要用鼠标事件对象
MouseEvent
和键盘事件对象KeyboardEvent
。
body {
height: 3000px;
}
document.addEventListener('click', function (ev) {
console.log(ev);
console.log(ev.type);
console.log(ev.clientX); // 这是鼠标相对于浏览器左缘的距离
console.log(ev.clientY); // 这是鼠标相对于浏览器上缘的距离
console.log('--------------------');
console.log(ev.pageX); // 相对于文档页面的 x坐标 ie9 以上才支持
console.log(ev.pageY); // 相对于文档页面的 y坐标 ie9 以上才支持
console.log(ev.screenX); // 距离的是自己电脑屏幕左缘的距离
console.log(ev.screenY); // 距离的是自己电脑屏幕上缘的距离
});
3.3 案例:跟随鼠标移动的天使
img {
position: absolute;
}
img src="../images/angel.gif" alt="">
let img = document.querySelector('img');
document.addEventListener('mousemove', function (ev) {
console.log(ev.pageY);
console.log(ev.pageX);
img.style.top = ev.pageY - 50 + 'px';
img.style.left = ev.pageX - 50 + 'px';
});