JS Web API
在如今各種框架的盛行的時(shí)代杠娱,讓開(kāi)發(fā)人員手動(dòng)操作DOM的機(jī)會(huì)越來(lái)越少廷区,像Vue嬉挡、React這樣的框架胚迫,內(nèi)部都封裝了各種DOM操作喷户,但是DOM操作一直都會(huì)是前端工程師的基礎(chǔ),也是必備的知識(shí)访锻,只會(huì)Vue而不會(huì)DOM操作的前端工程師不會(huì)長(zhǎng)久褪尝。
DOM(Document Object Model)是哪種數(shù)據(jù)結(jié)構(gòu)(DOM的本質(zhì))
DOM是樹(shù)形結(jié)構(gòu)闹获,所以在操作DOM的時(shí)候才會(huì)有各種方法,比如獲取父節(jié)點(diǎn)河哑、子節(jié)點(diǎn)避诽、相鄰的節(jié)點(diǎn)
DOM操作常見(jiàn)的API
- 節(jié)點(diǎn)操作
- 結(jié)構(gòu)操作
- property操作
- attribute操作
DOM節(jié)點(diǎn)操作
document.getElementById()
document.getElementsByClassName()
document.getElementByTagName()
document.getElementByName()
property操作
通過(guò)js屬性的形式操作樣式,修改dom元素對(duì)應(yīng)的js屬性璃谨,不會(huì)提現(xiàn)到html結(jié)構(gòu)中
div.style.width = '20px'
attribute操作
會(huì)修改html標(biāo)簽上的屬性沙庐,會(huì)體現(xiàn)在html結(jié)構(gòu)上
div.setAttribute('data-name', 'xxx');
console.log(div.getAttribute('data-name')); // xxx
PS:通過(guò)property和attribute的方式,都會(huì)引起DOM渲染睬罗,但盡量使用property操作轨功,attribute會(huì)改變頁(yè)面的html結(jié)構(gòu),所以attribute一定會(huì)使頁(yè)面寵幸渲染容达,而property可能會(huì)使頁(yè)面重新渲染
DOM結(jié)構(gòu)操作
1. 新增節(jié)點(diǎn)
const div = document.createElement('div')
2. 插入節(jié)點(diǎn)
document.body.appendChild(div)
3. 刪除節(jié)點(diǎn)
document.body.removeChild(div)
4. 移動(dòng)節(jié)點(diǎn)
// 未移動(dòng)前的DOM結(jié)構(gòu)
<div id="div1">
<p id="p1">p1</p>
<p>p2</p>
<p>p3</p>
</div>
<div id="div2">
<p>p4</p>
</div>
// 移動(dòng)操作
<script>
const p1 = document.getElementById('p1');
const div2 = document.getElementById('div2');
div2.appendChild(p1); // 這里的p1是一個(gè)現(xiàn)有元素古涧,故能夠移動(dòng)
</script>
// 移動(dòng)后的DOM結(jié)構(gòu)
<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會(huì)獲取各種節(jié)點(diǎn),如元素節(jié)點(diǎn)花盐、注釋節(jié)點(diǎn)羡滑、文本節(jié)點(diǎn)。所以我們必須使用nodeName和nodeType屬性來(lái)判斷是不是你想要的元素
// 獲取所有的元素節(jié)點(diǎn)
nodeList.filter(child => child.nodeType === 1)
6. 獲取父元素
div.parentNode
DOM操作是十分昂貴的算芯,所以應(yīng)該避免頻繁的DOM操作柒昏,對(duì)DOM查詢(xún)進(jìn)行緩存,將多次的操作轉(zhuǎn)化為一次性操作熙揍。
<div id="div1">
<p>p1</p>
<p>p2</p>
<p>p3</p>
</div>
<script>
// 對(duì)div1緩存
const div1 = document.getElementById("div1");
// 緩存div1的子元素
const children = Array.prototype.slice
.call(div1.childNodes)
.filter(child => child.nodeType === 1);
// 緩存長(zhǎng)度
const length = children.length;
for (let i = 0; i < length; i++) {
console.log(children[i].innerHTML);
}
// 將多次的操作轉(zhuǎn)化為一次性操作
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 // 全網(wǎng)址
location.protocal // 協(xié)議
location.host // 域名
location.search // 查詢(xún)參數(shù)
location.hash // 井號(hào)后面的內(nèi)容
location.pathname // 網(wǎng)站的路徑职祷,包括井號(hào)
4. history
5. window
6. document
事件冒泡
順著DOM結(jié)構(gòu)向上冒,事件源本身沒(méi)有事件
<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(); // 阻止默認(rèn)行為届囚,這里是a標(biāo)簽有梆,a標(biāo)簽?zāi)J(rèn)會(huì)跳轉(zhuǎn),這里阻止a的默認(rèn)跳轉(zhuǎn)行為
console.log(ev.target); // 點(diǎn)擊什么元素意系,它就是什么元素
});
</script>
事件代理
事件代理是在事件冒泡的機(jī)制上去實(shí)行的泥耀。其優(yōu)點(diǎn)是代碼簡(jiǎn)潔,減少瀏覽器的內(nèi)存占用蛔添,但不要濫用痰催。使用場(chǎng)景,比如有一個(gè)很長(zhǎng)的列表迎瞧,這時(shí)候要判斷店家的是哪個(gè)item夸溶,這個(gè)時(shí)候就可以使用事件代理
<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>
編寫(xiě)一個(gè)通用的事件監(jiān)聽(tīng)函數(shù)
<body>
<div id="div1">
<a href="#">a1</a><br />
<a href="#">a2</a><br />
<a href="#">a3</a><br />
</div>
<button>add</button>
</body>
// 事件監(jiān)聽(tīng)和事件代理
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"
);
手寫(xiě)一個(gè)簡(jiǎn)易的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
- 未初始化凶硅,還未調(diào)用send()方法
- 載入完成蜘醋,已調(diào)用send()方法,正在發(fā)送請(qǐng)求
- 交互咏尝,正在解析響應(yīng)內(nèi)容
- 完成压语,響應(yīng)內(nèi)容解析完成,可在客戶(hù)端調(diào)用
同源策略
ajax請(qǐng)求時(shí)编检,瀏覽器要求當(dāng)前網(wǎng)頁(yè)和server必須同源(安全)
同源:協(xié)議胎食、域名、端口允懂,三者必須一致
加載圖片厕怜、css、js可無(wú)視同源策略
跨域
所有的跨域蕾总,都必須經(jīng)過(guò)server端的允許和配合
未經(jīng)server端允許實(shí)現(xiàn)跨域粥航,說(shuō)明瀏覽器有漏洞,危險(xiǎn)信號(hào)
JSONP基本源流
script可繞過(guò)跨域限制
服務(wù)器可以任意動(dòng)態(tài)拼接數(shù)據(jù)返回
所以生百,script就可以用來(lái)回去跨域的數(shù)據(jù)递雀,只要服務(wù)器愿意返回
window.cb = function(res) {
console.log(res); // 服務(wù)器返回的數(shù)據(jù)
}
<script src="xxx?user?callback=cb"></script>
// 服務(wù)器返回?cái)?shù)據(jù)格式
cb({ name: "xxx" })