JS Web API
在如今各種框架的盛行的時代,讓開發(fā)人員手動操作DOM的機會越來越少,像Vue、React這樣的框架,內(nèi)部都封裝了各種DOM操作,但是DOM操作一直都會是前端工程師的基礎,也是必備的知識,只會Vue而不會DOM操作的前端工程師不會長久。
DOM(Document Object Model)是哪種數(shù)據(jù)結構(DOM的本質)
DOM是樹形結構,所以在操作DOM的時候才會有各種方法,比如獲取父節(jié)點、子節(jié)點、相鄰的節(jié)點
DOM操作常見的API
- 節(jié)點操作
- 結構操作
- property操作
- attribute操作
DOM節(jié)點操作
document.getElementById()
document.getElementsByClassName()
document.getElementByTagName()
document.getElementByName()
property操作
通過js屬性的形式操作樣式,修改dom元素對應的js屬性,不會提現(xiàn)到html結構中
div.style.width = '20px'
attribute操作
會修改html標簽上的屬性,會體現(xiàn)在html結構上
div.setAttribute('data-name', 'xxx');
console.log(div.getAttribute('data-name')); // xxx
PS:通過property和attribute的方式,都會引起DOM渲染,但盡量使用property操作,attribute會改變頁面的html結構,所以attribute一定會使頁面寵幸渲染,而property可能會使頁面重新渲染
DOM結構操作
1. 新增節(jié)點
const div = document.createElement('div')
2. 插入節(jié)點
document.body.appendChild(div)
3. 刪除節(jié)點
document.body.removeChild(div)
4. 移動節(jié)點
// 未移動前的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是一個現(xiàn)有元素,故能夠移動
</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會獲取各種節(jié)點,如元素節(jié)點、注釋節(jié)點、文本節(jié)點。所以我們必須使用nodeName和nodeType屬性來判斷是不是你想要的元素
// 獲取所有的元素節(jié)點
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 // 全網(wǎng)址
location.protocal // 協(xié)議
location.host // 域名
location.search // 查詢參數(shù)
location.hash // 井號后面的內(nèi)容
location.pathname // 網(wǎng)站的路徑,包括井號
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>
事件代理
事件代理是在事件冒泡的機制上去實行的。其優(yōu)點是代碼簡潔,減少瀏覽器的內(nèi)存占用,但不要濫用。使用場景,比如有一個很長的列表,這時候要判斷店家的是哪個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>
編寫一個通用的事件監(jiān)聽函數(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)聽和事件代理
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()方法,正在發(fā)送請求
- 交互,正在解析響應內(nèi)容
- 完成,響應內(nèi)容解析完成,可在客戶端調用
同源策略
ajax請求時,瀏覽器要求當前網(wǎng)頁和server必須同源(安全)
同源:協(xié)議、域名、端口,三者必須一致
加載圖片、css、js可無視同源策略
跨域
所有的跨域,都必須經(jīng)過server端的允許和配合
未經(jīng)server端允許實現(xiàn)跨域,說明瀏覽器有漏洞,危險信號
JSONP基本源流
script可繞過跨域限制
服務器可以任意動態(tài)拼接數(shù)據(jù)返回
所以,script就可以用來回去跨域的數(shù)據(jù),只要服務器愿意返回
window.cb = function(res) {
console.log(res); // 服務器返回的數(shù)據(jù)
}
<script src="xxx?user?callback=cb"></script>
// 服務器返回數(shù)據(jù)格式
cb({ name: "xxx" })