Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
享一些自己常用的js簡(jiǎn)寫技巧,長(zhǎng)期更新,會(huì)著重挑選一些實(shí)用的簡(jiǎn)寫技巧,使自己的代碼更簡(jiǎn)潔優(yōu)雅~
這里只會(huì)收集一些大多數(shù)人不知道的用法,但是確實(shí)能提高自己的編碼技巧,像ES6那些基礎(chǔ)的簡(jiǎn)寫語法或者是三目運(yùn)算符代替if else那些我覺得是基礎(chǔ),沒必要寫在這里浪費(fèi)精力。
注意本篇內(nèi)容涉及到的語法從ES6到ES11不等,具體使用需要考慮項(xiàng)目兼容性問題!
另外推薦一個(gè)只用一行代碼實(shí)現(xiàn)一個(gè)方法的實(shí)用網(wǎng)站1loc.dev,github
邏輯或操作符||,這里要注意的是0和''也會(huì)認(rèn)為是false
如果||前面的值是0 '' false null undefined NaN其中的任意一種,則直接返回||后面的值
function(obj){
var a=obj || {}
}
// 等價(jià)于=>>
function(obj){
var a;
if(
obj===0 ||
obj==="" ||
obj===false ||
obj===null ||
obj===undefined ||
isNaN(obj)
){
a={}
} else {
a=obj;
}
}
空值合并操作符??如果沒有定義左側(cè)返回右側(cè)。如果是,則返回左側(cè)。
這種方法非常實(shí)用,有時(shí)候僅僅只是想判斷一個(gè)字段有沒有值,而不是把空字符串或者0也當(dāng)做false處理
function(obj){
var a=obj ?? {}
}
// 等價(jià)于=>>
function(obj){
var a;
if(
obj===null ||
obj===undefined
){
a={}
} else {
a=obj;
}
}
輸入框非空的判斷(有時(shí)候不想把0當(dāng)成false可以用此方法。比如分?jǐn)?shù)0也是個(gè)值,這種情況就不能認(rèn)為是false)
if(value !==null && value !==undefined && value !==''){}
// 等價(jià)于==>
if((value ?? '') !==''){}
在上面邏輯或操作符||代碼段里有一個(gè)if判斷比較長(zhǎng),這時(shí)候就可以用includes去簡(jiǎn)化代碼
if(
obj===0 ||
obj==="" ||
obj===false ||
obj===null ||
obj===undefined
){
// ...
}
// 使用includes簡(jiǎn)化
if([0, '', false, null, undefined].includes(obj)){
// ...
}
可選鏈操作符?. 如果訪問未定義的屬性,則會(huì)產(chǎn)生錯(cuò)誤。這就是可選鏈的用武之地。 在未定義屬性時(shí)使用可選鏈運(yùn)算符,undefined將返回而不是錯(cuò)誤。這可以防止你的代碼崩潰。
const student={
name: "lwl",
address: {
state: "New York"
},
}
// 一層一層判斷
console.log(student && student.address && student.address.ZIPCode) // 輸出:undefined
// 使用可選鏈操作符
console.log(student?.address?.ZIPCode) // 輸出:undefined
可選鏈運(yùn)算符也可以用于方法調(diào)用。如果方法存在,它將被調(diào)用,否則將返回 undefined。例如:
const obj={
foo() {
console.log('Hello from foo!')
}
}
obj.foo?.() // 輸出:'Hello from foo!'
obj.bar?.() // 輸出:undefined,因?yàn)?bar 方法不存在
同樣,數(shù)組也可以使用。例如:
const arr=[1, 2, 3, 4]
console.log(arr[0]) // 輸出:1
console.log(arr[4]) // 輸出:undefined
// 使用可選鏈運(yùn)算符
console.log(arr?.[0]) // 輸出:1
console.log(arr?.[4]) // 輸出:undefined
console.log(arr?.[0]?.toString()) // 輸出:'1'
// 多維數(shù)組也可以
const arr2=[[[1]]]
console.log(arr2?.[0]?.[0]?.[0]) // 輸出:1
console.log(arr2?.[0]?.[1]?.[0]) // 輸出:undefined
邏輯空賦值??=邏輯空賦值運(yùn)算符(x ??=y)僅在 x 是 nullish (null 或 undefined) 時(shí)對(duì)其賦值。
const a={ duration: 50 };
a.duration ??=10;
console.log(a.duration);
// expected output: 50
a.speed ??=25;
console.log(a.speed);
// expected output: 25
生成0-9,利用了數(shù)組的下標(biāo)值。推薦使用方法二或方法三,比較靈活,改成(v, k)=> k + 1就是1-10
// 方法一
const arr1=[...new Array(10).keys()]
// 方法二
const arr2=Array.from(Array(10), (v, k)=> k)
// 方法三
const arr3=Array.from({length: 10}, (v, k)=> k)
生成1-10,通過map的特性
const arr1=[...Array(10)].map((v, i)=> i + 1)
// 下面這兩個(gè)和上面生成0-9的方式一樣
const arr2=Array.from(Array(10), (v, k)=> k + 1)
const arr3=Array.from({length: 10}, (v, k)=> k + 1)
const arr=new Array(10).fill(0)
注意: 二維數(shù)組不能直接寫成new Array(10).fill([])(也就是fill方法不能傳引用類型的值,[]換成new Array()也不行),因?yàn)閒ill里傳入引用類型值會(huì)導(dǎo)致每一個(gè)數(shù)組都指向同一個(gè)地址,改變一個(gè)數(shù)據(jù)的時(shí)候其他數(shù)據(jù)也會(huì)隨之改變,詳見mdn官方說明
// 錯(cuò)誤寫法
const arr=new Array(10).fill([]) // 注意這是錯(cuò)誤寫法,不要這么寫
// 正確寫法
const arr=new Array(10).fill().map(()=> new Array())
你是否還在用遞歸給一個(gè)多維數(shù)組降維?如果是,那你應(yīng)該知道一下es6的 flat() 方法。
如果不確定需要降維的數(shù)組有多深,可以傳入最大值作為參數(shù)Infinity,默認(rèn)值深度為1
const arr=[1, [2, [3, 4], 5], 6]
const flatArr=arr.flat(Infinity) // 輸出 [1, 2, 3, 4, 5, 6]
你是否在使用map的時(shí)候想要對(duì)數(shù)組降維?大概像這樣:
const arr=[1, 2, 3, 4]
const result=arr.map(v=> [v, v * 2]).flat()
console.log(result); // 輸出 [1, 2, 2, 4, 3, 6, 4, 8]
其實(shí)js也提供了更簡(jiǎn)便的方法,那就是flatMap(),可以改成這樣:
const result=arr.flatMap(v=> [v, v * 2])
js// 1.數(shù)組去重
let arr1=[1, 3, 2, 1, 4, 2, 1]
let result1=[...new Set(arr1)]
console.log(result1) // [1, 3, 2, 4]
// 2.交集
let arr2=[4, 3, 4, 7]
let result2=[...new Set(arr1)].filter(v=> new Set(arr2).has(v))
console.log(result2) // [3, 4]
// 3.并集
let result3=[...new Set([...arr1, ...arr2])]
console.log(result3) // [1, 3, 2, 4, 7]
// 4.差集
let result4=[...new Set(arr1)].filter(v=> !(new Set(arr2).has(v)))
console.log(result4) // [1, 2]
在 JavaScript 中,你可以使用解構(gòu)從數(shù)組中拆分值。這可以應(yīng)用于交換兩個(gè)變量而無需第三個(gè)
比較簡(jiǎn)單,es6語法
let x=1;
let y=2;
// 交換變量
[x, y]=[y, x];
用于Object.values()將對(duì)象的所有值收集到一個(gè)新數(shù)組中
const info={ name: "Matt", country: "Finland", age: 35 };
// LONGER FORM
let data=[];
for (let key in info) {
data.push(info[key]);
}
// SHORTHAND
const data=Object.values(info);
你Math.pow()習(xí)慣把一個(gè)數(shù)字提高到一個(gè)冪嗎?你知道你也可以使用**運(yùn)算符嗎?
雖然可以簡(jiǎn)寫,不過我還是建議寫成Math.pow()方法,代碼更有語義化。
注意:**運(yùn)算符要求操作數(shù)為數(shù)值類型,不過在js里也能正常運(yùn)行。
Math.pow(2, 3); // 輸出: 8
2 ** 3; // 輸出: 8
Math.pow(4, 0.5); // 輸出: 2
4 ** 0.5; // 輸出: 2
Math.pow(3, -2); // 輸出: 0.1111111111111111
3 ** -2; // 輸出: 0.1111111111111111
Math.pow('2', '3'); // 輸出: 8 (參數(shù)被自動(dòng)轉(zhuǎn)換為數(shù)字)
'2' ** '3'; // js中輸出: 8,其他語言可能報(bào)錯(cuò)
向下取整Math.floor()并不是什么新鮮事。但是你知道你也可以使用~~運(yùn)算符嗎?
同上雖然可以簡(jiǎn)寫,不過我還是建議寫成Math.floor()方法,代碼更有語義化。
注意:對(duì)于正數(shù)而言兩者都是直接去掉小數(shù)位,但對(duì)于負(fù)數(shù)來說Math.floor()是向下取整,~~依然是只去掉小數(shù)位,整數(shù)位不變。 請(qǐng)看下面輸出結(jié)果:
Math.floor(3.14); // 輸出: 3
Math.floor(5.7); // 輸出: 5
Math.floor(-2.5); // 輸出: -3
Math.floor(10); // 輸出: 10
~~3.14; // 輸出: 3
~~5.7; // 輸出: 5
~~(-2.5); // 輸出: -2
~~10; // 輸出: 10
逗號(hào)( , )運(yùn)算符對(duì)它的每個(gè)操作數(shù)從左到右求值,并返回最后一個(gè)操作數(shù)的值。這讓你可以創(chuàng)建一個(gè)復(fù)合表達(dá)式,其中多個(gè)表達(dá)式被評(píng)估,復(fù)合表達(dá)式的最終值是其成員表達(dá)式中最右邊的值。這通常用于為 for 循環(huán)提供多個(gè)參數(shù)。
這里只說一下函數(shù)return的時(shí)候用逗號(hào)運(yùn)算符簡(jiǎn)化代碼的技巧,其他用法請(qǐng)直接點(diǎn)擊查看官方文檔。
舉一個(gè)簡(jiǎn)單的例子:
// 簡(jiǎn)化前
const result=arr=> {
arr.push('a')
return arr
}
console.log(result([1,2])) // 輸出:[1, 2, 'a']
這段代碼需要返回修改后的數(shù)組,不能直接return arr.push('a'),因?yàn)閜ush的返回值是修改后數(shù)組的長(zhǎng)度,這時(shí)候可以用逗號(hào)運(yùn)算符簡(jiǎn)化成一行代碼。
// 簡(jiǎn)化后
const result=arr=> (arr.push('a'), arr)
console.log(result([1,2])) // 輸出:[1, 2, 'a']
比如想要拿到接口返回的特定字段的值,可以用解構(gòu)賦值和對(duì)象的簡(jiǎn)寫方法對(duì)map方法簡(jiǎn)寫,詳細(xì)解釋請(qǐng)移步j(luò)s map方法應(yīng)用場(chǎng)景 處理對(duì)象數(shù)組。
比如接口返回?cái)?shù)據(jù),此時(shí)如果只想要數(shù)據(jù)里的id和name,就可以用下面的簡(jiǎn)寫方式。
// 接口返回?cái)?shù)據(jù)
res=[{
id: 1,
name: 'zhangsan',
age: 16,
gender: 0
}, {
id: 2,
name: 'lisi',
age: 20,
gender: 1
}]
// 第一種方法:箭頭函數(shù)、 解構(gòu)賦值
const data=res.map(({id, name})=> ({id, name}))
// 第二種方法:箭頭函數(shù)、返回對(duì)象(相對(duì)更容易理解)
const data=res.map(v=> ({id: v.id, name: v.name}))
我們知道數(shù)組方法sort()默認(rèn)是按照UTF-16碼元值升序排序
我們可以使用charCodeAt()方法獲取UTF-16碼元
對(duì)于中文或者其他語言按照拼音排序(字典順序排序)則需要用到localeCompare()方法
['張三', '李四', '趙五', '王二', '陳六'].sort()
// 輸出:['張三', '李四', '王二', '趙五', '陳六']
['張三', '李四', '趙五', '王二', '陳六'].sort((a, b)=> a.localeCompare(b, 'zh-Hans-CN'))
// 輸出:['陳六', '李四', '王二', '張三', '趙五']
['apple', 'Banana', 'cherry', 'Date', 'apricot', 'Banana'].sort()
// 輸出:['Banana', 'Banana', 'Date', 'apple', 'apricot', 'cherry']
['apple', 'Banana', 'cherry', 'Date', 'apricot', 'Banana'].sort((a, b)=> a.localeCompare(b, 'en'))
// 輸出:['apple', 'apricot', 'Banana', 'Banana', 'cherry', 'Date']
如果您不提供locales參數(shù)(第二個(gè)參數(shù)),localeCompare方法通常會(huì)默認(rèn)使用瀏覽器或操作系統(tǒng)的當(dāng)前語言環(huán)境
// 獲取arr的最后一位值
const arr=[1, 2, 3, 4, 5]
// 一般寫法
const last=arr[arr.length - 1]
// 二般寫法
const last=arr.slice(-1)[0]
// 終極寫法
const last=arr.at(-1)
作者:lwlcode
鏈接:https://juejin.cn/post/7236664417308524581
知道瀏覽器和服務(wù)端是通過 HTTP 協(xié)議進(jìn)行數(shù)據(jù)傳輸?shù)模?HTTP 協(xié)議又是純文本協(xié)議,那么瀏覽器在得到服務(wù)端傳輸過來的 HTML 字符串,是如何解析成真實(shí)的 DOM 元素的呢,也就是我們常說的生成 DOM Tree,最近了解到狀態(tài)機(jī)這樣一個(gè)概念,于是就萌生一個(gè)想法,實(shí)現(xiàn)一個(gè) innerHTML 功能的函數(shù),也算是小小的實(shí)踐一下。
函數(shù)原型
我們實(shí)現(xiàn)一個(gè)如下的函數(shù),參數(shù)是 DOM 元素和 HTML 字符串,將 HTML 字符串轉(zhuǎn)換成真實(shí)的 DOM 元素且 append 在參數(shù)一傳入的 DOM 元素中。
function html(element, htmlString) { // 1. 詞法分析 // 2. 語法分析 // 3. 解釋執(zhí)行 } 復(fù)制代碼
在上面的注釋我已經(jīng)注明,這個(gè)步驟我們分成三個(gè)部分,分別是詞法分析、語法分析和解釋執(zhí)行。
詞法分析
詞法分析是特別重要且核心的一部分,具體任務(wù)就是:把字符流變成 token 流。
詞法分析通常有兩種方案,一種是狀態(tài)機(jī),一種是正則表達(dá)式,它們是等效的,選擇你喜歡的就好。我們這里選擇狀態(tài)機(jī)。
首先我們需要確定 token 種類,我們這里不考慮太復(fù)雜的情況,因?yàn)槲覀冎粚?duì)原理進(jìn)行學(xué)習(xí),不可能像瀏覽器那樣有強(qiáng)大的容錯(cuò)能力。除了不考慮容錯(cuò)之外,對(duì)于自閉合節(jié)點(diǎn)、注釋、CDATA 節(jié)點(diǎn)暫時(shí)均不做考慮。
接下來步入主題,假設(shè)我們有如下節(jié)點(diǎn)信息,我們會(huì)分出哪些 token 來呢。
<p class="a" data="js">測(cè)試元素</p> 復(fù)制代碼
對(duì)于上述節(jié)點(diǎn)信息,我們可以拆分出如下 token
狀態(tài)機(jī)的原理,將整個(gè) HTML 字符串進(jìn)行遍歷,每次讀取一個(gè)字符,都要進(jìn)行一次決策(下一個(gè)字符處于哪個(gè)狀態(tài)),而且這個(gè)決策是和當(dāng)前狀態(tài)有關(guān)的,這樣一來,讀取的過程就會(huì)得到一個(gè)又一個(gè)完整的 token,記錄到我們最終需要的 tokens 中。
萬事開頭難,我們首先要確定起初可能處于哪種狀態(tài),也就是確定一個(gè) start 函數(shù),在這之前,對(duì)詞法分析類進(jìn)行簡(jiǎn)單的封裝,具體如下
function HTMLLexicalParser(htmlString, tokenHandler) { this.token=[]; this.tokens=[]; this.htmlString=htmlString this.tokenHandler=tokenHandler } 復(fù)制代碼
簡(jiǎn)單解釋下上面的每個(gè)屬性
我們可以很容易的知道,字符串要么以普通文本開頭,要么以<開頭,因此 start 代碼如下
HTMLLexicalParser.prototype.start=function(c) { if(c==='<') { this.token.push(c) return this.tagState } else { return this.textState(c) } } 復(fù)制代碼
start處理的比較簡(jiǎn)單,如果是<字符,表示開始標(biāo)簽或結(jié)束標(biāo)簽,因此我們需要下一個(gè)字符信息才能確定到底是哪一類 token,所以返回tagState函數(shù)去進(jìn)行再判斷,否則我們就認(rèn)為是文本節(jié)點(diǎn),返回文本狀態(tài)函數(shù)。
接下來分別展開tagState和textState函數(shù)。tagState根據(jù)下一個(gè)字符,判斷進(jìn)入開始標(biāo)簽狀態(tài)還是結(jié)束標(biāo)簽狀態(tài),如果是/表示是結(jié)束標(biāo)簽,否則是開始標(biāo)簽,textState用來處理每一個(gè)文本節(jié)點(diǎn)字符,遇到<表示得到一個(gè)完整的文本節(jié)點(diǎn) token,代碼如下
HTMLLexicalParser.prototype.tagState=function(c) { this.token.push(c) if(c==='/') { return this.endTagState } else { return this.startTagState } } HTMLLexicalParser.prototype.textState=function(c) { if(c==='<') { this.emitToken('text', this.token.join('')) this.token=[] return this.start(c) } else { this.token.push(c) return this.textState } } 復(fù)制代碼
這里初次見面的函數(shù)是emitToken、startTagState和endTagState。
emitToken用來將產(chǎn)生的完整 token 存儲(chǔ)在 tokens 中,參數(shù)是 token 類型和值。
startTagState用來處理開始標(biāo)簽,這里有三種情形
endTagState用來處理結(jié)束標(biāo)簽,結(jié)束標(biāo)簽不存在屬性,因此只有兩種情形
邏輯上面說的比較清楚了,代碼也比較簡(jiǎn)單,看看就好啦
HTMLLexicalParser.prototype.emitToken=function(type, value) { var res={ type, value } this.tokens.push(res) // 流式處理 this.tokenHandler && this.tokenHandler(res) } HTMLLexicalParser.prototype.startTagState=function(c) { if(c.match(/[a-zA-Z]/)) { this.token.push(c.toLowerCase()) return this.startTagState } if(c===' ') { this.emitToken('startTag', this.token.join('')) this.token=[] return this.attrState } if(c==='>') { this.emitToken('startTag', this.token.join('')) this.token=[] return this.start } } HTMLLexicalParser.prototype.endTagState=function(c) { if(c.match(/[a-zA-Z]/)) { this.token.push(c.toLowerCase()) return this.endTagState } if(c==='>') { this.token.push(c) this.emitToken('endTag', this.token.join('')) this.token=[] return this.start } } 復(fù)制代碼
最后只有屬性標(biāo)簽需要處理了,也就是上面看到的attrState函數(shù),也處理三種情形
代碼如下
HTMLLexicalParser.prototype.attrState=function(c) { if(c.match(/[a-zA-Z'"=]/)) { this.token.push(c) return this.attrState } if(c===' ') { this.emitToken('attr', this.token.join('')) this.token=[] return this.attrState } if(c==='>') { this.emitToken('attr', this.token.join('')) this.token=[] return this.start } } 復(fù)制代碼
最后我們提供一個(gè)parse解析函數(shù),和可能用到的getOutPut函數(shù)來獲取結(jié)果即可,就不啰嗦了,上代碼
HTMLLexicalParser.prototype.parse=function() { var state=this.start; for(var c of this.htmlString.split('')) { state=state.bind(this)(c) } } HTMLLexicalParser.prototype.getOutPut=function() { return this.tokens } 復(fù)制代碼
接下來簡(jiǎn)單測(cè)試一下,對(duì)于<p class="a" data="js">測(cè)試并列元素的</p><p class="a" data="js">測(cè)試并列元素的</p>HTML 字符串,輸出結(jié)果為
看上去結(jié)果很 nice,接下來進(jìn)入語法分析步驟
語法分析
首先們需要考慮到的情況有兩種,一種是有多個(gè)根元素的,一種是只有一個(gè)根元素的。
我們的節(jié)點(diǎn)有兩種類型,文本節(jié)點(diǎn)和正常節(jié)點(diǎn),因此聲明兩個(gè)數(shù)據(jù)結(jié)構(gòu)。
function Element(tagName) { this.tagName=tagName this.attr={} this.childNodes=[] } function Text(value) { this.value=value || '' } 復(fù)制代碼
目標(biāo):將元素建立起父子關(guān)系,因?yàn)檎鎸?shí)的 DOM 結(jié)構(gòu)就是父子關(guān)系,這里我一開始實(shí)踐的時(shí)候,將 childNodes 屬性的處理放在了 startTag token 中,還給 Element 增加了 isEnd 屬性,實(shí)屬愚蠢,不但復(fù)雜化了,而且還很難實(shí)現(xiàn)。仔細(xì)思考 DOM 結(jié)構(gòu),token 也是有順序的,合理利用棧數(shù)據(jù)結(jié)構(gòu),這個(gè)問題就變的簡(jiǎn)單了,將 childNodes 處理放在 endTag 中處理。具體邏輯如下
代碼如下
function HTMLSyntacticalParser() { this.stack=[] this.stacks=[] } HTMLSyntacticalParser.prototype.getOutPut=function() { return this.stacks } // 一開始搞復(fù)雜了,合理利用基本數(shù)據(jù)結(jié)構(gòu)真是一件很酷炫的事 HTMLSyntacticalParser.prototype.receiveInput=function(token) { var stack=this.stack if(token.type==='startTag') { stack.push(new Element(token.value.substring(1))) } else if(token.type==='attr') { var t=token.value.split('='), key=t[0], value=t[1].replace(/'|"/g, '') stack[stack.length - 1].attr[key]=value } else if(token.type==='text') { if(stack.length) { stack[stack.length - 1].childNodes.push(new Text(token.value)) } else { this.stacks.push(new Text(token.value)) } } else if(token.type==='endTag') { var parsedTag=stack.pop() if(stack.length) { stack[stack.length - 1].childNodes.push(parsedTag) } else { this.stacks.push(parsedTag) } } } 復(fù)制代碼
簡(jiǎn)單測(cè)試如下:
沒啥大問題哈
解釋執(zhí)行
對(duì)于上述語法分析的結(jié)果,可以理解成 vdom 結(jié)構(gòu)了,接下來就是映射成真實(shí)的 DOM,這里其實(shí)比較簡(jiǎn)單,用下遞歸即可,直接上代碼吧
function vdomToDom(array) { var res=[] for(let item of array) { res.push(handleDom(item)) } return res } function handleDom(item) { if(item instanceof Element) { var element=document.createElement(item.tagName) for(let key in item.attr) { element.setAttribute(key, item.attr[key]) } if(item.childNodes.length) { for(let i=0; i < item.childNodes.length; i++) { element.appendChild(handleDom(item.childNodes[i])) } } return element } else if(item instanceof Text) { return document.createTextNode(item.value) } } 復(fù)制代碼
實(shí)現(xiàn)函數(shù)
上面三步驟完成后,來到了最后一步,實(shí)現(xiàn)最開始提出的函數(shù)
function html(element, htmlString) { // parseHTML var syntacticalParser=new HTMLSyntacticalParser() var lexicalParser=new HTMLLexicalParser(htmlString, syntacticalParser.receiveInput.bind(syntacticalParser)) lexicalParser.parse() var dom=vdomToDom(syntacticalParser.getOutPut()) var fragment=document.createDocumentFragment() dom.forEach(item=> { fragment.appendChild(item) }) element.appendChild(fragment) } 復(fù)制代碼
三個(gè)不同情況的測(cè)試用例簡(jiǎn)單測(cè)試下
html(document.getElementById('app'), '<p class="a" data="js">測(cè)試并列元素的</p><p class="a" data="js">測(cè)試并列元素的</p>') html(document.getElementById('app'), '測(cè)試<div>你好呀,我測(cè)試一下沒有深層元素的</div>') html(document.getElementById('app'), '<div class="div"><p class="p">測(cè)試一下嵌套很深的<span class="span">p的子元素</span></p><span>p同級(jí)別</span></div>') 復(fù)制代碼
聲明:簡(jiǎn)單測(cè)試下都沒啥問題,本次實(shí)踐的目的是對(duì) DOM 這一塊通過詞法分析和語法分析生成 DOM Tree 有一個(gè)基本的認(rèn)識(shí),所以細(xì)節(jié)問題肯定還是存在很多的。
總結(jié)
其實(shí)在了解了原理之后,這一塊代碼寫下來,并沒有太大的難度,但卻讓我很興奮,有兩個(gè)成果吧
這篇文章中,作者將分享 12 個(gè)非常有用的 JavaScript 技巧,可以幫助你寫出簡(jiǎn)潔且高性能的代碼。
ES6 引入了 Set 對(duì)象和延展(spread)語法…,我們可以用它們來創(chuàng)建一個(gè)只包含唯一值的數(shù)組。
const array=[1, 1, 2, 3, 5, 5, 1] const uniqueArray=[...new Set(array)]; console.log(uniqueArray); // Result: [1, 2, 3, 5]
在 ES6 之前,獲得同樣的數(shù)組需要更多的代碼!
這個(gè)技巧可以支持包含原始類型的數(shù)組:undefined、null、boolean、string 和 number。但如果你的數(shù)組包含了對(duì)象、函數(shù)或其他嵌套數(shù)組,就不能使用這種方法了。
在我們學(xué)習(xí)使用 for 循環(huán)時(shí),一般建議使用這種結(jié)構(gòu):
for (let i=0; i < array.length; i++){ console.log(i); }
在使用這種方式時(shí),for 循環(huán)的每次迭代都會(huì)重復(fù)計(jì)算數(shù)組長(zhǎng)度。
有時(shí)候這個(gè)會(huì)很有用,但在大多數(shù)情況下,如果能夠緩存數(shù)組的長(zhǎng)度會(huì)更好,這樣只需要計(jì)算一次就夠了。我們可以把數(shù)組長(zhǎng)度復(fù)制給一個(gè)叫作 length 的變量,例如:
for (let i=0, length=array.length; i < length; i++){ console.log(i); }
這段代碼和上面的差不多,但從性能方面來看,即使數(shù)組變得很大,也不需要花費(fèi)額外的運(yùn)行時(shí)重復(fù)計(jì)算 array.length。
使用三元運(yùn)算符可以很快地寫出條件語句,例如:
x > 100 ? 'Above 100' : 'Below 100'; x > 100 ? (x > 200 ? 'Above 200' : 'Between 100-200') : 'Below 100';
但有時(shí)候三元運(yùn)算符仍然很復(fù)雜,我們可以使用邏輯運(yùn)算符 && 和||來替代,讓代碼更簡(jiǎn)潔一些。這種技巧通常被稱為“短路求值”。
假設(shè)我們想要返回兩個(gè)或多個(gè)選項(xiàng)中的一個(gè),使用 && 可以返回第一個(gè) false。如果所有操作數(shù)的值都是 true,將返回最后一個(gè)表達(dá)式的值。
let one=1, two=2, three=3; console.log(one && two && three); // Result: 3 console.log(0 && null); // Result: 0
使用||可以返回第一個(gè) true。如果所有操作數(shù)的值都是 false,將返回最后一個(gè)表達(dá)式的值。
let one=1, two=2, three=3; console.log(one || two || three); // Result: 1 console.log(0 || null); // Result: null
示例 1
假設(shè)我們想要返回一個(gè)變量的 length,但又不知道變量的類型。
我們可以使用 if/else 來檢查 foo 是否是一個(gè)可接受的類型,但這樣會(huì)讓代碼變得很長(zhǎng)。這個(gè)時(shí)候可以使用短路求值:
return (foo || []).length;
對(duì)于上述兩種情況,如果變量 foo 具有 length 屬性,這個(gè)屬性的值將被返回,否則將返回 0。
示例 2
你是否曾經(jīng)在訪問嵌套對(duì)象屬性時(shí)遇到過問題?你可能不知道對(duì)象或某個(gè)子屬性是否存在,所以經(jīng)常會(huì)碰到讓你頭疼的錯(cuò)誤。
假設(shè)我們想要訪問 this.state 中的一個(gè)叫作 data 的屬性,但 data 卻是 undefined 的。在某些情況下調(diào)用 this.state.data 會(huì)導(dǎo)致 App 無法運(yùn)行。為了解決這個(gè)問題,我們可以使用條件語句:
if (this.state.data) { return this.state.data; } else { return 'Fetching Data'; }
但這樣似乎有點(diǎn)啰嗦,而||提供了更簡(jiǎn)潔的解決方案:
return (this.state.data || 'Fetching Data');
除了標(biāo)準(zhǔn)的布爾值 true 和 false,在 JavaScript 中,所有的值要么是“真值”要么是“假值”。
在 JavaScript 中,除了 0、“”、null、undefined、NaN 和 false 是假值之外,其他的都是真值。
我們可以使用! 云算法來切換 true 和 false。
const isTrue=!0; const isFalse=!1; const alsoFalse=!!0; console.log(true); // Result: true console.log(typeof true); // Result: "boolean"
要快速將數(shù)字轉(zhuǎn)換成字符串,我們可以使用 + 運(yùn)算符,然后在后面跟上一個(gè)空字符串。
const val=1 + ""; console.log(val); // Result: "1" console.log(typeof val); // Result: "string"
要把字符串轉(zhuǎn)成數(shù)字,也可以使用 + 運(yùn)算符。
let int="15"; int=+int; console.log(int); // Result: 15 console.log(typeof int); Result: "number"
也可以使用這種方式將布爾值轉(zhuǎn)成數(shù)字,例如:
console.log(+true); // Return: 1 console.log(+false); // Return: 0
在某些情況下,+ 運(yùn)算符會(huì)被解析成連接操作,而不是加法操作。對(duì)于這種情況,可以使用兩個(gè)波浪號(hào):~~。
一個(gè)波浪號(hào)表示按位取反操作,例如,~15 等于 -16。
const int=~~"15" console.log(int); // Result: 15 console.log(typeof int); Result: "number"
使用兩個(gè)波浪號(hào)可以再次取反,因?yàn)?-(-n-1)=n+1-1=n,所以~-16 等于 15。
從 ES7 開始,可以使用 ** 進(jìn)行冪運(yùn)算,比使用 Math.power(2,3) 要快得多。
console.log(2 ** 3); // Result: 8
但要注意不要把這個(gè)運(yùn)算符于 ^ 混淆在一起了,^ 通常用來表示指數(shù)運(yùn)算,但在 JavaScript 中,^ 表示位異或運(yùn)算。
在 ES7 之前,可以使用位左移運(yùn)算符 << 來表示以 2 為底的冪運(yùn)算:
// 以下表達(dá)式是等效的: Math.pow(2, n); 2 << (n - 1); 2**n;
例如,2 << 3=16 等同于 2 ** 4=16。
我們可以使用 Math.floor()、Math.ceil() 或 Math.round() 將浮點(diǎn)數(shù)轉(zhuǎn)換成整數(shù),但有另一種更快的方式,即使用位或運(yùn)算符 |。
console.log(23.9 | 0); // Result: 23 console.log(-23.9 | 0); // Result: -23
| 的實(shí)際行為取決于操作數(shù)是正數(shù)還是負(fù)數(shù),所以在使用這個(gè)運(yùn)算符時(shí)要確保你知道操作數(shù)是正是負(fù)。
如果 n 是正數(shù),那么 n|0 向下取整,否則就是向上取整。它會(huì)移除小數(shù)部分,也可以使用~~ 達(dá)到同樣的效果。
移除整數(shù)尾部數(shù)字
| 運(yùn)算符也可以用來移除整數(shù)的尾部數(shù)字,這樣就不需要像下面這樣:
let str="1553"; Number(str.substring(0, str.length - 1));
相反,我們可以這樣:
console.log(1553 / 10 | 0) // Result: 155 console.log(1553 / 100 | 0) // Result: 15 console.log(1553 / 1000 | 0) // Result: 1
在 ES6 中,我們可以使用箭頭進(jìn)行隱式綁定,這樣可以為類的構(gòu)造器省下一些代碼,并跟一些重復(fù)出現(xiàn)的表達(dá)式說再見,比如 this.myMethod=this.myMethod.bind(this)。
import React, { Component } from React; export default class App extends Compononent { constructor(props) { super(props); this.state={}; } myMethod=()=> { // This method is bound implicitly! } render() { return ( <> <div> {this.myMethod()} </div> </> ) } };
如果你想從一個(gè)數(shù)組尾部移除某些元素,可以使用一種比 splice() 更快的方法。
例如,如果你知道初始數(shù)組的大小,可以像下面這樣重新定義它的 length 屬性:
let array=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; array.length=4; console.log(array); // Result: [0, 1, 2, 3]
這顯然是一種更簡(jiǎn)潔的解決方案。不過,我發(fā)現(xiàn) slice() 的運(yùn)行速度更快,所以,如果你更看重速度,可以像下面這樣:
let array=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; array=array.slice(0, 4); console.log(array); // Result: [0, 1, 2, 3]
數(shù)組的 slice() 方法可以接受負(fù)整數(shù),并從數(shù)組的尾部開始獲取元素。
let array=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; console.log(array.slice(-1)); // Result: [9] console.log(array.slice(-2)); // Result: [8, 9] console.log(array.slice(-3)); // Result: [7, 8, 9]
你之前可能使用過 JSON.stringify,但你是否知道它還可以用來給 JSON 添加縮進(jìn)?
stringify() 方法可以接受兩個(gè)額外的參數(shù),一個(gè)是函數(shù)(形參為 replacer),用于過濾要顯示的 JSON,另一個(gè)是空格個(gè)數(shù)(形參為 space)。
space 可以是一個(gè)整數(shù),表示空格的個(gè)數(shù),也可以是一個(gè)字符串(比如’\t’表示制表符),這樣得到的 JSON 更容易閱讀。
console.log(JSON.stringify({ alpha: 'A', beta: 'B' }, null, '\t')); // Result: // '{ // "alpha": A, // "beta": B // }'
英文原文:https://medium.com/@bretcameron/12-javascript-tricks-you-wont-find-in-most-tutorials-a9c9331f169d
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。