
批改狀態(tài):合格
老師批語:1. 文檔片斷不會被添加到頁面,只有它的引用才會被添加 2. 具體到本例,如果<ul>是頁面中已經(jīng)存在的元素,需要給它創(chuàng)建1000個子元素,你覺得應(yīng)該怎么做呢?是否是創(chuàng)建一個添加一個呢? 咱們課堂上的案例是一個特例,沒有體現(xiàn)出文檔片斷的優(yōu)勢
在http://ipnx.cn/blog/detail/24740.html已經(jīng)介紹了DOM元素的獲取和遍歷,不清楚可以去熟悉下,這里介紹DOM元素常用操作,包括創(chuàng)建元素、添加元素、插入元素、替換元素、刪除元素以及大量添加元素時優(yōu)化方案 文檔片斷DocumentFragment 。這里增加了一些拓展測試,值得一看
1、 創(chuàng)建元素createElement 語法:document.createElement(‘tag’), 根是document ,參數(shù)是標(biāo)簽名稱,用單引號或雙引號包裹 ,創(chuàng)建元素對象并不在頁面中 ,而在 內(nèi)存中 ,沒有添加到頁面,需要掛載到頁面才顯示。這里要注意標(biāo)簽名稱一般是HTML規(guī)范的名稱,經(jīng)測試也可以是自定義的。
// 1.創(chuàng)建元素對象,此時在內(nèi)存中,需要掛載才可顯示
const ul = document.createElement('ul');
ul.id = 'ul1';
// 創(chuàng)建元素時,標(biāo)簽名稱也可以是自定義的
const score = document.createElement('score');
score.innerHTML = '大家好';
2、 添加元素appendChild 也稱 掛載 ,語法:父元素對象.appendChild(新元素對象), 根是父元素對象 ,添加元素前提要有一個父元素,否則無法定位位置, 參數(shù)是元素對象,不要引號 元素對象可以是createElement創(chuàng)建的元素對象,也可以是獲取的或遍歷的得到的元素對象。在測試時發(fā)現(xiàn)了它的一個 有趣現(xiàn)象 ,就是測試同一個父元素反復(fù)添加和添加到不同父元素的結(jié)果。
- 父元素對象 可以是在內(nèi)存中的元素對象(createElement或createDocumentFragment),也可以頁面中元素對象。常見的頁面元素對象有document.head,document.body和document.documetElement(Html對象)
- 參數(shù)中元素對象 同父元素對象,但要注意不能是頁面中唯一的對象,如body對象、head對象等。經(jīng)測試document.appendChild時會報唯一對象沖突錯誤。
- 總是在尾部添加 append英文翻譯是追加,就是在最后添加元素的意思。
- 同一個父元素反復(fù)添加 先說測試結(jié)果,反復(fù)添加最終是只算一個 ,估計是元素對象在頁面文檔流中都有唯一標(biāo)號,不可以重復(fù)出現(xiàn)。
- 添加到不同父元素 這個更有趣,它會 刪除以前所在位置 ,出現(xiàn)在最后添加的位置 。這個結(jié)果就非常有用了,經(jīng)典應(yīng)用場景 就是用戶在備用選項中選擇,如選擇了某項愛好后,它就在提供的備用選項中移動到選擇中。
<style>
ul { width: 10em; height: 5em; }
#ul1 { background-color: aquamarine; }
#ul2 { background-color: seagreen; }
</style>
<script>
const ul = document.createElement('ul');
ul.id = 'ul1';
// 2.頁面加載元素,父元素一般是頁面中元素對象
document.body.appendChild(ul);
const li = document.createElement('li');
li.innerHTML = '123';
// appendChild父元素可以是內(nèi)存中元素對象
ul.appendChild(li);
const ul2 = document.createElement('ul');
ul2.id = 'ul2';
document.body.appendChild(ul2);
// 當(dāng)同一個元素添加到不同父元素時,出現(xiàn)有趣移動效果,這個應(yīng)用場景就是用戶在備用選項中選擇
ul.onclick = function (ev) {
ul2.appendChild(li);
};
ul2.onclick = function (ev) {
ul.appendChild(li);
};
</script>
3、 插入元素insertBefore 相比于appendChild只能在最后添加元素對象的限制外,insertBefore可以在指定元素對象前插入元素對象。語法:父元素對象.insertBefore(新元素對象,參考元素對象)。
- 父元素對象和參考元素對象 二者是父子關(guān)系 即二者所在空間是一致的,即同為內(nèi)存中元素對象或頁面中元素對象。
- 新元素對象 同appendChild中一樣
- 不在insertAfter JS默認(rèn)沒提供insertAfter,可以根據(jù)insertBefore寫一個。
const li2 = document.createElement('li');
li2.innerHTML = 'hello';
li2.style.color = 'red';
ul.insertBefore(li2,ul.firstChild);
4、 替換元素replaceChild 語法:父元素對象.replaceChild(新元素對象,參考元素對象)。比較簡單,參考插入,不再演示
5、 刪除元素removeChild 語法: 父元素對象.removeChild(存在元素對象),其實刪除并不是真正的刪除,它在內(nèi)存中仍然存在,可再次掛載。
// 刪除并不是真正的刪除,它在內(nèi)存中仍然存在,可再次掛載
score.onclick = function (ev) {
document.body.removeChild(score);
document.body.appendChild(score);
};
在說優(yōu)化方案之前,我簡單說下網(wǎng)頁的二個重要部分:DOM樹和內(nèi)存,前者就是頁面中已經(jīng)存在的元素,后者應(yīng)該是頁面緩存(我也不清楚對不對,有的文檔稱為內(nèi)存)。DOM樹是已經(jīng)渲染完成后的結(jié)果,每一次更新操作都會導(dǎo)致頁面再渲染,所以大量dom操作則會導(dǎo)致頁面一直忙于渲染,這種 “頁面回流” 用戶體驗就非常不好。目前老師解決方案是文檔片斷,來優(yōu)化或提升dom操作的效率。不過在前面我在偶然發(fā)現(xiàn)在內(nèi)存中也可以組裝,于是就有了兩種優(yōu)化方案。比較如下:
// 大量元素時優(yōu)化方案
ul = document.createElement('ul');
// 第一種優(yōu)化方案:內(nèi)存組裝,一次加載
for (let i = 0; i < 1000; i++) {
let li = document.createElement('li');
li.innerHTML = 'item' + i;
ul.appendChild(li);
}
// 第二種優(yōu)化方案:文檔片斷組裝,一次加載
// const frag = document.createDocumentFragment();
// for (let i = 0; i < 1000; i++) {
// let li = document.createElement('li');
// li.innerHTML = 'item' + i;
// frag.appendChild(li);
// }
// ul.appendChild(frag);
document.body.appendChild(ul);
測試結(jié)果和結(jié)論: 二者加載1000個列表元素 時間相近 ,實質(zhì)都是在 內(nèi)存中組裝 。關(guān)于二者區(qū)別,后來咨詢了老師,老師說文檔片斷是通用容器,可臨時存儲任何類型對象,不過前者在內(nèi)存中好像也是可以存儲任何類型,目前我沒發(fā)現(xiàn)什么區(qū)別。
事件是js操作中經(jīng)常要打交道的,我們重點關(guān)注和用戶交互的事件,就是鼠標(biāo)事件和鍵盤事件。不過之前還是要看下事件常用的兩個屬性target和currentTarget。
事件的兩個重要屬性: 觸發(fā)者target和綁定者currentTarget 以前錯誤想法是target是當(dāng)前元素,而currentTarget是上級對象,經(jīng)測試才明白它們的區(qū)別
- currentTarget 綁定者就是 綁定這個事件的元素對象 ,即是在JS中定義事件時的對象。
- target 觸發(fā)者是 觸發(fā)事件行為的元素對象 ,估計這個不好理解。那就實際測試?yán)斫?,測試結(jié)果是 觸發(fā)者一定是綁定者或綁定者的子孫元素 。如只定義了元素的事件,那么該元素的子孫元素若沒有定義事件時,則自動繼承該事件。
上面對target的測試的結(jié)論,是 事件委托代理的工作原理 ,就是 事件也有繼承性 。本文中實戰(zhàn)案例大量應(yīng)用事件委托代理,其實在日常JS編程中事件委托代理是經(jīng)常使用的技巧,可簡化邏輯和代碼。
<style>
.parent {
width: 20em;
height: 20em;
background-color: red;
}
.self {
width: 15em;
height: 15em;
background-color: green;
}
.child {
width: 10em;
height: 10em;
color: white;
background-color: blue;
}
</style>
<div class="parent">
<div class="self">
<div class="child">大家好,學(xué)習(xí)事件</div>
</div>
</div>
<script>
const parent1 = document.querySelector('.parent');
const self1 = document.querySelector('.self');
const child = document.querySelector('.child');
// 1.target和currentTarget
parent1.onclick = function (ev) {
console.log('類名:%s => 觸發(fā)者:%s , 綁定者:%s', this.className, ev.target, ev.currentTarget);
};
</script>
鼠標(biāo)事件MouseEvent: 感覺常用的就是點擊類型type、各種位置坐標(biāo)x與y,具體可以console.dir打印
鍵盤事件KeyboardEvent 感覺常用的就是鍵盤事件類型type、key和keyCode等,一般鍵盤事件添加到window或input。
window.onkeyup=function(ev){
console.log(ev.key);
console.dir(ev);
}
- 視口高度clientHeight 通俗地講就是 可視區(qū)域高度 ,它總是小于設(shè)備屏幕尺寸。通過 document.documentElement.clientHeight 獲取,要注意不是viewHeight,我案例中開始以為它,它是代表看過的高度。
- 滾動高度scrollTop 就是滾動條上邊距可視區(qū)域頂部的高度。它加上視口高度所包括的內(nèi)容就是用戶可以瀏覽的內(nèi)容。通過 document.documentElement.scrollTop 獲取。
- 元素偏移高度offsetTop 元素在文檔流中,到文檔頂部的高度。通過 元素的offsetTop 屬性獲取。
三者關(guān)系見下圖:
- this 事件函數(shù)中this表示觸發(fā)者,若是事函數(shù)使用箭頭函數(shù)時,此時this不是事件觸發(fā)者。
- innerHTML和innerText 前者功能比后者強大,可以解析html標(biāo)簽元素。
- dataset自定義數(shù)據(jù)屬性 在元素中,用戶可以通過data-為前綴添加自定義的數(shù)據(jù)屬性,尤其是多個元素中同步切換子元素時非常有用,如tab選項卡中tab和選項區(qū)兩個同步。
實現(xiàn)功能:
- 用戶輸入留言后,若不是空格或空,回車后則添加留言區(qū)
- 最新的留言總是在最上面
- 留言條可以刪除
<div class="container">
<label for="content">輸入留言:</label>
<input type="text" id="content" name="content" value="" placeholder="輸入留言后回車確認(rèn)" />
<ul id="lists"></ul>
</div>
<script>
const content = document.querySelector('#content');
const lists = document.querySelector('#lists');
content.onkeyup = function (ev) {
// console.log(ev.key);
// 判斷回車時,添加內(nèi)容到列表中
if (ev.key == 'Enter') {
// trim除去空格,空內(nèi)容不添加
if (content.value.trim().length > 0) {
let li = document.createElement('li');
li.innerHTML = content.value + "<button onclick='del(this)'>刪除</button>";
// 若列表有內(nèi)容則添加到最前,沒有則追加
lists.childElementCount == 0 ? lists.appendChild(li) : lists.insertBefore(li, lists.firstElementChild);
// 添加內(nèi)容后,清空輸入框
content.value = null;
} else {
// 無效添加后,輸入框獲取焦點
content.focus();
}
}
};
function del(el) {
// confirm確認(rèn)返回true,取消返回false
if (confirm('確認(rèn)刪除')) lists.removeChild(el.parentElement);
}
</script>
tab選項卡功能就不說了,這里我是通過dataset自定義屬性同步tab和內(nèi)容區(qū),另一個我是通過切換order來實現(xiàn)內(nèi)容區(qū)切換的
<script>
const divs = document.querySelectorAll('.container div:nth-child(n)');
const ul = document.querySelector('ul');
// console.log(divs);
ul.addEventListener('click', tab, false);
function tab(ev) {
// tab菜單切換:先清除所有,再設(shè)置當(dāng)前選擇的
for (let el of ul.children) {
el.classList.remove('active');
}
ev.target.classList.toggle('active');
// tab區(qū)切換:先清除激活樣式和order,然后設(shè)置和tab相同數(shù)據(jù)屬性的區(qū)
for (let el of divs) {
if (el.dataset.index == ev.target.dataset.index) {
el.classList.add('active');
el.style.order = 0;
} else {
el.classList.remove('active');
el.style.order = 1;
}
}
}
</script>
太簡單了,就是設(shè)置body的background-image
<div class="container">
<img src="static/images/1.jpg" alt="" />
<img src="static/images/2.jpg" alt="" />
<img src="static/images/3.jpg" alt="" />
</div>
<script>
const container = document.querySelector('.container');
container.onclick = ev => (document.body.style.backgroundImage = 'url(' + ev.target.src + ')');
</script>
這里關(guān)鍵是:一個是圖片占位的概念,另一個就是通過真正的圖片路徑已經(jīng)包括在元素data-src自定義屬性中。當(dāng)元素的偏移高度小于視口高度和滾動高度之和就加載圖片。
<script>
const imgs = document.querySelectorAll('.container img');
// 視口高度,即可視區(qū)域高度
const clientHeight = document.documentElement.clientHeight;
window.addEventListener('scroll', lazy, false);
window.addEventListener('load', lazy, false);
function lazy(ev) {
// 滾動高度,可視區(qū)域滾動過的距離
let scrollTop = document.documentElement.scrollTop;
for (let img of imgs) {
// 元素在文檔中偏移高度,也可稱為真實高度
let offsetTop = img.offsetTop;
// 當(dāng)元素偏移高度小于(視口高度+滾動高度)時,元素就出現(xiàn)在可視區(qū)域了
if (offsetTop <= scrollTop + clientHeight) {
setTimeout(() => (img.src = img.dataset.src), 500);
}
}
}
</script>
功能描述: 從提供的愛好中選擇自己的愛好,此時備選區(qū)的愛好就移動到自己愛好區(qū)。
關(guān)鍵技術(shù): 還記得本文前面介紹appendChild有趣的現(xiàn)象了嗎?不知道可以向上看看。
<div class="container">
<div class="box">
<h2>你的愛好:</h2>
<ul id="selected" class="item"></ul>
</div>
<div class="box">
<h2>從下面選擇愛好:</h2>
<ul id="unselected" class="item">
<li>攝影</li>
<li>編程</li>
<li>游戲</li>
<li>旅游</li>
<li>駕駛</li>
<li>文學(xué)</li>
</ul>
</div>
</div>
<script>
// 移動關(guān)鍵是利用appendchild一個特性:不同父元素添加同一個元素時,以前的位置會刪除,最終出現(xiàn)在最后的位置
const ulSelect = document.querySelector('#selected');
const ul = document.querySelector('#unselected');
ul.addEventListener('click', ulAdd, false);
function ulAdd(ev) {
// 判斷是否有子元素,防止一次多選擇
if (ev.target.childElementCount == 0) ulSelect.appendChild(ev.target);
}
ulSelect.addEventListener('click', ulDel, false);
function ulDel(ev) {
if (ev.target.childElementCount == 0) ul.appendChild(ev.target);
}
</script>
今天是原生JS實戰(zhàn)課,非常感謝朱老師這段時間的耐心的講解,同時也很慶幸自己每節(jié)課都認(rèn)真梳理、測試和總結(jié),在實戰(zhàn)環(huán)節(jié)沒什么壓力,視頻看了一遍就自己完成代碼,只參考了老師核心的思想,具體實現(xiàn)有自己的改進。上面只貼了關(guān)鍵代碼,源文件歡迎訪問我的GitHubhttps://github.com/woxiaoyao81/phpcn13或Giteehttps://gitee.com/freegroup81/phpcn13
- JS基本知識的核心要理解透,如條件、循環(huán)控制、函數(shù)和dom操作
- 熟悉json、ajax的原理和流程,最好能動手寫出代碼。
- 熟悉事件的添加、傳遞和委托代理,尤其是本文中測試的target和currentTaget的總結(jié)。
到這里原生JS已經(jīng)學(xué)完,學(xué)習(xí)關(guān)鍵是多寫代碼,多思為什么,對自己疑問要測試,不要輕易相信網(wǎng)上搜的文章,要經(jīng)過驗證才能變成自己的。
微信掃碼
關(guān)注PHP中文網(wǎng)服務(wù)號
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://ipnx.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號