新聞中心
在前端開(kāi)發(fā)過(guò)程中,常常遇到各種各樣的問(wèn)題和坑點(diǎn)。尤其是隨著技術(shù)的不斷發(fā)展和更新,新的問(wèn)題也不斷涌現(xiàn)。對(duì)于初學(xué)者而言,這些問(wèn)題往往讓人感到十分困惑和無(wú)助。因此,本文將旨在探討一些前端開(kāi)發(fā)過(guò)程中常見(jiàn)的問(wèn)題和坑點(diǎn)以及解決方法,幫助讀者更加深入地了解前端開(kāi)發(fā),并解決實(shí)際工作中遇到的問(wèn)題。

數(shù)據(jù)類(lèi)型
數(shù)字
1. 進(jìn)制轉(zhuǎn)化問(wèn)題:
/**
* 為什么 010 會(huì)是 8
*/
const num1 = 09 // 9
const num2 = 010 // 8這邊是因?yàn)?0 開(kāi)始的數(shù)字js會(huì)嘗試先把它轉(zhuǎn)成八進(jìn)制的數(shù)字。如果你出現(xiàn)大于 8 的數(shù)字,他知道不是八進(jìn)制還給你轉(zhuǎn)十進(jìn)制。純粹的八進(jìn)制應(yīng)該用 0o ,類(lèi)似的還有 0b 二進(jìn)制和 0x 十六進(jìn)制,但是他們寫(xiě)的不符合轉(zhuǎn)換條件的話會(huì)直接報(bào)錯(cuò)。
2. 精度丟失問(wèn)題:
0.1+0.2 // 0.30000000000000004
2.55.toFixed(1) // '2.5'
2.45.toFixed(1) // '2.5'
2.toFixed(1) // Uncaught SyntaxError: Invalid or unexpected token
2..toFixed(1) // '2.0'js 計(jì)算有精度問(wèn)題呢?大家一定都是知道的, 今天就是來(lái)簡(jiǎn)單解釋一下為什么會(huì)出現(xiàn)丟失精度的問(wèn)題。這邊其實(shí)分兩部分,存儲(chǔ)和展示。存儲(chǔ)的時(shí)候 JavaScript 是以 64 位二進(jìn)制補(bǔ)碼的方式來(lái)存儲(chǔ)。由于改方式是以 2 為底進(jìn)行表示的,所以執(zhí)行某些運(yùn)算時(shí)容易出現(xiàn)誤差、溢出、無(wú)限循環(huán)等問(wèn)題。
圖片
我們可以發(fā)現(xiàn)本來(lái)應(yīng)該是 11001100 無(wú)限循環(huán)被截?cái)嗔?,尾?hào) 11001 的時(shí)候1被舍去了,然后進(jìn)了一位 最后存儲(chǔ)成了上圖的 1101 的樣子。所以 0.1 其實(shí)存的比 0.1 要大一點(diǎn)點(diǎn),0.2 也是一樣,而 0.3 比實(shí)際小一些。所以計(jì)算 0.1+0.2 的時(shí)候其實(shí)是拿二進(jìn)制計(jì)算的,兩個(gè)都偏大的數(shù)字相加 誤差被近一步的放大了。
下面這張圖可以看到他們真實(shí)存下來(lái)的數(shù)據(jù)轉(zhuǎn)成十進(jìn)制的樣子。實(shí)際顯示的時(shí)候會(huì)做近似處理,js 會(huì)判斷一個(gè)數(shù)字特別像 0.1 它就顯示 0.1 了。
圖片
toFixed 問(wèn)題也是一樣。
圖片
還有就是有時(shí)候我們對(duì)一個(gè)數(shù)字使用 .toFixed .toString 會(huì)報(bào)錯(cuò)。
0.toString() // Uncaught SyntaxError: Invalid or unexpected token
// 我們期待的是它會(huì)隱性轉(zhuǎn)換讓我們調(diào)用 Number 構(gòu)造函數(shù)上的方法,
// 但是程序會(huì)以為你在寫(xiě)一個(gè)小數(shù),小數(shù)還不合規(guī),所以報(bào)錯(cuò)了,
// 解決方法就是拿變量裝一下,或者 0..toString()在 JavaScript 中,采用 64 位二進(jìn)制補(bǔ)碼表示數(shù)值類(lèi)型,即雙精度浮點(diǎn)數(shù)。符號(hào)位(S)、指數(shù)位(E)和尾數(shù)位(M)的比特?cái)?shù)分別為 1 位、11 位和 52 位。在使用 IEEE 754 標(biāo)準(zhǔn)表示雙精度浮點(diǎn)數(shù)時(shí),使用一些特殊的位表示:其中一個(gè)隱含位表示數(shù)字 1,在正常項(xiàng)中省略,因此一共有 53 位表示有效數(shù)字。
- 符號(hào)位:在數(shù)值類(lèi)型的二進(jìn)制補(bǔ)碼表示中,第一位表示符號(hào)位,0 表示正號(hào),1 表示負(fù)號(hào)。
- 指數(shù)位:在數(shù)值類(lèi)型的二進(jìn)制補(bǔ)碼表示中,指數(shù)位用來(lái)表示科學(xué)計(jì)數(shù)法的指數(shù)部分。在雙精度浮點(diǎn)數(shù)中,指數(shù)部分使用11個(gè)位表示,其中 10 個(gè)位表示二進(jìn)制整數(shù),在運(yùn)算前需要減去 2^n 的形式,剩下一位表示符號(hào),1表示負(fù)指數(shù),0表示正指數(shù)??杀硎?-1023 ~ 1024 之間的范圍。
- 尾數(shù)位:尾數(shù)位用來(lái)表示實(shí)數(shù)的小數(shù)部分。在雙精度浮點(diǎn)數(shù)中,尾數(shù)部分使用 52 個(gè)位表示。這意味著 JavaScript 浮點(diǎn)數(shù)的精度是有限的,并且可能會(huì)發(fā)生舍入誤差。
長(zhǎng)度問(wèn)題:
function fn(a,b,c){
return a+b+c
}
fn.length // 3 一般來(lái)說(shuō)fn的長(zhǎng)度是形參的個(gè)數(shù) 但是形參有默認(rèn)值就不同
function fn1(a = 1,b,c){
return a+b+c
}
fn1.length // 0
function fn2(a,b=1,c){
return a+b+c
}
fn2.length //1
// 它只會(huì)統(tǒng)計(jì)首個(gè)默認(rèn)之前的參數(shù)對(duì)象排序問(wèn)題:
a.b=1
a.a=1
a.c=1
a[2]=2
a[12]=2
a[1]=2
// 結(jié)果 {1: 2, 2: 2, 12: 2, b: 1, a: 1, c: 1}
// 對(duì)象的內(nèi)部key value的存儲(chǔ)順序是這樣的
// 如果屬性可以轉(zhuǎn)number,提前上來(lái),按升序排列,其他的字符串屬性按添加的先后順序賦值中斷問(wèn)題:js 里沒(méi)有事務(wù)的機(jī)制,不會(huì)恢復(fù)到操作之前的狀態(tài)。如果中途失敗了,之前賦值和操作過(guò)的數(shù)據(jù)是保留的,失敗后的操作不執(zhí)行。
異步
定時(shí)器不準(zhǔn):這里說(shuō)的不準(zhǔn)還不是說(shuō)一點(diǎn)小誤差。定時(shí)器由于渲染主進(jìn)程阻塞也好,延時(shí)任務(wù)嵌套過(guò)深也好,事件循環(huán)優(yōu)先級(jí)被排隊(duì)到后邊也好。這些都可以認(rèn)為是“誤差”,但是如果說(shuō)你 setIntervel 是 10ms,結(jié)果它間隔 n 秒調(diào)一次函數(shù),那可不是誤差了,可能直接會(huì)產(chǎn)生 bug。
這個(gè)問(wèn)題的原因是:用戶(hù)在使用谷歌瀏覽器的過(guò)程中將窗口最小化或切換到其他應(yīng)用程序中去,瀏覽器會(huì)將當(dāng)前標(biāo)簽頁(yè)和其中的 JavaScript 定時(shí)器掛起,這將導(dǎo)致定時(shí)器延遲調(diào)用。通常情況下,瀏覽器會(huì)盡可能保持定時(shí)器的準(zhǔn)確性,并在恢復(fù)標(biāo)簽頁(yè)后立即執(zhí)行延遲的定時(shí)器。但是,如果計(jì)算機(jī)負(fù)載過(guò)重或其他原因?qū)е?JavaScript 的執(zhí)行速度變慢,定時(shí)器可能會(huì)更加延遲。經(jīng)過(guò)測(cè)試,新版本的瀏覽器上基本都是至少 1 秒一次。
圖片
詳細(xì)參考 https://developer.mozilla.org/zh-CN/docs/Web/API/setTimeout
競(jìng)態(tài)問(wèn)題:異步的競(jìng)態(tài)問(wèn)題也是開(kāi)發(fā)中經(jīng)常遇到的問(wèn)題。舉個(gè)例子用戶(hù)輸入搜索就請(qǐng)求相應(yīng)的商品,用戶(hù)很快的輸入了“手機(jī)殼” 3 個(gè)字;“手”“手機(jī)”“手機(jī)殼” 3 個(gè)不同參數(shù)的請(qǐng)求幾乎同時(shí)發(fā)了出去,異步請(qǐng)求很難保證哪個(gè)請(qǐng)求,先回來(lái)哪個(gè)請(qǐng)求后回來(lái)。那加上防抖呢?其實(shí)這也不是防抖該應(yīng)用的場(chǎng)景,弱網(wǎng)環(huán)境下請(qǐng)求 5 秒、10 秒返回都說(shuō)不準(zhǔn),防抖防幾秒都不太合適。我根據(jù)個(gè)人的經(jīng)驗(yàn)總結(jié)了 3 種方式:
1. 鏈?zhǔn)秸{(diào)用
// 讓要執(zhí)行的異步函數(shù)通過(guò)一個(gè)鏈?zhǔn)椒绞秸{(diào)用
export class SequenceQueue {
promise = Promise.resolve();
excute (promise) {
this.promise = this.promise.then(() => promise);
return this.promise;
}
};2. 設(shè)置一個(gè)疊加器,每次調(diào)用就累加,回調(diào)函數(shù)內(nèi)就可以知道當(dāng)前是不是“最新”。antd 里有一段例子如下
const fetchRef = useRef(0);
const debounceFetcher = useMemo(() => {
const loadOptions = (value: string) => {
fetchRef.current += 1;
const fetchId = fetchRef.current;
setOptions([]);
setFetching(true);
fetchOptions(value).then((newOptions) => {
if (fetchId !== fetchRef.current) {
// for fetch callback order
return;
}
setOptions(newOptions);
setFetching(false);
});
};
return debounce(loadOptions, debounceTimeout);
}, [fetchOptions, debounceTimeout]);可以進(jìn)一步封裝,將請(qǐng)求封裝成 request( url, [option], [queueName] ), 通過(guò)外部傳入來(lái)指定需要競(jìng)態(tài)的映射名。也就是將上述的疊加器放在一個(gè) Map 里,使用 queueName 做 Map 的 key。
“
如果作為通用的請(qǐng)求中間件封裝,處于內(nèi)存優(yōu)化考慮,此處可以將 Map 優(yōu)化成 weakMap。Map 鍵值對(duì)是強(qiáng)引用,如有一個(gè)鍵被引用,那么GC是無(wú)法回收對(duì)應(yīng)的值的,weakmap 不存在這樣的問(wèn)題,但要注意 weakMap 只能使用對(duì)象做 key。
3. 新請(qǐng)求發(fā)出的時(shí)候取消老的請(qǐng)求。一般來(lái)說(shuō)請(qǐng)求發(fā)出去了是追不回來(lái)的。但是 fetch 和原生 ajax 提供了 abort 之類(lèi)的取消方法。如果你項(xiàng)目的請(qǐng)求是 fetch 或 XMLHttpRequest 就可以用他們自帶的方式取消。需要注意的是,如果請(qǐng)求已經(jīng)被發(fā)送到服務(wù)器,并且請(qǐng)求體數(shù)據(jù)已被上傳,那么 abort() 方法就無(wú)法中止請(qǐng)求。大多數(shù)情況項(xiàng)目用的可能是 axios、uni.request 等其他更熱門(mén)的請(qǐng)求庫(kù),這時(shí)候我們可以利用 promise.race 來(lái)封裝一個(gè)可以取消的請(qǐng)求,傳一個(gè)自定義能帶取消方法的 promise 進(jìn) promise.race 來(lái)控制 真正要執(zhí)行的 promise 函數(shù)提前取消。
// 封裝
function cancelableRequest(requestPromise) {
const cancelToken = {};
const cancelablePromise = new Promise((resolve, reject) => {
cancelToken.cancel = () => {
reject(new Error('Request was canceled'));
};
Promise.race([requestPromise, cancelToken])
.then(resolve)
.catch(reject);
});
return { promise: cancelablePromise, cancel: cancelToken.cancel };
}
// 使用
const mockApi= () =>
new Promise(resolve => {
setTimeout(() => {
resolve([{ title: 'Post 1' }, { title: 'Post 2' }, { title: 'Post 3' }]);
}, 3000);
});
const { promise, cancel } = cancelableRequest(mockApi());
promise
.then(posts => console.log(posts))
.catch(error => console.error(error.message));
// 取消請(qǐng)求
cancel();樣式
定位:一般來(lái)說(shuō)寫(xiě) position: fixed 都是想相對(duì)窗口定位實(shí)現(xiàn)一些彈窗、抽屜或者浮動(dòng)組件等效果,但是如果父元素中存在 transform 屬性的話,固定效果將直接降級(jí)變成 position: absolute 的表現(xiàn)。這可能也是大多數(shù)UI庫(kù)選擇將 modal、drawer 之類(lèi)的 fixd 元素都插入在 body 下,和應(yīng)用本身分離開(kāi),可能就是擔(dān)心有 transform 來(lái)影響定位。究其原因是因?yàn)榘瑝K的定義:
如果 position 屬性為 static、relative 或 sticky,包含塊可能由它的最近的祖先塊元素(比如說(shuō) inline-block, block 或 list-item 元素)的內(nèi)容區(qū)的邊緣組成,也可能會(huì)建立格式化上下文 (比如說(shuō) a table container, flex container, grid container, 或者是 the block container 自身)。
- 如果 position 屬性為 absolute ,包含塊就是由它的最近的 position 的值不是 static (也就是值為 fixed, absolute, relative 或 sticky )的祖先元素的內(nèi)邊距區(qū)的邊緣組成。
- 如果 position 屬性是 fixed,在連續(xù)媒體的情況下 (continuous media) 包含塊是 viewport ,在分頁(yè)媒體 (paged media) 下的情況下包含塊是分頁(yè)區(qū)域 (page area)。
- 如果 position 屬性是 absolute 或 fixed,包含塊也可能是由滿(mǎn)足以下條件的最近父級(jí)元素的內(nèi)邊距區(qū)的邊緣組成的:
- transform 或 perspective 的值不是 none
- will-change 的值是 transform 或 perspective
- filter 的值不是 none 或 will-change 的值是 filter(只在 Firefox 下生效)。
- contain 的值是 paint(例如:contain: paint;)
- backdrop-filter 的值不是 none(例如:backdrop-filter: blur (10px) ; )
層疊計(jì)算:有的時(shí)候,如果引入了很多的庫(kù),會(huì)發(fā)現(xiàn)樣式會(huì)偶發(fā)的發(fā)生錯(cuò)誤。這是因?yàn)闃邮經(jīng)_突了,那樣式的優(yōu)先級(jí)是什么樣子的呢?css 全稱(chēng)為 cascader style sheet, 層疊樣式表。其層疊的目的就是為了比對(duì)樣式?jīng)_突后的“勝出者”;mdn 里詳細(xì)的介紹了其比較計(jì)算的規(guī)則。https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers
比對(duì)分以下步驟進(jìn)行
- 相關(guān)性: 為每個(gè)元素查找具有選擇器匹配的聲明塊。
- 重要性: 根據(jù)它們是正常的還是重要的對(duì)規(guī)則進(jìn)行排序。重要的樣式是那些設(shè)置了 !important 標(biāo)志的樣式。
- 來(lái)源: 在上面兩個(gè)重要性比較中,按作者、用戶(hù)或用戶(hù)代理來(lái)源對(duì)規(guī)則進(jìn)行排序。
- 層級(jí): 在每個(gè)六個(gè)來(lái)源重要性桶中,通過(guò)級(jí)聯(lián)層進(jìn)行排序。正常聲明的層次順序是從第一層創(chuàng)建到最后一層,然后是未分層的正常樣式。重要樣式的順序是相反的,未分層的重要樣式優(yōu)先級(jí)較低。
- 特定性: 對(duì)于來(lái)源層的競(jìng)爭(zhēng)樣式,根據(jù)特定性對(duì)聲明進(jìn)行排序。
- 出現(xiàn)順序: 當(dāng)具有優(yōu)先級(jí)的來(lái)源層中的兩個(gè)選擇器具有相同的特定性時(shí),具有最高特定性的最后聲明的選擇器的屬性值獲勝。通俗的解釋一下:規(guī)則自上往下去執(zhí)行,一旦找到了勝出者就不必向下執(zhí)行了。
- 看選擇器選沒(méi)選中東西,有選中的“勝出”
- 多人勝出,看有沒(méi)有 !important,有的話勝出
- 還是分不出唯一的勝者的話,繼續(xù)看來(lái)源,按作者、用戶(hù)或用戶(hù)代理來(lái)源的優(yōu)先級(jí),其中【作者】就是代碼的你,【用戶(hù)】就是站點(diǎn)用戶(hù),【用戶(hù)代理】就是瀏覽器默認(rèn)樣式
- ( 相關(guān)性+重要性 )*來(lái)源 有 6 種情況,這些情況要按分層進(jìn)行排序。先創(chuàng)建層級(jí)的比后創(chuàng)建的高,分層的比未分層優(yōu)先級(jí)高,對(duì)于重要樣式,這個(gè)順序則被倒轉(zhuǎn)了。所有未進(jìn)行圖層分層的重要樣式都會(huì)級(jí)聯(lián)在一起,形成一個(gè)隱含的層,這個(gè)層優(yōu)先級(jí)高于所有未進(jìn)行過(guò)轉(zhuǎn)換的普通樣式,但是優(yōu)先級(jí)低于任何分層的重要樣式。下面是一張 apple 官網(wǎng)網(wǎng)頁(yè)的分層圖,我們可以清除的看見(jiàn)分層之間的關(guān)系
分層就是瀏覽器畫(huà)制圖畫(huà)的順序,瀏覽器會(huì)根據(jù)一定的規(guī)則劃分圖層,當(dāng)然代碼也能干預(yù)圖層的劃分比如定位、動(dòng)畫(huà)等等,不同圖層直接不能相互影響,換句話說(shuō)一個(gè)圖層在另一個(gè)圖層下面的話,盡管 z-index 是 0 也能覆蓋 z-index: 100 的元素。
11. 特定性就是 4 位數(shù)字( 0,0,0,0 )第一位代表是否是行內(nèi)樣式,后面3位就是 id,class,tag 的個(gè)數(shù)。統(tǒng)計(jì)完特定性的 4 個(gè)數(shù)字后,從前往后比較大小。比較極端的情況下如果你寫(xiě)了足夠多的 class 是可以超過(guò) id 選擇器的。例如,以下選擇器有 11 個(gè) class 選擇器組成:
div.navbar ul ul.dropdown-menu li.active > a.btn-primary:hover span.icon {
/* styles */
}
// 雖然其中沒(méi)有 id 選擇器,但它顯然比單個(gè) id 選擇器的優(yōu)先級(jí)更高。12. 全部比對(duì)完后還是沒(méi)有“勝出者”的話,我們就會(huì)根據(jù)源代碼書(shū)寫(xiě)的順序,后來(lái)的覆蓋先來(lái)的。言歸正傳,所以我們樣式如果偶發(fā)的出現(xiàn)問(wèn)題,可能是因?yàn)榫W(wǎng)絡(luò)原因 javascript 下載下來(lái)的時(shí)間不確定,從而導(dǎo)致執(zhí)行后插入 css 文件的順序不一致,最終呈現(xiàn)出一種偶發(fā)的現(xiàn)象
性能
造成性能問(wèn)題的原因是多種多樣的,大體可以分為 3 種,一是網(wǎng)絡(luò),二是渲染,三是計(jì)算
- 網(wǎng)絡(luò)優(yōu)化的手段:
- 壓縮和合并資源,減少請(qǐng)求次數(shù)(一定程度的節(jié)省請(qǐng)求自身的消耗,請(qǐng)求本身就有一些請(qǐng)求頭、響應(yīng)頭等固定開(kāi)銷(xiāo))
- 減小體積:按需打包加載,模塊化的同構(gòu)相同邏輯的代碼
- 使用緩存:可以利用瀏覽器緩存機(jī)制,讓用戶(hù)再次訪問(wèn)頁(yè)面時(shí)不必重新加載文件,從而加快頁(yè)面的載入速度。
- 利用 CDN:可以使用內(nèi)容分發(fā)網(wǎng)絡(luò)( CDN )來(lái)分發(fā)資源,縮短用戶(hù)請(qǐng)求的時(shí)延。
- DNS 優(yōu)化:預(yù)解析一下網(wǎng)站內(nèi)的域名 ip,首次訪問(wèn)的用戶(hù)能更快的請(qǐng)求到資源
- 合理安排依賴(lài)資源的加載模式和加載順序,例如將 JavaScript 代碼放置在頁(yè)面底部,避免影響頁(yè)面的首次渲染時(shí)間
- 渲染優(yōu)化的手段:
為了避免頻繁的頁(yè)面重排和重繪,我們應(yīng)該盡量減少對(duì) DOM 的訪問(wèn)和修改。為了控制元素的樣式變化,應(yīng)該盡可能使用 CSS。這樣有助于提高頁(yè)面性能和用戶(hù)體驗(yàn)。
懶加載和預(yù)加載:懶加載可以減少初始頁(yè)面渲染時(shí)間,當(dāng)用戶(hù)需要訪問(wèn)到某個(gè)資源時(shí),才去加載這個(gè)資源,而預(yù)加載可以預(yù)先加載即將用到的資源,加快用戶(hù)訪問(wèn)其他頁(yè)面時(shí)的速度。
虛擬 DOM:使用 Virtual DOM,實(shí)現(xiàn)局部修改視圖而不是整體重新渲染,減少 DOM 的操作。
優(yōu)化 JavaScript 代碼:采用 JavaScript 模塊化、使用面試編程思想,減少頁(yè)面的 JavaScript 代碼,從而減少瀏覽器的工作量。
多線程:使用延時(shí)線程、網(wǎng)絡(luò)線程、Web Workers 等其他不會(huì)阻塞渲染的進(jìn)程來(lái)完成工作。
運(yùn)行時(shí)計(jì)算的手段:
優(yōu)化 JavaScript 代碼:采用 JavaScript 模塊化、使用面試編程思想,減少頁(yè)面的 JavaScript 代碼,從而減少瀏覽器的工作量。
算法優(yōu)化:用更加合理的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)和算法,以更優(yōu)的方式完成需求。
事件委托:將事件綁定在父元素上,減少事件的處理次數(shù)。
函數(shù)節(jié)流和防抖:使用函數(shù)節(jié)流技術(shù)避免頻繁觸發(fā)事件處理。
合理的事件注冊(cè)和解綁
合理的釋放不使用的內(nèi)存
兼容性
一些想當(dāng)然覺(jué)得應(yīng)該是一致的東西結(jié)果不一致,比如前瞻匹配和后瞻匹配的兼容是不一樣的。需要兼容IE的話就不能使用后瞻寫(xiě)法
圖片
參考文獻(xiàn)
https://developer.mozilla.org/zh-CN/docs/Web/CSS/Containing_block
https://caniuse.com/
https://standards.ieee.org/ieee/754/6210/
新聞標(biāo)題:前端常見(jiàn)問(wèn)題分析,你學(xué)會(huì)了嗎?
文章地址:http://fisionsoft.com.cn/article/copspcg.html


咨詢(xún)
建站咨詢(xún)
