
批改狀態(tài):合格
老師批語:不少地方的分析,非常精辟, js中的一些概念, 很多人有不同的解讀, 這是正常的, 畢竟ECMA只制定了一個標(biāo)準(zhǔn),目前 主流的有好幾個JS引擎,實現(xiàn)手段不同, 并不統(tǒng)一
今天同樣是夯實 JS 基礎(chǔ)的課程,學(xué)習(xí)了實際使用中常遇到的條件控制、循環(huán)控制、函數(shù)、箭頭函數(shù)、DOM 操作、classList 和 dataset 對象。新認(rèn)識主要有以下幾個:
- 三元運(yùn)算符 雖然老師只講了使用三元運(yùn)算符簡化雙分支控制,但是我想到了壓縮 JS 中它是常用的一種技巧,對它進(jìn)行了測試并總結(jié)了如何使用。
- for…of ES6 新增的遍歷操作,大有一統(tǒng)遍歷界的意思,與之相近的有 forEach 只適合數(shù)組、for…in 一般用于 JSON 格式的對象。下文有具體介紹。
- 剩余運(yùn)算符 rest 和展開運(yùn)算符 spread 雖然二者運(yùn)算符都是…(省略號),但在不同位置則表示不同含義。
- querySelector ,通過選擇器查詢 dom 元素,尤其是當(dāng)朱老師用 querySelectorAll()實現(xiàn) jquery 的\$()功能時,我感覺 JS 一下子就活了,原來 jquery 也是在 js 的基礎(chǔ)上封裝了常用操作,方便了開發(fā)者使用。正如上學(xué)時 C++老師用 C 語言實現(xiàn) ping 程序,它不再只是打個九九乘法表的演示工具而已。
JavaScript 中條件控制語句主要關(guān)鍵字同 C++語言一樣,就是 if、if…else、if…else if…else if…else 或 switch…case。
最簡單的兩種分支,就是if 表示的單分支和if…else 表示的雙分支 。對于雙分支的簡化就是三元運(yùn)算符。
// 單分支
let score = 70;
if (score >= 60) {
console.log('合格');
}
// 雙分支
score = 50;
if (score >= 60) {
console.log('合格');
} else {
// 默認(rèn)分支
console.log('補(bǔ)考');
}
這里要單獨(dú)說下三元運(yùn)算符,它也是現(xiàn)在JS 壓縮中一種常用技巧 。但是要注意以下兩點(diǎn):
- 若分支中是一條語句時,直接寫在返回值即可,冒號前的語句分號要去除,否則報錯。
- 若分支中是多條語句時,要使用圓括號()包裹所有語句 ,語句之間使用逗號,隔開 ,切記不要有 let 定義變量 ,變量要在運(yùn)算符外定義。當(dāng)然分號也要去除。
在常規(guī)使用三元運(yùn)算符中,它一般將結(jié)果賦值給一個變量,就是如:
let result= scorce >=60 ? ‘合格’ : ‘補(bǔ)考’;
// 簡化
// 若只有一行執(zhí)行語句時,可直接寫上
score >= 60 ? console.log('合格') : console.log('補(bǔ)考');
// 若是雙分支中是多行語句時,要用圓括號()包裹,每個句子用逗號,隔開,不可有變量定義let,也不可有分號。
// 這種寫法也是壓縮js的中最常用的寫法。
let name = 'woxiaoyao';
score >= 60 ? ((name = 'xiaoyao'), console.log(name + '合格')) : ((name = 'peter zhu'), console.log(name + '補(bǔ)考'));
多分支最基本就是從單分支和雙分支拓展而來的 if…else if…else if…else,也有另一種簡化語法:switch…case。不過要注意的是:switch…case 一般用于 單值判斷 ,若是區(qū)間判斷 則要將條件設(shè)置為 true ,這個是以前我苦惱的,老師一講就明白了。還有 switch…case 使用要注意 break 。
// switch簡化多分支
score = 70;
switch (true) {
// 區(qū)間判斷:一定返回布爾值【重要】
case score >= 60 && score < 80:
console.log('合格');
break;
case score >= 80 && score <= 100:
console.log('優(yōu)秀');
break;
case score > 100 || score < 0:
console.log('非法分?jǐn)?shù)');
break;
default:
// 默認(rèn)分支
console.log('補(bǔ)考');
}
// switch最常用還單值判斷
let status = 'fail';
status = 'abc';
status = 'Success';
switch (status.toLowerCase()) {
case 'success':
console.log('成功');
break;
case 'fail':
console.log('失敗');
break;
default:
console.log('未知錯誤');
}
循環(huán)控制是最常見的操作,尤其是遍歷對象和數(shù)組時。循環(huán)的核心一般是 循環(huán)變量初始值、循環(huán)條件、更新循環(huán)變量 。
最基本的循環(huán),實際使用很少,只要知道就可以了。二者區(qū)別是前者是在循環(huán)開始就判斷條件是否滿足,不滿足則不循環(huán)。而后者則先執(zhí)行一次再判斷,很明顯這種循環(huán)有點(diǎn)雞肋,在實在使用中很少使用。
let arr = [10, 20, 30, 40, 50];
// 循環(huán)變量初始值
let i = 0;
// 循環(huán)條件
while (i < arr.length) {
console.log(arr[i]);
// 循環(huán)更新
i++;
}
i = 0;
do {
console.log(arr[i]);
i++;
} while (i < arr.length);
語法格式:for(循環(huán)變量初始化;循環(huán)結(jié)束條件;循環(huán)變量更新){}。
for 循環(huán)可以使用兩個關(guān)鍵字: break 和 continue ,前者是從 break 位置跳出并結(jié)束循環(huán) ,后者是從 continue 位置跳出提前進(jìn)入下一輪循環(huán)判斷。
// for循環(huán)
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
forEach 和 for…in 是 ES6 之前就支持的,而 for…of 是 ES6 新增的循環(huán)遍歷方法。這三者中 for…in 其實存在不少問題,完全可以使用更好的 for…of 來替代。我的原則是優(yōu)先使用 for…of ,其次是 forEach,就是使用在不支持新語法for…of時使用forEach。
forEach: 對數(shù)組的每一個元素執(zhí)行一次提供的函數(shù)(不能使用 return、break 等中斷循環(huán)),不改變原數(shù)組, 無返回值。老師說遍歷對象,我測試遍歷對象報錯。目前它只適合數(shù)組 。
- 語法: arr.forEach(function(item[,key,arr]){}); 其中 item 是數(shù)組中每個值,key 是索引,arr 是原數(shù)組。除第一個參數(shù)必須外,后兩個參數(shù)可以不要。
// forEach:只能是對數(shù)組遍歷,對象則報錯。
let user={name:'woxiaoyao',age:28};
arr.forEach(function(item,key){console.log('forEach=>',item);});
user.forEach(function(item){console.log('forEach=>',item)});
for…in 設(shè)計之初,是給普通以 字符串的值為 key 的對象(如 JSON 格式的對象)使用的,而非數(shù)組。它的幾個特點(diǎn):
for(let index in objArr){console.log(objArr[index])}
- index 是字符串 String 類型 , 數(shù)組索引、普通對象屬性名都將轉(zhuǎn)換為字符串,此時訪問值就是objArr[index],對于數(shù)組和對象都可以。關(guān)于數(shù)組的索引或鍵名(默認(rèn)是索引的字符串)訪問都是合法的,詳細(xì)見http://ipnx.cn/blog/detail/24718.html。使用它進(jìn)行運(yùn)算時一定要切記它是字符串 ,尤其在數(shù)組中進(jìn)行偏移時要注意。
- 作用于數(shù)組的 for-in 循環(huán)體除了遍歷數(shù)組元素外,還會遍歷自定義屬性。舉個例子,如果你的數(shù)組中有一個可枚舉屬性 myArray.name,循環(huán)將額外執(zhí)行一次,遍歷到名為“name”的索引。就連數(shù)組原型鏈上的屬性都能被訪問到。(沒明白,以后再探討)
- 最讓人震驚的是,在某些情況下,這段代碼可能按照隨機(jī)順序遍歷數(shù)組元素。(都這么說,我沒遇到過)
簡而言之,for-in 是為字符串的值為 key的對象設(shè)計的,你可以遍歷得到字符串類型的鍵,因此不適用于數(shù)組遍歷。
// for...in
for (let item in user) {
console.log('for...in=>', user[item]);
}
for…of ES6 后推薦的遍歷對象和數(shù)組的方式,用來循環(huán)獲取一對鍵值對中的值。課中老師測試了 for…of 默認(rèn)是不支持 Object,后面會探討解決方案。它具有幾下優(yōu)點(diǎn):
- 最簡潔、最直接的遍歷數(shù)組元素的語法,對數(shù)組默認(rèn)遍歷值。對鍵名或鍵-值遍歷要使用 ES6 為數(shù)組新增的 keys()和 entries()方法。
- 避開了 for…in 循環(huán)的所有缺陷
- 不同于 forEach(),可以使用 break,continue 和 return。 (我還以為 break 和 continue 只是 for 的專利呢,它有點(diǎn)集大成者的趨勢)
- 支持?jǐn)?shù)組、類數(shù)組(如 HTMLCollection 和 NodeList,后面遍歷 dom 元素時要用到)、字符串、Map 和 Set 等具有 Symbol.iterator 屬性的數(shù)據(jù)對象。
- 它默認(rèn)不支持對象的訪問,可使用 ES6 對 Object 新增的方法 Object.keys()、Object.values()或 Object.entries()將對象的鍵、值或鍵-值轉(zhuǎn)變成可遍歷的。如:
for (let key of Object.keys(obj)){}。
for (let value of Object.values(obj)){}。
for (let [key,value] of Object.entries(obj)){}。要注意是解構(gòu)賦值寫法和順序。- 對數(shù)組也提供了和對象同樣的方法,不同的是數(shù)組不是參數(shù)而是它們是數(shù)組的方法 。如下所示:
for (let key of arr.keys()){}
for (let value of arr.values()){}
for (let [key,value] of arr.entries()){}目前 Jquery 遍歷方法是 each、foreach 和 for…in,正因為 for…of 使 ES6 在遍歷方面目前比 jquery 更加先進(jìn)。即有for 的 break、continue 和 return 控制,又有forEach 和 for…in 所有功能 ,強(qiáng)烈推薦 for…of 的使用 。
// for...of
// for...of默認(rèn)訪問數(shù)組的值
for (let item of arr) {
console.log('for...of=>', item);
}
// for...of訪問數(shù)組的健、值或健-值對則可使用ES6給數(shù)組新增的keys()、values()和entries方法。
for (let key of arr.keys()) {
console.log('for...of=>key=>', key);
}
// for...of默認(rèn)不支持對象,但可用Object.keys(obj)、Object.values(obj)和Object.entries(obj)將對象的對應(yīng)部分轉(zhuǎn)為可遍歷
for (let value of Object.values(user)) {
console.log('for...of=>object=>', value);
}
在阮一峰老師教程中有詳細(xì)介紹,這里主要學(xué)習(xí)了剩余運(yùn)算符 rest 和展開運(yùn)算符 spread,順便介紹下函數(shù)表達(dá)式和箭頭函數(shù)。
- 二者都是以 …(省略號) 開頭,操作對象一般是 數(shù)組 。
- 在函數(shù)定義時圓括號中是剩余運(yùn)算符 rest ,在函數(shù)調(diào)用時圓括號中是展開運(yùn)算符 spread 。在 C++語言中,前者是接受參數(shù),將所有參數(shù)打包到一個數(shù)組 ,后者是傳入?yún)?shù),將數(shù)組解構(gòu)賦值給相同模式的接受參數(shù)。二者主要面對 未知長度 的數(shù)組,已知長度的數(shù)組當(dāng)然也支持。
- 它們是現(xiàn)代函數(shù)優(yōu)秀特性,可以處理未知多個變量。
// 剩余運(yùn)算符rest和展開運(yùn)算符spread
// 剩余運(yùn)算符接受未知個數(shù)參數(shù)
function sum(...args) {
let ret = 0;
for (let val of args) {
ret += val;
}
return ret;
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 展開運(yùn)算符展開未知個數(shù)的數(shù)組,傳遞給函數(shù)處理
console.log('sum=>', sum(...arr));
對于標(biāo)簽函數(shù)利用 ES6 的 rest 和 spread 可以改進(jìn)
// 對標(biāo)簽函數(shù)的改進(jìn)
function myTag(strings, ...args) {
let thtext = '',
tdtext = '';
for (let val of strings.values()) {
if (val) thtext += `<th>${val}</th>`;
}
for (let val of args) {
tdtext += `<td>${val}</td>`;
}
let ret = `<table><tr>${thtext}</tr><tr>${tdtext}</tr></table>`;
return ret;
}
let name = 'woxiaoyao';
let age = '28';
console.log(myTag`my name${name}my age${age}`);
如let demo=function(){}形式的,后面就是函數(shù)表達(dá)式 ,也稱匿名函數(shù) ,沒有名字,通過把它賦值給一個變量,來引用它 ,常用于回調(diào)方法 。在ES6中簡化就是箭頭函數(shù),刪除關(guān)鍵字function, 在參數(shù)列表與大括號之間添加一個”=>”(胖箭頭) ,它就是箭頭函數(shù)名字的由來。
不需要使用this情況下,推薦使用箭頭函數(shù)。它有幾點(diǎn)注意:
- 如果沒有參數(shù),也必須加上一對”()”
- 如果函數(shù)體只有一行代碼,可以省略掉{},有多行代碼,函數(shù)體的大括號不能省略。
- 箭頭函數(shù)中沒有自己的this ,這點(diǎn)尤其要注意。
// 箭頭函數(shù)
let sumArrow = (...args) => {
let ret = 0;
for (let val of args) {
ret += val;
}
return ret;
};
console.log('箭頭函數(shù)改進(jìn)=>',sumArrow(1, 2, 3, 4, 5, 6, 7, 8, 9));
</script>
DOM即document object model,文檔對象模型。dom元素都是對象 ,這個概念一定要牢記,正因為是對象,可以有屬性和方法。
- 根據(jù)標(biāo)簽Tag: document.getElementsByTagName(),如document.getElementsByTagName(“l(fā)i”);
- 根據(jù)ID: document.getElementById(),如document.getElementById(“l(fā)ist”);
- 根據(jù)類class: document.getElementsByClassName(),如document.getElementsByClassName(“item active”);
- 根據(jù)name: document.getElementsByName(),如document.getElementsByName(“first”);
- 根據(jù)選擇器: document.querySelector和document.querySelectorAll(),這是推薦方式,簡單靈活。juqery的$()也是這個思路。
對于常見的HTML元素,js都直接提供了獲取方法,如html為document.documentElement,head為document.head,body為document.body。
<div class="container" id="container">
<p class="item active">item1</p>
<p class="item" name="item">item2</p>
<p class="item" name="item">item3</p>
<p class="item" name="item">item4</p>
</div>
<script>
// 一、獲取dom元素
const tag = document.getElementsByTagName('p');
console.log('tag=>', tag);
console.log('tag=>', tag[1].innerHTML);
console.log('tag=>', tag.item(1).innerHTML);
const classdom = document.getElementsByClassName('item active');
console.log('class=>', classdom);
console.log('class=>', classdom[0].innerHTML);
const name = document.getElementsByName('item');
console.log('name=>', name);
// id獲取的不是類數(shù)組,不要用索引或item訪問,它相當(dāng)于元素對象HTMLDivElement(可看下面打印的信息)
const id = document.getElementById('container');
console.log('id=>', Object.prototype.toString.call(id));
const selector = document.querySelector(".item");
console.log('selector=>', Object.prototype.toString.call(selector));
const selectors = document.querySelectorAll(".item");
console.log('selectors=>', selectors);
const html=document.documentElement;
console.log(Object.prototype.toString.call(html));
const head=document.head;
console.log(Object.prototype.toString.call(head));
const body=document.body;
console.log(Object.prototype.toString.call(body));
</script>
獲取到dom元素分析: 只有知道獲取什么,操作才會得心應(yīng)手。
- HTMLCollection類數(shù)組 指Tag、class兩種獲取,可通過索引或item()方法來訪問。如tag[0].innerHTML和tag.item(0).innerHTML。
- NodeList類數(shù)組 指name和querySelectorAll&&兩種獲取,訪問方式同上**。
- 單個元素對象 指id和querySelector兩種獲取,都是HTMLXxxElement形式的對象,其中Xxx是元素的英文,首字母大寫。訪問方式是直接屬性或訪問即可,其實上兩種類數(shù)組通過索引或item()得到就是單個元素對象。id.innerHTML,若使用索引或item()將報錯,因為它不是數(shù)組。
老師經(jīng)典一例: 單獨(dú)列出,是因為它讓我眼前一亮,原生JS并不弱,尤其是ES6以后
// 模擬jQuery的$()來獲取元素
const $ = (selector) => document.querySelectorAll(selector);
console.log($(“.item:last-of-type”));
所謂遍歷元素節(jié)點(diǎn)最常見就是遍歷父節(jié)點(diǎn)、兄弟節(jié)點(diǎn)和子節(jié)點(diǎn),如下圖
parent父節(jié)點(diǎn):parentElement或parentNode
sibling兄弟節(jié)點(diǎn): 上一個兄弟previousElementSibling,下一個兄弟nextElementSibling。獲取所有兄弟原生JS沒有直接方法,可通過父節(jié)點(diǎn)遍歷所有子元素得到。
child子節(jié)點(diǎn):children 它是類數(shù)組,可以通過索引或item()方向。對于特殊位置子元素也有直接方法,如第一個子元素firstElementChild,最后一個子元素lastElementChild。子元素數(shù)量可通過children的length,也可是節(jié)點(diǎn)的直接屬性childElementCount獲取。
// 父節(jié)點(diǎn)
console.log('父節(jié)點(diǎn)parentNode=>', selector.parentNode);
console.log('父節(jié)點(diǎn)parentElement=>', selector.parentElement);
// 兄弟節(jié)點(diǎn)
const second = document.querySelector('#second');
console.log('上一個兄弟=>',second.previousElementSibling.innerHTML);
console.log('上一個兄弟=>',Object.prototype.toString.call(second.previousElementSibling));
console.log('上一個兄弟=>',Object.prototype.toString.call(second.previousSibling));
console.log('下一個兄弟=>',second.nextElementSibling.innerHTML);
// 子節(jié)點(diǎn)
// 子節(jié)點(diǎn)數(shù)量
console.log('子節(jié)點(diǎn)數(shù)量=>',id.children.length,id.childElementCount);
// 所有子節(jié)點(diǎn)
console.log('所有子節(jié)點(diǎn)=>',id.children);
// 第一個或最后一個子節(jié)點(diǎn)
console.log('第一個子節(jié)點(diǎn)=>',id.firstElementChild);
console.log('最后一個子節(jié)點(diǎn)=>',id.lastElementChild);
// 數(shù)組遍歷,推薦for...of
for(let val of id.children){
console.log('for...of遍歷元素=>',val.innerHTML);
// forEach作為了解就可以了
Array.from(id.children).forEach((item)=>item.style.color="red");
遍歷元素總結(jié):
- 元素需要遍歷節(jié)點(diǎn)分為 父節(jié)點(diǎn)、兄弟節(jié)點(diǎn)和子節(jié)點(diǎn) ,都是 Element元素對象 ,屬性中體現(xiàn)了類型,如children、firstElementChild表示子節(jié)點(diǎn),parentElement或parentNode表示父節(jié)點(diǎn),previousElementSibling和nextElementSiblings是兄弟節(jié)點(diǎn)。非常好記憶。
- 以上獲取的節(jié)點(diǎn)中,children是類數(shù)組,其它獲取節(jié)點(diǎn)都是單個元素對象,要注意訪問方式。
- 對數(shù)組的遍歷推薦for…of 。尤其本文在循環(huán)中已經(jīng)對for…of進(jìn)行了詳細(xì)介紹,無論是遍歷數(shù)組還是遍歷對象,都不在話下,是ES6新增的,也是推薦的。至于老師說的forEach了解就可以,還有就是類數(shù)組轉(zhuǎn)數(shù)組Array.from
- 元素對象Element可直接對內(nèi)置屬性進(jìn)行訪問 ,如item.style.color就是訪問元素的樣式中color。
classList其實就是元素Element對象的內(nèi)置屬性class的對象,只不過在js中class是關(guān)鍵字,所以取別名className代表,而對象則是classList。正如CSS中一樣,我們控制元素樣式最多的就是類,所以classList是JS中操作對象樣式變化的重要手段。
classList常見操作:
- 添加add(): 為元素增加類,如item.classList.add(‘red’);
- 移除remove(): 移除元素中類,如item.classList.remove(‘red’);
- 替換replace(): 替換元素指定的類,如item.classList.replace(‘red’,’blue’);
- 自動切換toggle(): 當(dāng)元素有指定類時則移除remove,若沒有則添加add。如item.classList.toggle(‘red’)
//classList對象操作
for(let val of id.children){
val.classList.add('red');
val.classList.remove('red');
val.classList.replace('red','blue');
val.classList.toggle('red');
}
測試中遇到的問題:
- replace替換時,若找不到第一個參數(shù)表示的類時,直接返回false。
- classList僅僅是對元素的類名進(jìn)行操作,最終效果還是由類的優(yōu)先級和源碼中順序決定 。
dataset是用戶自定義數(shù)據(jù)屬性對象,這是HTML5新增的特性,支持用戶給元素添加自定義屬性,就如微信小程序中data-。它的特點(diǎn)是以data-為前綴 ,訪問是通過元素的dataset屬性來訪問,獲取時請省略掉”data-“。
// dataset訪問用戶自定義屬性
const user=document.querySelector('#user');
console.log('dataset=>',user.dataset.id);
// 將使用連接線命名的多個單詞的屬性名,轉(zhuǎn)換"駝峰命名法"來獲取
console.log('dataset=>',user.dataset.userName);
- 三元運(yùn)算符和switch…case可簡化分支控制,尤其是三元運(yùn)算符在占用空間小的優(yōu)良特性,被眾多JS壓縮所采用,要注意多行語句的書寫。
- for…of循環(huán)或遍歷是ES6新增的,它是原生JS優(yōu)秀于jquery一個點(diǎn),無論是數(shù)組和對象,它都可以簡便訪問,尤其是配合新增數(shù)組的keys()、values()和entries()以及對象的keys()、values和entries(),沒有什么不能遍歷的。可以放棄forEach和for…in,原因在文中已經(jīng)說明了
- 剩余運(yùn)算符rest和展開運(yùn)算符spread使得JS越來越走近合格編程語言,前面let和const,現(xiàn)在的rest和spread,我們看到不斷成熟的JavaScript編程語言。
- dom操作對元素獲取我推薦querySelectorAll(),一個就搞定了,對于元素遍歷,按文中我提到方法記父節(jié)點(diǎn)、兄弟節(jié)點(diǎn)和子節(jié)點(diǎn)屬性就可以,對于內(nèi)置屬性(包括classList)和自定義數(shù)據(jù)屬性dataset要掌握。
- 最后想說的話,本課中我影響最深的就是老師用querySelectorAll來模擬Jquery的$()時,我有種感覺原生JS正變得越來越成熟,結(jié)合我上篇博文http://ipnx.cn/blog/detail/24718.html中就感覺更明顯。
微信掃碼
關(guān)注PHP中文網(wǎng)服務(wù)號
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://ipnx.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號