JS Web API
在如今各种框架的盛行的时代,让开发人员手动操作DOM的机会越来越少,像Vue、React这样的框架,内部都封装了各种DOM操作,但是DOM操作一直都会是前端工程师的基础,也是必备的知识,只会Vue而不会DOM操作的前端工程师不会长久。
DOM(Document Object Model)是哪种数据结构(DOM的本质)
DOM是树形结构,所以在操作DOM的时候才会有各种方法,比如获取父节点、子节点、相邻的节点
DOM操作常见的API
- 节点操作
- 结构操作
- property操作
- attribute操作
DOM节点操作
document.getElementById()
document.getElementsByClassName()
document.getElementByTagName()
document.getElementByName()
property操作
通过js属性的形式操作样式,修改dom元素对应的js属性,不会提现到html结构中
div.style.width = '20px'
attribute操作
会修改html标签上的属性,会体现在html结构上
div.setAttribute('data-name', 'xxx');
console.log(div.getAttribute('data-name')); // xxx
PS:通过property和attribute的方式,都会引起DOM渲染,但尽量使用property操作,attribute会改变页面的html结构,所以attribute一定会使页面宠幸渲染,而property可能会使页面重新渲染
DOM结构操作
1. 新增节点
const div = document.createElement('div')
2. 插入节点
document.body.appendChild(div)
3. 删除节点
document.body.removeChild(div)
4. 移动节点
// 未移动前的DOM结构
<div id="div1">
<p id="p1">p1</p>
<p>p2</p>
<p>p3</p>
</div>
<div id="div2">
<p>p4</p>
</div>
// 移动操作
<script>
const p1 = document.getElementById('p1');
const div2 = document.getElementById('div2');
div2.appendChild(p1); // 这里的p1是一个现有元素,故能够移动
</script>
// 移动后的DOM结构
<div id="div1">
<p>p2</p>
<p>p3</p>
</div>
<div id="div2">
<p>p4</p>
<p id="p1">p1</p>
</div>
5.获取子元素列表
div.childNodes
childNodes会获取各种节点,如元素节点、注释节点、文本节点。所以我们必须使用nodeName和nodeType属性来判断是不是你想要的元素
// 获取所有的元素节点
nodeList.filter(child => child.nodeType === 1)
6. 获取父元素
div.parentNode
DOM操作是十分昂贵的,所以应该避免频繁的DOM操作,对DOM查询进行缓存,将多次的操作转化为一次性操作。
<div id="div1">
<p>p1</p>
<p>p2</p>
<p>p3</p>
</div>
<script>
// 对div1缓存
const div1 = document.getElementById("div1");
// 缓存div1的子元素
const children = Array.prototype.slice
.call(div1.childNodes)
.filter(child => child.nodeType === 1);
// 缓存长度
const length = children.length;
for (let i = 0; i < length; i++) {
console.log(children[i].innerHTML);
}
// 将多次的操作转化为一次性操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < length; i++) {
const p = document.createElement("p");
p.id = "p" + i;
p.innerHTML = "this is p " + i;
fragment.appendChild(p);
}
// 一次性去操作DOM
div1.appendChild(fragment);
</script>
BOM(Browser Object Model)操作
1. navigator
// 获取浏览器的信息
const ua = navigator.userAgent
2. screen
3. location
location.href // 全网址
location.protocal // 协议
location.host // 域名
location.search // 查询参数
location.hash // 井号后面的内容
location.pathname // 网站的路径,包括井号
4. history
5. window
6. document
事件冒泡
顺着DOM结构向上冒,事件源本身没有事件
<div id="div1">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
</div>
<script>
const div1 = document.getElementById("div1");
div1.addEventListener("click", function(ev) {
ev.preventDefault(); // 阻止默认行为,这里是a标签,a标签默认会跳转,这里阻止a的默认跳转行为
console.log(ev.target); // 点击什么元素,它就是什么元素
});
</script>
事件代理
事件代理是在事件冒泡的机制上去实行的。其优点是代码简洁,减少浏览器的内存占用,但不要滥用。使用场景,比如有一个很长的列表,这时候要判断店家的是哪个item,这个时候就可以使用事件代理
<div id="div1">
<a href="#">a1</a><br />
<a href="#">a2</a><br />
<a href="#">a3</a><br />
</div>
<button>add</button>
<script>
const div1 = document.getElementById("div1");
document.body.addEventListener("click", function(ev) {
ev.preventDefault();
const target = ev.target;
const nodeName = target.nodeName;
console.log(nodeName);
if (nodeName === "A" || nodeName === "p") {
console.log(target.innerHTML);
} else if (nodeName === "BUTTON") {
const fragment = document.createDocumentFragment();
for (let i = 0; i < 5; i++) {
const p = document.createElement("p");
p.id = "p" + i;
p.innerHTML = "this is p " + i;
fragment.appendChild(p);
}
div1.appendChild(fragment);
}
});
</script>
编写一个通用的事件监听函数
<body>
<div id="div1">
<a href="#">a1</a><br />
<a href="#">a2</a><br />
<a href="#">a3</a><br />
</div>
<button>add</button>
</body>
// 事件监听和事件代理
function bindEvent(el, type, fn, selector, prevent) {
el.addEventListener(type, ev => {
if (prevent) {
ev.preventDefault();
}
const target = ev.target;
const nodeName = target.nodeName;
if (selector) {
if (
typeof selector === "string" &&
selector.toUpperCase() === nodeName
) {
// 将fn的this指向target
fn.call(target, ev, target);
}
} else {
fn.call(target, ev, target);
}
});
}
const div1 = document.getElementById("div1");
bindEvent(
document.body,
"click",
function(ev) {
console.log(this);
console.log(this.innerHTML);
},
"a"
);
手写一个简易的ajax
function ajax({url, method, body, query, async = true}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method.toUpperCase(), url, async) ; // async表示是否异步,true为异步
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText && JSON.parse(xhr.responseText));
} else {
reject(xhr)
}
}
}
xhr.send(body)
})
}
xhr.readyState
- 未初始化,还未调用send()方法
- 载入完成,已调用send()方法,正在发送请求
- 交互,正在解析响应内容
- 完成,响应内容解析完成,可在客户端调用
同源策略
ajax请求时,浏览器要求当前网页和server必须同源(安全)
同源:协议、域名、端口,三者必须一致
加载图片、css、js可无视同源策略
跨域
所有的跨域,都必须经过server端的允许和配合
未经server端允许实现跨域,说明浏览器有漏洞,危险信号
JSONP基本源流
script可绕过跨域限制
服务器可以任意动态拼接数据返回
所以,script就可以用来回去跨域的数据,只要服务器愿意返回
window.cb = function(res) {
console.log(res); // 服务器返回的数据
}
<script src="xxx?user?callback=cb"></script>
// 服务器返回数据格式
cb({ name: "xxx" })