使用原生 JavaScript 操作 DOM
参考链接:https://www.sitepoint.com/dom-manipulation-vanilla-javascript-no-jquery/
微软官方放弃了对 IE10- 的支持,所以现在可以放心地使用原生 JavaScript 操作 DOM 了。
本文针对如下几个方面进行介绍:
- 查询修改 DOM。
- 修改类和特性。
- 事件监听
- 动画
一、查询 DOM
1.1 .querySelector()
使用 CSS 选择器获取元素(一个),查询网页中符合条件的元素快照,不是实时的。
const myElement = document.querySelector('#foo > div.bar');
1.2 .matches()
元素是否匹配指定选择器?
myElement.matches('div.bar') === true;
1.3 .querySelectorAll()
.querySelector() 使用 CSS 选择器获取元素(多个),查询网页中符合条件的元素快照,不是实时的。
const myElements = document.querySelectorAll('.bar');
1.4 在 HTMLElement 元素上使用
.querySelector()/.querySelectorAll() 不仅可以在 document 上使用,还可以在 HTMLElement 元素上使用。
const myChildElemet = myElement.querySelector('input[type="submit"]');
// 等同于
// document.querySelector('#foo > div.bar input[type="submit"]');
1.5 .getElementsByTagName()
根据标签来查询元素,是实时的。
// HTML
<div></div>
// JavaScript
const elements1 = document.querySelectorAll('div')
const elements2 = document.getElementsByTagName('div')
const newElement = document.createElement('div')
document.body.appendChild(newElement)
elements1.length // 1
elements2.length // 2
二、操作 NodeList
.querySelectorAll() 查询的结果为 NodeList 类型,无法使用数组方法(比如 .forEach()),如果想要使用数组方法,可以
- 把
NodeList元素转换成数组。 - 借用数组的方法。
2.1 把 NodeList 元素转换成数组
Array.prototype.slice.call(myElements).forEach(doSomethingWithEachElement);
// 或者使用 `Array.from()`(ES6 中提供的方法)
Array.from(myElements).forEach(doSomethingWithEachElement);
2.2 借用数组的方法
Array.prototype.forEach.call(myElements, doSomethingWithEachElement);
// 或者
[].forEach.call(myElements, doSomethingWithEachElement);
2.3 查询亲属
每个 Element 元素还提供了查询亲属结点的只读属性。列举如下:
myElement.children
myElement.firstElementChild
myElement.lastElementChild
myElement.previousElementSibling
myElement.nextElementSibling
因为 Element 元素继承自 Node,所以还拥有下面的属性:
myElement.childNodes
myElement.firstChild
myElement.lastChild
myElement.previousSibling
myElement.nextSibling
myElement.parentNode
myElement.parentElement
可以通过结点的 nodeType 属性值,确定结点类型。
myElement.firstChild.nodeType === 3 // 判断是否为文本结点
三、修改类和特性
3.1 .classList API
myElement.classList.add('foo');
myElement.classList.remove('bar');
myElement.classList.toggle('baz');
// 获取元素属性 `value` 的值
const value = myElement.value;
// 设置元素属性 `value` 的值
myElement.value = 'foo';
3.2 Object.assign()
// 使用 `Object.assign()` 为元素同时设置多个属性
Object.assign(myElement, {
value: 'foo',
id: 'bar'
})
// 删除元素属性
myElement.value = null;
与元素属性赋值不同的是,.getAttibute()、.setAttribute() 和 .removeAttribute() 方法修改的是 HTML 特性,会引起浏览器重绘,代价高,不建议使用。如果要永久地更改 HTML,可以通过使用父元素的 .innerHTML 属性做到。
3.3 添加 CSS 样式
myElement.style.marginLeft = '2em';
通过 .style 属性获得的属性值是没有经过计算的。要获取经过计算的值,使用 window.getComputedStyle()。
window.getComputedStyle(myElement).getPropertyValue('margin-left');
四、 修改 DOM
4.1 .appendChild() 和 .insertBefore()
// 将 element2 追加为 element1 的最后一个孩子
element1.appendChild(element2);
// 在 element1 的孩子 element3 之前插入 element2
element1.insertBefore(element2, element3);
4.2 .cloneNode()
要复制一个元素插入,可以使用 .cloneNode() 方法。
const myElementClone = myElement.cloneNode();
myParentElement.appendChild(myElementClone);
.cloneNode() 还可接收一个布尔值参数,true 表示深度复制(除了元素本身,元素的孩子也会被复制),为 false 或默认为浅复制(只复制元素本身)。
4.3 创建和删除元素
const myNewElement = document.createElement('div');
const myNewTextNode = document.createTextNode('some text');
myParentElement.removeChild(myElement);
myElement.parentNode.removeChild(myElement);
4.4 .innerHTML 和 .textContent
每个元素都有属性 .innerHTML 和 .textContent(类似 .innerText,但更常用)。
// 替换掉 myElement 内部的 HTML
myElement.innerHTML = `
<div>
<h2>新内容</h2>
<p>gulu gulu gulu</p>
</div>
`;
// 删除 myElement 元素的所有子节点
myElement.innerHTML = null;
// 为 myElement 元素追加内部的 HTML
myElement.innerHTML += `
<a href="foo.html">继续阅读...</a>
<hr/>
`;
为元素追加内部的 HTML 并不好,因为元素会丢失之前所做的属性改变和事件监听绑定。
追加元素较好的方式是以创建元素的形式进行追加。
const link = document.createElement('a');
const text = document.createTextNode('continue reading...');
const hr = document.createElement('hr');
link.href = 'foo.html';
link.appendChild(text);
myElement.appendChild(link);
myElement.appendChild(hr);
上面的追加代码会导致浏览器两次重绘(而不像 .innerHTML 只发生一次重绘),这时可以借助 DocumentFragment 追加元素。
const fragment = document.createDocumentFragment();
fragment.appendChild(text);
fragment.appendChild(hr);
myElement.appendChild(fragment);
五、事件监听
5.1 DOM 0 级
myElement.onclick = function onclick (event) {
console.log(event.type + ' got fired')
}
这种方式只能为某一事件添加唯一的一个事件处理函数。若想添加多个,可以使用 DOM 2 级事件监听方法 .addEventListener()。
5.2 DOM 2 级
myElement.addEventListener('click', function (event) {
console.log(event.type + ' 事件遭触发');
})
myElement.addEventListener('click', function (event) {
console.log(event.type + ' 事件再一次遭触发');
})
在事件处理函数内部,event.target 指向触发事件的元素(或使用箭头函数作为事件处理函数,则函数里的 this 也是指向触发事件的元素)。
5.3 阻止默认行为和阻止冒泡
使用 .preventDefault() 可以阻止浏览器的默认行为(比如点击超链接、提交表单时)。
myForm.addEventListener('submit', function (event) {
const name = this.querySelector('#name');
if (name.value === 'Donald Duck') {
alert('You gotta be kidding!');
event.preventDefault();
}
})
另外一个重要的方法是 .stopPropagation(),它阻止事件冒泡至祖先结点。
5.4 .addEventListener() 可选的第三个参数
.addEventListener() 还提供可选的第三个参数,有两种类型:
- 可选得表示配置选项的对象。
- 是否在捕获阶段触发事件的布尔值(默认
false,即在冒泡阶段触发事件)。
// `.addEventListener()` 两类可选的第三个参数
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
5.4.1 配置对象
可选的配置对象有下列 3 个布尔值属性(默认都为 false):
capture:为true时,表示在捕获阶段触发事件(即到达事件目标之前,会立即触发事假)。once:为true时,表示事件只能被触发一次。passive:为true时,会忽略event.preventDefault()代码,不会阻止默认行为的发生(通常会引起控制台发出警告)。
这三个中,最经常使用的是 .capture,这样,就可以使用可选的表示“是否在捕获阶段触发事件的布尔值”替代“可选的配置对象”了。
5.4.1 表示是否在捕获阶段触发事件的布尔值
// 在捕获阶段触发事件
myElement.addEventListener(type, listener, true);
5.5 .removeEventListener()
删除事件监听使用 .removeEventListener()。比如可选的配置对象的 once 属性可以用 .removeEventListener() 模拟实现。
myElement.addEventListener('change', function listener (event) {
console.log(event.type + ' 触发在 ' + this + ' 元素上');
this.removeEventListener('change', listener);
})
六、事件代理
这是一个很有用的模式。现在有一个表单,当表单元素里的输入框发生 change 事件时,我们要对此监听。
myForm.addEventListener('change', function (event) {
const target = event.target;
// 监听表单元素里的输入框的 `change` 事件时,定义处理逻辑
if (target.matches('input')) {
console.log(target.value);
}
})
这样的一个好处是,即使表单中的输入框个数发生了改变,也不会影响监听事件起作用。
七、动画
使用 CSS 原生的动画效果已经很好了(通过 transition 属性和 @keyframes),但如果需要更加复杂的动画效果,可以使用 JavaScript。
JavaScript 实现动画效果,主要有两种方式:
window.setTimeout():在动画完成前,不断调用。在动画完成后,停止调用。缺点是动画可能会不连续。window.requestAnimationFrame()
const start = window.performance.now();
const duration = 4000;
window.requestAnimationFrame(function fadeIn (now) {
const progress = now - start;
myElement.style.opacity = progress / duration;
if (progress < duration) {
window.requestAnimationFrame(fadeIn);
}
})
八、写成辅助方法
第一种方式:
const $ = function $ (selector, context = document) {
const elements = (selector, context = document) => context.querySelectorAll(selector);
const element = elements[0];
return {
element,
elements,
html (newHtml) {
this.elements.forEach(element => {
element.innerHTML = newHtml;
})
return this;
},
css (newCss) {
this.elements.forEach(element => {
Object.assign(element.style, newCss);
})
return this;
},
on (event, handler, options) {
this.elements.forEach(element => {
element.addEventListener(event, handler, options);
})
return this;
}
};
};
第二种方式:
const $ = (selector, context = document) => context.querySelector(selector);
const $$ = (selector, context = document) => context.querySelectorAll(selector);
const html = (nodeList, newHtml) => {
Array.from(nodeList).forEach(element => {
element.innerHTML = newHtml;
})
}
(完)
使用原生 JavaScript 操作 DOM的更多相关文章
- 框架操作DOM和原生js操作DOM比较
问题引出 对于Angular和React操作DOM的速度,和原生js操作DOM的速度进行了一个比较: 一个同学做的demo 代码如下: <!DOCTYPE html> <html n ...
- JavaScript 操作 DOM 常用 API 总结
文本整理了javascript操作DOM的一些常用的api,根据其作用整理成为创建,修改,查询等多种类型的api,主要用于复习基础知识,加深对原生js的认识. 基本概念 在讲解操作DOM的api之前, ...
- 【repost】Javascript操作DOM常用API总结
Javascript操作DOM常用API总结 文本整理了javascript操作DOM的一些常用的api,根据其作用整理成为创建,修改,查询等多种类型的api,主要用于复习基础知识,加深对原生js的认 ...
- Python javascript操作DOM
文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口.它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式.我们最为关心的是,DOM把 ...
- javaScript操作DOM对象(看三遍,敲三遍,写三遍! 不会你找我)!!
DOM是Document Object Model的缩写,即文档对象模型,是基于文档编程的一套API 使用javaScript操作DOM对象通常分为三类:1.DOM CORE 2.HTM ...
- 第四章 JavaScript操作DOM对象
第四章 JavaScript操作DOM对象 一.DOM操作 DOM是Document Object Model的缩写,即文档对象模型,是基于文档编程的一套API接口,1988年,W3C发布了第一级 ...
- 你所不了解的javascript操作DOM的细节知识点(一)
你所不了解的javascript操作DOM的细节知识点(一) 一:Node类型 DOM1级定义了一个Node接口,该接口是由DOM中的所有节点类型实现.每个节点都有一个nodeType属性,用于表明节 ...
- JavaScript 操作DOM对象
1)JavaScript 操作DOM對象 1.DOM:是Document Object Model 的缩写,及文档对象模型 2.DOM通常分为三类:DOM Core(核心).HTML-DOM 和 ...
- 原生js操作Dom节点:CRUD
知识点,依然会遗忘.我在思考到底是什么原因.想到研究生考试准备的那段岁月,想到知识体系的建立,知识体系分为正向知识体系和逆向知识体系:正向知识体系可以理解为教科书目录,逆向知识体系可以理解考试真题. ...
随机推荐
- Android游戏开发实践(1)之NDK与JNI开发04
Android游戏开发实践(1)之NDK与JNI开发04 有了前面几篇NDK与JNI开发相关基础做铺垫,再来通过代码说明下这方面具体的操作以及一些重要的细节.那么,就继续NDK与JNI的学习总结. 作 ...
- 纪中集训 Day 0?
好吧昨天的等到今天才来写,现在超不想刷题,来写下blog吧= = 坐了近10H的火车终于来到了中山市 火车上在看空之境界,等有时间补下动画吧= = 到了宿舍各种不习惯(现在才发现还是母校好QAQ)然后 ...
- 关于fibonacci数列用JS写的一点小优化
直接上代码 var month = prompt("请输入月数:") function fibobo(x) { //先定义一个已有前两项的数组,用来作缓存 var arr = [1 ...
- 详细领悟ThreadLocal变量
关于对ThreadLocal变量的理解,我今天查看一下午的博客,自己也写了demo来测试来看自己的理解到底是不是那么回事.从看到博客引出不解,到仔细查看ThreadLocal源码(JDK1.8),我觉 ...
- python算法(一)
python算法(一) 一.求数x的因子 x=100 divisors=()#初始化空的元组 for i in range(1,x): if x%i==0: divisors=divisors+(i, ...
- Selenium 使用过程遇到问题随笔
最近正在学习Selenium,自学是比较难的,也很感谢网络环境中,各位大大的博文帮助. 也希望在此能够记录一下从小白学习使用selenium测试的过程,也希望能对别人有所帮助. 关于环境部署,以及入门 ...
- .NET入行之工作前
时间就像轻风一样,刻意感受的时候几乎把你吹倒,不留意的时候又从你身边轻轻飘走了:长此以后,我怕自己会变得麻木,忘记了原来的样子.所以还是决定给自己留点什么,万一哪天忘记了,还可以再翻起来. 工作两年的 ...
- 几种功能类似Linux命令汇总
wc 命令用于统计文本的行数.字数.字节数,格式为"wc [参数] 文本". -l 只显示行数 -w 只显示单词数 -c 只显示字节数 例:统计当前系统中的用户个数: [roo ...
- Kubernetes 1.5安装
Kubernetes从1.3开始引入kubeadm来试图简化其复杂的安装.但kubeadm至今仍不稳定,而且我个人觉得kubeadm反而麻烦,还不如直接用脚本或者其他自动化工具来安装来的利索.关于ku ...
- android开发艺术探索读书笔记之-------view的事件分发机制
View的点击事件的分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,而这个过程就是分发过程. 分发过程主要由以下 ...