摘要:網(wǎng)上找到的各種面試題整理,長期更新。大部分答案整理來自網(wǎng)絡(luò),有問題的地方,希望大家能指出,及時(shí)修改技術(shù)更新迭代,也會及時(shí)更新博客原地址前端前端性能優(yōu)化清理文檔,即超文本標(biāo)記語言,幾乎是所有網(wǎng)站的支柱。在最近更新的中,甚至可以創(chuàng)建圖表。
網(wǎng)上找到的各種面試題整理,長期更新。大部分答案整理來自網(wǎng)絡(luò),有問題的地方,希望大家能指出,及時(shí)修改;技術(shù)更新迭代,也會及時(shí)更新
博客原地址:https://finget.github.io/2019...前端 前端性能優(yōu)化
1.清理 HTML 文檔
HTML,即超文本標(biāo)記語言,幾乎是所有網(wǎng)站的支柱。HTML 為網(wǎng)頁帶來標(biāo)題、子標(biāo)題、列表和其它一些文檔結(jié)構(gòu)的格式。在最近更新的 HTML5 中,甚至可以創(chuàng)建圖表。
HTML 很容易被網(wǎng)絡(luò)爬蟲識別,因此搜索引擎可以根據(jù)網(wǎng)站的內(nèi)容在一定程度上實(shí)時(shí)更新。在寫 HTML 的時(shí)候,你應(yīng)該嘗試讓它簡潔而有效。此外,在 HTML 文檔中引用外部資源的時(shí)候也需要遵循一些最佳實(shí)踐方法。
a.恰當(dāng)放置 CSS
Web 設(shè)計(jì)者喜歡在網(wǎng)頁建立起主要的 HTML 骨架之后再來創(chuàng)建樣式表。這樣一來,網(wǎng)頁中的樣式表往往會放在 HTML 的后面,接近文檔結(jié)束的地方。然而推薦的做法是把 CSS 放在 HTML 的上面部分,文檔頭之內(nèi),這可以確保正常的渲染過程。
這個(gè)策略不能提高網(wǎng)站的加載速度,但它不會讓訪問者長時(shí)間看著空白屏幕或者無格式的文本(FOUT)等待。如果網(wǎng)頁大部分可見元素已經(jīng)加載出來了,訪問者才更有可能等待加載整個(gè)頁面,從而帶來對前端的優(yōu)化效果。這就是知覺性能
b.正確放置 Javascript
另一方面,如果將 JavaScript 放置在 head 標(biāo)簽內(nèi)或 HTML 文檔的上部,這會阻塞 HTML 和 CSS 元素的加載過程。這個(gè)錯(cuò)誤會導(dǎo)致頁面加載時(shí)間增長,增加用戶等待時(shí)間,容易讓人感到不耐煩而放棄對網(wǎng)站的訪問。不過,您可以通過將 JavaScript 屬性置于 HTML 底部來避免此問題。
此外,在使用 JavaScript 時(shí),人們通常喜歡用異步腳本加載。這會阻止標(biāo)簽在 HTML 中的呈現(xiàn)過程,如,在文檔中間的情況。
雖然對于網(wǎng)頁設(shè)計(jì)師來說, HTML 是最值得使用的工具之一,但它通常要與 CSS 和 JavaScript 一起使用,這可能會導(dǎo)致網(wǎng)頁瀏覽速度減慢。 雖然 CSS 和 JavaScript 有利于網(wǎng)頁優(yōu)化,但使用時(shí)也要注意一些問題。使用 CSS 和 JavaScript 時(shí),要避免嵌入代碼。因?yàn)楫?dāng)您嵌入代碼時(shí),要將 CSS 放置在樣式標(biāo)記中,并在腳本標(biāo)記中使用 JavaScript,這會增加每次刷新網(wǎng)頁時(shí)必須加載的 HTML 代碼量。
2.優(yōu)化 CSS 性能
CSS,即級聯(lián)樣式表,能從 HTML 描述的內(nèi)容生成專業(yè)而又整潔的文件。很多 CSS 需要通過 HTTP 請求來引入(除非使用內(nèi)聯(lián) CSS),所以你要努力去除累贅的 CSS 文件,但要注意保留其重要特征。
如果你的 Banner、插件和布局樣式是使用 CSS 保存在不同的文件內(nèi),那么,訪問者的瀏覽器每次訪問都會加載很多文件。雖然現(xiàn)在 HTTP/2 的存在,減少了這種問題的發(fā)生,但是在外部資源加載的情況下,仍會花費(fèi)較長時(shí)間。要了解如何減少 HTTP 請求以大幅度縮減加載時(shí)間,請閱讀WordPress 性能。
此外,不少網(wǎng)站管理員在網(wǎng)頁中錯(cuò)誤的使用 @import 指令 來引入外部樣式表。這是一個(gè)過時(shí)的方法,它會阻止瀏覽并行下載。link 標(biāo)簽才是最好的選擇,它也能提高網(wǎng)站的前端性能。多說一句,通過 link 標(biāo)簽請求加載的外部樣式表不會阻止并行下載。
3.減少外部HTTP請求
在很多情況下,網(wǎng)站的大部分加載時(shí)間來自于外部的 Http 請求。外部資源的加載速度隨著主機(jī)提供商的服務(wù)器架構(gòu)、地點(diǎn)等不同而不同。減少外部請求要做的第一步就是簡略地檢查網(wǎng)站。研究你網(wǎng)站的每個(gè)組成部分,消除任何影響訪問者體驗(yàn)不好的成分。這些成分可能是:
不必要的圖片
沒用的 JavaScript 代碼
過多的 css
多余的插件
在你去掉這些多余的成分之后,再對剩下的內(nèi)容進(jìn)行整理,如,壓縮工具、CDN 服務(wù)和預(yù)獲取(prefetching)等,這些都是管理 HTTP 請求的最佳選擇。除此之外,減少DNS路由查找教程會教你如何一步一步的減少外部 HTTP 請求。
4.壓縮 CSS, JS 和 HTML
壓縮技術(shù)可以從文件中去掉多余的字符。你在編輯器中寫代碼的時(shí)候,會使用縮進(jìn)和注釋,這些方法無疑會讓你的代碼簡潔而且易讀,但它們也會在文檔中添加多余的字節(jié)。
使用預(yù)先獲取
預(yù)先獲取可以在真正需要之前通過取得必需的資源和相關(guān)數(shù)據(jù)來改善訪問用戶的瀏覽體驗(yàn),主要有3類預(yù)先獲取:
鏈接預(yù)先獲取
DNS 預(yù)先獲取
預(yù)先渲染
在你離開當(dāng)前 web 頁面之前,使用預(yù)先獲取方式,對應(yīng)每個(gè)鏈接的 URL 地址,CSS,圖片和腳本都會被預(yù)先獲取。這保證了訪問者能在最短時(shí)間內(nèi)使用鏈接在畫面間切換。
幸運(yùn)的是,預(yù)先獲取很容易實(shí)現(xiàn)。根據(jù)你想要使用的預(yù)先獲取形式,你只需在網(wǎng)站 HTML 中的鏈接屬性上增加 rel=”prefetch”,rel=”dns-prefetch”,或者 rel=”prerender” 標(biāo)記。
6.使用 CDN 和緩存提高速度
內(nèi)容分發(fā)網(wǎng)絡(luò)能顯著提高網(wǎng)站的速度和性能。使用 CDN 時(shí),您可以將網(wǎng)站的靜態(tài)內(nèi)容鏈接到全球各地的服務(wù)器擴(kuò)展網(wǎng)絡(luò)。如果您的網(wǎng)站觀眾遍布全球,這項(xiàng)功能十分有用。 CDN 允許您的網(wǎng)站訪問者從最近的服務(wù)器加載數(shù)據(jù)。如果您使用 CDN,您網(wǎng)站內(nèi)的文件將自動(dòng)壓縮,以便在全球范圍內(nèi)快速分發(fā)。
CDN 是一種緩存方法,可極大改善資源的分發(fā)時(shí)間,同時(shí),它還能實(shí)現(xiàn)一些其他的緩存技術(shù),如,利用瀏覽器緩存。
合理地設(shè)置瀏覽器緩存,能讓瀏覽器自動(dòng)存儲某些文件,以便加快傳輸速度。此方法的配置可以直接在源服務(wù)器的配置文件中完成。
7.壓縮文件
雖然許多 CDN 服務(wù)可以壓縮文件,但如果不使用 CDN,您也可以考慮在源服務(wù)器上使用文件壓縮方法來改進(jìn)前端優(yōu)化。 文件壓縮能使網(wǎng)站的內(nèi)容輕量化,更易于管理。 最常用的文件壓縮方法之一是 Gzip。 這是縮小文檔、音頻文件、PNG圖像和等其他大文件的絕佳方法。
Brotli 是一個(gè)比較新的文件壓縮算法,目前正變得越來越受歡迎。 此開放源代碼算法由來自 Google 和其他組織的軟件工程師定期更新,現(xiàn)已被證明比其他現(xiàn)有壓縮方法更好用。 這種算法的支持目前還比較少,但作為后起之秀指日可待。
8.使用輕量級框架
除非你只用現(xiàn)有的編碼知識構(gòu)建網(wǎng)站,不然,你可以嘗試使用一個(gè)好的前端框架來避免許多不必要的前端優(yōu)化錯(cuò)誤。雖然有一些更大,更知名的框架能提供更多功能和選項(xiàng),但它們不一定適合你的 Web 項(xiàng)目。
所以說,不僅確定項(xiàng)目所需功能很重要,選擇合適的框架也很重要——它要在提供所需功能的同時(shí)保持輕量。最近許多框架都使用簡潔的 HTML,CSS 和 JavaScript 代碼。
一個(gè)頁面從輸入 URL 到頁面加載顯示完成,這個(gè)過程中都發(fā)生了什么?參考鏈接:
詳細(xì)解讀https://segmentfault.com/a/1190000006879700
詳細(xì)解讀https://mp.weixin.qq.com/s/jjL4iA7p6aYEAQyWhn4QbQ
輸入地址
1.瀏覽器查找域名的 IP 地址
2.這一步包括 DNS 具體的查找過程,包括:瀏覽器緩存->系統(tǒng)緩存->路由器緩存…
3.瀏覽器向 web 服務(wù)器發(fā)送一個(gè) HTTP 請求
4.服務(wù)器的永久重定向響應(yīng)(從 http://example.com 到 http://www.example.com)
5.瀏覽器跟蹤重定向地址
6.服務(wù)器處理請求
7.服務(wù)器返回一個(gè) HTTP 響應(yīng)
8.瀏覽器顯示 HTML
9.瀏覽器發(fā)送請求獲取嵌入在 HTML 中的資源(如圖片、音頻、視頻、CSS、JS等等)
10.瀏覽器發(fā)送異步請求
URL(Uniform Resource Locator),統(tǒng)一資源定位符,用于定位互聯(lián)網(wǎng)上資源,俗稱網(wǎng)址。
比如 http://www.w3school.com.cn/ht...,遵守以下的語法規(guī)則:
scheme://host.domain:port/path/filename
各部分解釋如下:
scheme - 定義因特網(wǎng)服務(wù)的類型。常見的協(xié)議有 http、https、ftp、file,其中最常見的類型是 http,而 https 則是進(jìn)行加密的網(wǎng)絡(luò)傳輸。
host - 定義域主機(jī)(http 的默認(rèn)主機(jī)是 www)
domain - 定義因特網(wǎng)域名,比如 w3school.com.cn
port - 定義主機(jī)上的端口號(http 的默認(rèn)端口號是 80)
path - 定義服務(wù)器上的路徑(如果省略,則文檔必須位于網(wǎng)站的根目錄中)。
filename - 定義文檔/資源的名稱
客服端和服務(wù)端在進(jìn)行http請求和返回的工程中,需要?jiǎng)?chuàng)建一個(gè)TCP connection(由客戶端發(fā)起),http不存在連接這個(gè)概念,它只有請求和響應(yīng)。請求和響應(yīng)都是數(shù)據(jù)包,它們之間的傳輸通道就是TCP connection。
位碼即tcp標(biāo)志位,有6種標(biāo)示:SYN(synchronous建立聯(lián)機(jī)) ACK(acknowledgement 確認(rèn)) PSH(push傳送) FIN(finish結(jié)束) RST(reset重置) URG(urgent緊急)Sequence number(順序號碼) Acknowledge number(確認(rèn)號碼)
第一次握手:主機(jī)A發(fā)送位碼為syn=1,隨機(jī)產(chǎn)生seq number=1234567的數(shù)據(jù)包到服務(wù)器,主機(jī)B由SYN=1知道,A要求建立聯(lián)機(jī);(第一次握手,由瀏覽器發(fā)起,告訴服務(wù)器我要發(fā)送請求了)
第二次握手:主機(jī)B收到請求后要確認(rèn)聯(lián)機(jī)信息,向A發(fā)送ack number=(主機(jī)A的seq+1),syn=1,ack=1,隨機(jī)產(chǎn)生seq=7654321的包;(第二次握手,由服務(wù)器發(fā)起,告訴瀏覽器我準(zhǔn)備接受了,你趕緊發(fā)送吧)
第三次握手:主機(jī)A收到后檢查ack number是否正確,即第一次發(fā)送的seq number+1,以及位碼ack是否為1,若正確,主機(jī)A會再發(fā)送ack number=(主機(jī)B的seq+1),ack=1,主機(jī)B收到后確認(rèn)seq值與ack=1則連接建立成功;(第三次握手,由瀏覽器發(fā)送,告訴服務(wù)器,我馬上就發(fā)了,準(zhǔn)備接受吧)
謝希仁著《計(jì)算機(jī)網(wǎng)絡(luò)》中講“三次握手”的目的是“為了防止已失效的連接請求報(bào)文段突然又傳送到了服務(wù)端,因而產(chǎn)生錯(cuò)誤。
這種情況是:一端(client)A發(fā)出去的第一個(gè)連接請求報(bào)文并沒有丟失,而是因?yàn)槟承┪粗脑蛟谀硞€(gè)網(wǎng)絡(luò)節(jié)點(diǎn)上發(fā)生滯留,導(dǎo)致延遲到連接釋放以后的某個(gè)時(shí)間才到達(dá)另一端(server)B。本來這是一個(gè)早已失效的報(bào)文段,但是B收到此失效的報(bào)文之后,會誤認(rèn)為是A再次發(fā)出的一個(gè)新的連接請求,于是B端就向A又發(fā)出確認(rèn)報(bào)文,表示同意建立連接。如果不采用“三次握手”,那么只要B端發(fā)出確認(rèn)報(bào)文就會認(rèn)為新的連接已經(jīng)建立了,但是A端并沒有發(fā)出建立連接的請求,因此不會去向B端發(fā)送數(shù)據(jù),B端沒有收到數(shù)據(jù)就會一直等待,這樣B端就會白白浪費(fèi)掉很多資源。如果采用“三次握手”的話就不會出現(xiàn)這種情況,B端收到一個(gè)過時(shí)失效的報(bào)文段之后,向A端發(fā)出確認(rèn),此時(shí)A并沒有要求建立連接,所以就不會向B端發(fā)送確認(rèn),這個(gè)時(shí)候B端也能夠知道連接沒有建立。
問題的本質(zhì)是,信道是不可靠的,但是我們要建立可靠的連接發(fā)送可靠的數(shù)據(jù),也就是數(shù)據(jù)傳輸是需要可靠的。在這個(gè)時(shí)候三次握手是一個(gè)理論上的最小值,并不是說是tcp協(xié)議要求的,而是為了滿足在不可靠的信道上傳輸可靠的數(shù)據(jù)所要求的。
這個(gè)網(wǎng)上轉(zhuǎn)載的例子不錯(cuò):
三次握手:
A:“喂,你聽得到嗎?”A->SYN_SEND
B:“我聽得到呀,你聽得到我嗎?”應(yīng)答與請求同時(shí)發(fā)出 B->SYN_RCVD | A->ESTABLISHED
A:“我能聽到你,今天balabala……”B->ESTABLISHED
四次揮手:
A:“喂,我不說了。”A->FIN_WAIT1
B:“我知道了。等下,上一句還沒說完。Balabala…..”B->CLOSE_WAIT | A->FIN_WAIT2
B:”好了,說完了,我也不說了。”B->LAST_ACK
A:”我知道了。”A->TIME_WAIT | B->CLOSED
A等待2MSL,保證B收到了消息,否則重說一次”我知道了”,A->CLOSE
iframe會阻塞主頁面的Onload事件;
搜索引擎的檢索程序無法解讀這種頁面,不利于SEO;
iframe和主頁面共享連接池,而瀏覽器對相同域的連接有限制,所以會影響頁面的并行加載。
使用iframe之前需要考慮這兩個(gè)缺點(diǎn)。如果需要使用iframe,最好是通過javascript動(dòng)態(tài)給iframe添加src屬性值,這樣可以繞開以上兩個(gè)問題
websocket握手過程在實(shí)現(xiàn)websocket連線過程中,需要通過瀏覽器發(fā)出websocket連線請求,然后服務(wù)器發(fā)出回應(yīng),這個(gè)過程通常稱為“握手” (handshaking)。
客戶端請求web socket連接時(shí),會向服務(wù)器端發(fā)送握手請求
請求頭大致內(nèi)容:
請求包說明:
必須是有效的http request 格式;
HTTP request method 必須是GET,協(xié)議應(yīng)不小于1.1 如: Get / HTTP/1.1;
必須包括Upgrade頭域,并且其值為”websocket”;
必須包括”Connection” 頭域,并且其值為”Upgrade”;
必須包括”Sec-WebSocket-Key”頭域,其值采用base64編碼的隨機(jī)16字節(jié)長的字符序列;
如果請求來自瀏覽器客戶端,還必須包括Origin頭域 。 該頭域用于防止未授權(quán)的跨域腳本攻擊,服務(wù)器可以從Origin決定是否接受該WebSocket連接;
必須包括”Sec-webSocket-Version” 頭域,當(dāng)前值必須是13;
可能包括”Sec-WebSocket-Protocol”,表示client(應(yīng)用程序)支持的協(xié)議列表,server選擇一個(gè)或者沒有可接受的協(xié)議響應(yīng)之;
可能包括”Sec-WebSocket-Extensions”, 協(xié)議擴(kuò)展, 某類協(xié)議可能支持多個(gè)擴(kuò)展,通過它可以實(shí)現(xiàn)協(xié)議增強(qiáng);
可能包括任意其他域,如cookie.
服務(wù)端響應(yīng)如下:
應(yīng)答包說明:
*必須包括Upgrade頭域,并且其值為”websocket”;
*必須包括Connection頭域,并且其值為”Upgrade”;
*必須包括Sec-WebSocket-Accept頭域,其值是將請求包“Sec-WebSocket-Key”的值,與”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″這個(gè)字符串進(jìn)行拼接,然后對拼接后的字符串進(jìn)行sha-1運(yùn)算,再進(jìn)行base64編碼,就是“Sec-WebSocket-Accept”的值;
*應(yīng)答包中冒號后面有一個(gè)空格;
*最后需要兩個(gè)空行作為應(yīng)答包結(jié)束
參考鏈接:
Websocket協(xié)議之握手連接
同源
符合”協(xié)議+域名+端口”三者相同,就是同源
同源策略
同源策略,其初衷是為了瀏覽器的安全性,通過以下三種限制,保證瀏覽器不易受到XSS、CSFR等攻擊。
- Cookie、LocalStorage 和 IndexDB 無法讀取 - DOM 和 Js對象無法獲得 - AJAX 請求不能發(fā)送
跨域解決方案
通過jsonp跨域
document.domain + iframe跨域
location.hash + iframe
window.name + iframe跨域
postMessage跨域
跨域資源共享(CORS)
nginx代理跨域
nodejs中間件代理跨域
WebSocket協(xié)議跨域
前端持久化的方式、區(qū)別最容易想到的解決方案是:
1.使用前端cookie技術(shù)來保存本地化數(shù)據(jù),如jquery.cookie.js;
2.使用html5提供的Web Storage技術(shù)來提供解決方案;
用cookie存儲永久數(shù)據(jù)存在以下幾個(gè)問題:
1.大小:cookie的大小被限制在4KB。
2.帶寬:cookie是隨HTTP事務(wù)一起被發(fā)送的,因此會浪費(fèi)一部分發(fā)送cookie時(shí)使用的帶寬。
3.復(fù)雜性:要正確的操縱cookie是很困難的。
針對這些問題,在HTML5中,重新提供了一種在客戶端本地保存數(shù)據(jù)的功能,它就是Web Storage。
具體來說,Web Storage又分為兩種:
1.sessionStorage:將數(shù)據(jù)保存在session對象中。所謂session,是指用戶在瀏覽某個(gè)網(wǎng)站時(shí),從進(jìn)入網(wǎng)站到瀏覽器關(guān)閉所經(jīng)過的這段時(shí)間,也就是用戶瀏覽這個(gè)網(wǎng)站所花費(fèi)的時(shí)間。session對象可以用來保存在這段時(shí)間內(nèi)所要求保存的任何數(shù)據(jù)。
2.localStorage:將數(shù)據(jù)保存在客戶端本地的硬件設(shè)備(通常指硬盤,也可以是其他硬件設(shè)備)中,即使瀏覽器被關(guān)閉了,該數(shù)據(jù)仍然存在,下次打開瀏覽器訪問網(wǎng)站時(shí)仍然可以繼續(xù)使用。
這兩者的區(qū)別在于,sessionStorage為臨時(shí)保存,而localStorage為永久保存。
前端持久化--evercookie
介紹http2.0所有數(shù)據(jù)以二進(jìn)制傳輸。HTTP1.x是基于文本的,無法保證健壯性,HTTP2.0絕對使用新的二進(jìn)制格式,方便且健壯
同一個(gè)連接里面發(fā)送多個(gè)請求不再需要按照順序來
頭信息壓縮以及推送等提高效率的功能
Http 2.0協(xié)議簡介
HTTP 2.0 詳細(xì)介紹,http2.0詳細(xì)介紹
HTTP/2.0 相比1.0有哪些重大改進(jìn)
我能想到的只有Promise.all(),歡迎補(bǔ)充
b和strong的區(qū)別 粗體文本, 用于強(qiáng)調(diào)文本,他們的樣式是一樣的
有一種說法,是貌似在盲人用的機(jī)器上會讀兩遍。因?yàn)闆]有對應(yīng)的測試條件,所以沒做驗(yàn)證。
header("Access-Control-Allow-Origin:*");
csrf跨站攻擊怎么解決CSRF,全稱為Cross-Site Request Forgery,跨站請求偽造,是一種網(wǎng)絡(luò)攻擊方式,它可以在用戶毫不知情的情況下,以用戶的名義偽造請求發(fā)送給被攻擊站點(diǎn),從而在未授權(quán)的情況下進(jìn)行權(quán)限保護(hù)內(nèi)的操作。
具體來講,可以這樣理解CSRF。攻擊者借用用戶的名義,向某一服務(wù)器發(fā)送惡意請求,對服務(wù)器來講,這一請求是完全合法的,但攻擊者確完成了一個(gè)惡意操作,比如以用戶的名義發(fā)送郵件,盜取賬號,購買商品等等
一般網(wǎng)站防御CSRF攻擊的方案:
(1)驗(yàn)證token值。
(2)驗(yàn)證HTTP頭的Referer。
(3)在HTTP頭中自定義屬性并驗(yàn)證
(4)服務(wù)器端表單hash認(rèn)證
在所有的表單里面隨機(jī)生成一個(gè)hash,server在表單處理時(shí)去驗(yàn)證這個(gè)hash值是否正確,這樣工作量比較大
CSRF(跨站請求偽造攻擊)漏洞詳解
CSS 清除浮動(dòng)的方式// 第一種 .ovh{ overflow:hidden; } // 第二種 .clear{ clear:both; } // 第三種 .clearfix:after{ content:"";//設(shè)置內(nèi)容為空 height:0;//高度為0 line-height:0;//行高為0 display:block;//將文本轉(zhuǎn)為塊級元素 visibility:hidden;//將元素隱藏 clear:both//清除浮動(dòng) } .clearfix{ zoom:1;為了兼容IE }
免費(fèi)公開課帶你徹底掌握 CSS 浮動(dòng)
當(dāng)給父元素設(shè)置"overflow:hidden"時(shí),實(shí)際上創(chuàng)建了一個(gè)超級屬性BFC,此超級屬性反過來決定了"height:auto"是如何計(jì)算的。在“BFC布局規(guī)則”中提到:計(jì)算BFC的高度時(shí),浮動(dòng)元素也參與計(jì)算。因此,父元素在計(jì)算其高度時(shí),加入了浮動(dòng)元素的高度,“順便”達(dá)成了清除浮動(dòng)的目標(biāo),所以父元素就包裹住了子元素。垂直居中的幾種方式
// 第一種 .center { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } // 第二種 .center { width: 100px; height: 100px; position: absolute; left: 50%; top: 50%; margin-top: -50px; margin-left: -50px; } // 第三種 .center { position: absolute; margin:auto; top: 0; bottom: 0; left: 0; right: 0; } // 第四種 .parent { display: flex; align-items: center; justify-content: center; } // 第五種 .parent{ display: flex; } .content{ margin: auto; /*自動(dòng)相對于父元素水平垂直居中*/ } // 第六種 .parent { display: table; } .child { display: table-cell; vertical-align: middle; }BFC是什么
BFC(Block Formatting Context),塊級格式化上下文,是Web頁面中盒模型布局的CSS渲染模式。它的定位體系屬于常規(guī)文檔流。
原理(渲染規(guī)則):
在BFC元素的垂直方向上的邊距,會發(fā)生重疊
BFC的區(qū)域不會與浮動(dòng)元素的box重疊
BFC在頁面上是一個(gè)獨(dú)立的容器,外面的元素不會影響里面的元素
計(jì)算BFC高度時(shí),浮動(dòng)元素也會參與計(jì)算
1
3
我是浮動(dòng)元素
怎么創(chuàng)建BFC:
float的值不為none
position的值不為static或者relative
display的值為 table-cell, table-caption, inline-block, flex, 或者 inline-flex中的其中一個(gè)
overflow的值不為visible
浮動(dòng),絕對定位元素,inline-blocks, table-cells, table-captions,和overflow的值不為visible的元素,(除了這個(gè)值已經(jīng)被傳到了視口的時(shí)候)將創(chuàng)建一個(gè)新的塊級格式化上下文。
上面的引述幾乎總結(jié)了一個(gè)BFC是怎樣形成的。但是讓我們以另一種方式來重新定義以便能更好的去理解.
參考鏈接:
理解CSS中BFC
這個(gè)直接看 阮一峰:Flex 布局教程
介紹css3中position:sticky單詞sticky的中文意思是“粘性的”,position:sticky表現(xiàn)也符合這個(gè)粘性的表現(xiàn)。基本上,可以看出是position:relative和position:fixed的結(jié)合體——當(dāng)元素在屏幕內(nèi),表現(xiàn)為relative,就要滾出顯示器屏幕的時(shí)候,表現(xiàn)為fixed。
詳細(xì)講解的還是看大神的吧,張鑫旭:position:sticky
JavaScript js三座大山原型與原型鏈,作用域及閉包,異步和單線程。
三座大山,真不是一兩句可以說清楚的,只有靠大家多看,多用,多理解,放點(diǎn)鏈接吧。
原型,原型鏈,call/apply
JavaScript從初級往高級走系列————prototype
JavaScript從初級往高級走系列————異步
JavaScript的預(yù)編譯過程
內(nèi)存空間詳解
作用域和閉包
JavaScript深入之詞法作用域和動(dòng)態(tài)作用域
JavaScript深入之作用域鏈
事件循環(huán)機(jī)制
參考鏈接:
什么是閉包?https://mp.weixin.qq.com/s/OthfFRwf-rQmVbMnXAqnCg
作用域與閉包https://yangbo5207.github.io/wutongluo/ji-chu-jin-jie-xi-lie/si-3001-zuo-yong-yu-lian-yu-bi-bao.html
簡言之,閉包是由函數(shù)引用其周邊狀態(tài)(詞法環(huán)境)綁在一起形成的(封裝)組合結(jié)構(gòu)。在 JavaScript 中,閉包在每個(gè)函數(shù)被創(chuàng)建時(shí)形成。
這是基本原理,但為什么我們關(guān)心這些?實(shí)際上,由于閉包與它的詞法環(huán)境綁在一起,因此閉包讓我們能夠從一個(gè)函數(shù)內(nèi)部訪問其外部函數(shù)的作用域。
要使用閉包,只需要簡單地將一個(gè)函數(shù)定義在另一個(gè)函數(shù)內(nèi)部,并將它暴露出來。要暴露一個(gè)函數(shù),可以將它返回或者傳給其他函數(shù)。
內(nèi)部函數(shù)將能夠訪問到外部函數(shù)作用域中的變量,即使外部函數(shù)已經(jīng)執(zhí)行完畢。
在 JavaScript 中,閉包是用來實(shí)現(xiàn)數(shù)據(jù)私有的原生機(jī)制。當(dāng)你使用閉包來實(shí)現(xiàn)數(shù)據(jù)私有時(shí),被封裝的變量只能在閉包容器函數(shù)作用域中使用。你無法繞過對象被授權(quán)的方法在外部訪問這些數(shù)據(jù)。在 JavaScript 中,任何定義在閉包作用域下的公開方法才可以訪問這些數(shù)據(jù)。
宏任務(wù) 與 微任務(wù)參考鏈接:
js引擎執(zhí)行機(jī)制https://segmentfault.com/a/1190000012806637
事件循環(huán)機(jī)制
一個(gè)線程中,事件循環(huán)是唯一的,但是任務(wù)隊(duì)列可以擁有多個(gè)。
任務(wù)隊(duì)列又分為macro-task(宏任務(wù))與micro-task(微任務(wù)),在最新標(biāo)準(zhǔn)中,它們被分別稱為task與jobs。
macro-task大概包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task大概包括: process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)
setTimeout/Promise等我們稱之為任務(wù)源。而進(jìn)入任務(wù)隊(duì)列的是他們指定的具體執(zhí)行任務(wù)。
// setTimeout中的回調(diào)函數(shù)才是進(jìn)入任務(wù)隊(duì)列的任務(wù) setTimeout(function() { console.log("xxxx"); }) // 非常多的同學(xué)對于setTimeout的理解存在偏差。所以大概說一下誤解: // setTimeout作為一個(gè)任務(wù)分發(fā)器,這個(gè)函數(shù)會立即執(zhí)行,而它所要分發(fā)的任務(wù),也就是它的第一個(gè)參數(shù),才是延遲執(zhí)行
來自不同任務(wù)源的任務(wù)會進(jìn)入到不同的任務(wù)隊(duì)列。其中setTimeout與setInterval是同源的。
事件循環(huán)的順序,決定了JavaScript代碼的執(zhí)行順序。它從script(整體代碼)開始第一次循環(huán)。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。直到調(diào)用棧清空(只剩全局),然后執(zhí)行所有的micro-task。當(dāng)所有可執(zhí)行的micro-task執(zhí)行完畢之后。循環(huán)再次從macro-task開始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,然后再執(zhí)行所有的micro-task,這樣一直循環(huán)下去。
其中每一個(gè)任務(wù)的執(zhí)行,無論是macro-task還是micro-task,都是借助函數(shù)調(diào)用棧來完成。
promise里面和then里面執(zhí)行有什么區(qū)別promise里面的是宏任務(wù),then后面的是微任務(wù)。
JS為什么要區(qū)分微任務(wù)和宏任務(wù)這個(gè)問題本質(zhì)就是為啥需要異步。如果js不是異步的話,由于js代碼本身是自上而下執(zhí)行的,那么如果上一行代碼需要執(zhí)行很久,下面的代碼就會被阻塞,對用戶來說,就是”卡死”,這樣的話,會造成很差的用戶體驗(yàn)。
JavaScript 實(shí)現(xiàn)異步編程的4種方法你可能知道,Javascript語言的執(zhí)行環(huán)境是"單線程"(single thread)。
所謂"單線程",就是指一次只能完成一件任務(wù)。如果有多個(gè)任務(wù),就必須排隊(duì),前面一個(gè)任務(wù)完成,再執(zhí)行后面一個(gè)任務(wù),以此類推。
這種模式的好處是實(shí)現(xiàn)起來比較簡單,執(zhí)行環(huán)境相對單純;壞處是只要有一個(gè)任務(wù)耗時(shí)很長,后面的任務(wù)都必須排隊(duì)等著,會拖延整個(gè)程序的執(zhí)行。常見的瀏覽器無響應(yīng)(假死),往往就是因?yàn)槟骋欢蜫avascript代碼長時(shí)間運(yùn)行(比如死循環(huán)),導(dǎo)致整個(gè)頁面卡在這個(gè)地方,其他任務(wù)無法執(zhí)行。
為了解決這個(gè)問題,Javascript語言將任務(wù)的執(zhí)行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。
回調(diào)函數(shù)
假定有兩個(gè)函數(shù)f1和f2,后者等待前者的執(zhí)行結(jié)果。
如果f1是一個(gè)很耗時(shí)的任務(wù),可以考慮改寫f1,把f2寫成f1的回調(diào)函數(shù)
function f1(callback){ setTimeout(function () { // f1的任務(wù)代碼 callback(); }, 1000); }
回調(diào)函數(shù)的優(yōu)點(diǎn)是簡單、容易理解和部署,缺點(diǎn)是不利于代碼的閱讀和維護(hù),各個(gè)部分之間高度耦合(Coupling),流程會很混亂,而且每個(gè)任務(wù)只能指定一個(gè)回調(diào)函數(shù)。
事件監(jiān)聽
另一種思路是采用事件驅(qū)動(dòng)模式。任務(wù)的執(zhí)行不取決于代碼的順序,而取決于某個(gè)事件是否發(fā)生。
f1.on("done", f2);
上面這行代碼的意思是,當(dāng)f1發(fā)生done事件,就執(zhí)行f2。然后,對f1進(jìn)行改寫:
function f1(){ setTimeout(function () { // f1的任務(wù)代碼 f1.trigger("done"); }, 1000); }
發(fā)布訂閱
我們假定,存在一個(gè)"信號中心",某個(gè)任務(wù)執(zhí)行完成,就向信號中心"發(fā)布"(publish)一個(gè)信號,其他任務(wù)可以向信號中心"訂閱"(subscribe)這個(gè)信號,從而知道什么時(shí)候自己可以開始執(zhí)行。這就叫做"發(fā)布/訂閱模式"(publish-subscribe pattern),又稱"觀察者模式"(observer pattern)。
jQuery.subscribe("done", f2);
function f1(){ setTimeout(function () { // f1的任務(wù)代碼 jQuery.publish("done"); }, 1000); }
Promise
f1().then(f2).then(f3);new 的過程
新生成了一個(gè)對象
鏈接到原型
綁定 this
返回新對象
function create() { // 創(chuàng)建一個(gè)空的對象 let obj = new Object() // 獲得構(gòu)造函數(shù) let Con = [].shift.call(arguments) // 鏈接到原型 obj.__proto__ = Con.prototype // 綁定 this,執(zhí)行構(gòu)造函數(shù) let result = Con.apply(obj, arguments) // 確保 new 出來的是個(gè)對象 return typeof result === "object" ? result : obj }原型繼承與類繼承
JS原型繼承和類式繼承http://www.cnblogs.com/constantince/p/4754992.html
// 類繼承 var father = function() { this.age = 52; this.say = function() { alert("hello i am "+ this.name " and i am "+this.age + "years old"); } } var child = function() { this.name = "bill"; father.call(this); } var man = new child(); man.say();
// 原型繼承 var father = function() { } father.prototype.a = function() { } var child = function(){} //開始繼承 child.prototype = new father(); var man = new child(); man.a();
和原型對比起來,構(gòu)造函數(shù)(類)式繼承有什么不一樣呢?首先,構(gòu)造函數(shù)繼承的方法都會存在父對象之中,每一次實(shí)例,都會將funciton保存在內(nèi)存中,這樣的做法毫無以為會帶來性能上的問題。其次類式繼承是不可變的。在運(yùn)行時(shí),無法修改或者添加新的方法,這種方式是一種固步自封的死方法。而原型繼承是可以通過改變原型鏈接而對子類進(jìn)行修改的。另外就是類式繼承不支持多重繼承,而對于原型繼承來說,你只需要寫好extend對對象進(jìn)行擴(kuò)展即可。
== 和 ===的區(qū)別,什么情況下用相等====是===類型轉(zhuǎn)換(又稱強(qiáng)制),==只需要值相等就會返回true,而===必須值和數(shù)據(jù)類型都相同才會返回true。
bind、call、apply的區(qū)別1.每個(gè)函數(shù)都包含兩個(gè)非繼承而來的方法:call()方法和apply()方法。
2.相同點(diǎn):這兩個(gè)方法的作用是一樣的。
都是在特定的作用域中調(diào)用函數(shù),等于設(shè)置函數(shù)體內(nèi)this對象的值,以擴(kuò)充函數(shù)賴以運(yùn)行的作用域。
一般來說,this總是指向調(diào)用某個(gè)方法的對象,但是使用call()和apply()方法時(shí),就會改變this的指向。
3.不同點(diǎn):接收參數(shù)的方式不同。
apply()方法 接收兩個(gè)參數(shù),一個(gè)是函數(shù)運(yùn)行的作用域(this),另一個(gè)是參數(shù)數(shù)組。
語法:apply([thisObj [,argArray] ]);,調(diào)用一個(gè)對象的一個(gè)方法,2另一個(gè)對象替換當(dāng)前對象。
說明:如果argArray不是一個(gè)有效數(shù)組或不是arguments對象,那么將導(dǎo)致一個(gè)TypeError,如果沒有提供argArray和thisObj任何一個(gè)參數(shù),那么Global對象將用作thisObj。
call()方法 第一個(gè)參數(shù)和apply()方法的一樣,但是傳遞給函數(shù)的參數(shù)必須列舉出來。
語法:call([thisObject[,arg1 [,arg2 [,…,argn]]]]);,應(yīng)用某一對象的一個(gè)方法,用另一個(gè)對象替換當(dāng)前對象。
說明: call方法可以用來代替另一個(gè)對象調(diào)用一個(gè)方法,call方法可以將一個(gè)函數(shù)的對象上下文從初始的上下文改變?yōu)閠hisObj指定的新對象,如果沒有提供thisObj參數(shù),那么Global對象被用于thisObj。
bind和call、apply最大的區(qū)別就是,call、apply不僅改變this的指向,還會直接支持代碼,而bind不會。
var cat = { name: "咪咪" } function beatTheMonster(){ console.log(this.name); } beatTheMonster.call(cat); // 1.call 改變了this的指向。改變到了cat上。 // 2.beatTheMonster函數(shù)/方法執(zhí)行了 // 3.bind(),保存了方法,并沒有直接調(diào)用它圖片預(yù)覽
function showPreview(source) { var file = source.files[0]; if(window.FileReader) { var fr = new FileReader(); fr.onloadend = function(e) { document.getElementById("portrait").src = e.target.result; }; fr.readAsDataURL(file); } }扁平化多維數(shù)組
var result = [] function unfold(arr){ for(var i=0;i< arr.length;i++){ if(typeof arr[i]=="object" && arr[i].length>1) { unfold(arr[i]); } else { result.push(arr[i]); } } } var arr = [1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]]; unfold(arr)
var c=[1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]]; var b = c.toString().split(",")
var arr=[1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]]; const flatten = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []); var result = flatten(arr)this的指向問題
參考鏈接:
歸納總結(jié)this的指向問題https://finget.github.io/2018/11/28/this/
ECMAScript規(guī)范解讀thishttps://github.com/mqyqingfeng/Blog/issues/7
function foo() { console.log(this.a) } var a = 1 foo() var obj = { a: 2, foo: foo } obj.foo() // 以上兩者情況 `this` 只依賴于調(diào)用函數(shù)前的對象,優(yōu)先級是第二個(gè)情況大于第一個(gè)情況 // 以下情況是優(yōu)先級最高的,`this` 只會綁定在 `c` 上,不會被任何方式修改 `this` 指向 var c = new foo() c.a = 3 console.log(c.a) // 還有種就是利用 call,apply,bind 改變 this,這個(gè)優(yōu)先級僅次于 new
箭頭函數(shù)中的this:
function a() { return () => { return () => { console.log(this) } } } console.log(a()()())
箭頭函數(shù)其實(shí)是沒有 this 的,這個(gè)函數(shù)中的 this 只取決于他外面的第一個(gè)不是箭頭函數(shù)的函數(shù)的 this。在這個(gè)例子中,因?yàn)檎{(diào)用 a 符合前面代碼中的第一個(gè)情況,所以 this 是 window。并且 this 一旦綁定了上下文,就不會被任何代碼改變。
async/await理解 JavaScript 的 async/awaithttps://segmentfault.com/a/1190000007535316
async function async1() { console.log( "async1 start") await async2() console.log( "async1 end") } async function async2() { console.log( "async2") } async1() console.log( "script start")
這里注意一點(diǎn),可能大家都知道await會讓出線程,阻塞后面的代碼,那么上面例子中, async2 和 script start 誰先打印呢?
是從左向右執(zhí)行,一旦碰到await直接跳出,阻塞 async2() 的執(zhí)行?
還是從右向左,先執(zhí)行async2后,發(fā)現(xiàn)有await關(guān)鍵字,于是讓出線程,阻塞代碼呢?
實(shí)踐的結(jié)論是,從右向左的。先打印async2,后打印的 script start。
之所以提一嘴,是因?yàn)槲医?jīng)常看到這樣的說法,「一旦遇到await就立刻讓出線程,阻塞后面的代碼」。
我的理解:callback是解決異步的早期方案,但是會導(dǎo)致‘回調(diào)地獄’,然后就出現(xiàn)了Promise,利用.then優(yōu)化了回調(diào)地獄的問題,而async/await是在promise 進(jìn)一步封裝,利用看似同步的方式解決異步問題。Promise和async/await都是語法糖。就是寫起來更簡單,閱讀性和維護(hù)性增強(qiáng)。
Promise 和 async/await在執(zhí)行時(shí)都干了什么,推薦看看:8 張圖幫你一步步看清 async/await 和 promise 的執(zhí)行順序
手寫實(shí)現(xiàn)promise直接粘貼大神的代碼:
// 三種狀態(tài) const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; // promise 接收一個(gè)函數(shù)參數(shù),該函數(shù)會立即執(zhí)行 function MyPromise(fn) { let _this = this; _this.currentState = PENDING; _this.value = undefined; // 用于保存 then 中的回調(diào),只有當(dāng) promise // 狀態(tài)為 pending 時(shí)才會緩存,并且每個(gè)實(shí)例至多緩存一個(gè) _this.resolvedCallbacks = []; _this.rejectedCallbacks = []; _this.resolve = function (value) { if (value instanceof MyPromise) { // 如果 value 是個(gè) Promise,遞歸執(zhí)行 return value.then(_this.resolve, _this.reject) } setTimeout(() => { // 異步執(zhí)行,保證執(zhí)行順序 if (_this.currentState === PENDING) { _this.currentState = RESOLVED; _this.value = value; _this.resolvedCallbacks.forEach(cb => cb()); } }) }; _this.reject = function (reason) { setTimeout(() => { // 異步執(zhí)行,保證執(zhí)行順序 if (_this.currentState === PENDING) { _this.currentState = REJECTED; _this.value = reason; _this.rejectedCallbacks.forEach(cb => cb()); } }) } // 用于解決以下問題 // new Promise(() => throw Error("error)) try { fn(_this.resolve, _this.reject); } catch (e) { _this.reject(e); } } MyPromise.prototype.then = function (onResolved, onRejected) { var self = this; // 規(guī)范 2.2.7,then 必須返回一個(gè)新的 promise var promise2; // 規(guī)范 2.2.onResolved 和 onRejected 都為可選參數(shù) // 如果類型不是函數(shù)需要忽略,同時(shí)也實(shí)現(xiàn)了透傳 // Promise.resolve(4).then().then((value) => console.log(value)) onResolved = typeof onResolved === "function" ? onResolved : v => v; onRejected = typeof onRejected === "function" ? onRejected : r => throw r; if (self.currentState === RESOLVED) { return (promise2 = new MyPromise(function (resolve, reject) { // 規(guī)范 2.2.4,保證 onFulfilled,onRjected 異步執(zhí)行 // 所以用了 setTimeout 包裹下 setTimeout(function () { try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === REJECTED) { return (promise2 = new MyPromise(function (resolve, reject) { setTimeout(function () { // 異步執(zhí)行onRejected try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === PENDING) { return (promise2 = new MyPromise(function (resolve, reject) { self.resolvedCallbacks.push(function () { // 考慮到可能會有報(bào)錯(cuò),所以使用 try/catch 包裹 try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); self.rejectedCallbacks.push(function () { try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); })); } }; // 規(guī)范 2.3 function resolutionProcedure(promise2, x, resolve, reject) { // 規(guī)范 2.3.1,x 不能和 promise2 相同,避免循環(huán)引用 if (promise2 === x) { return reject(new TypeError("Error")); } // 規(guī)范 2.3.2 // 如果 x 為 Promise,狀態(tài)為 pending 需要繼續(xù)等待否則執(zhí)行 if (x instanceof MyPromise) { if (x.currentState === PENDING) { x.then(function (value) { // 再次調(diào)用該函數(shù)是為了確認(rèn) x resolve 的 // 參數(shù)是什么類型,如果是基本類型就再次 resolve // 把值傳給下個(gè) then resolutionProcedure(promise2, value, resolve, reject); }, reject); } else { x.then(resolve, reject); } return; } // 規(guī)范 2.3.3.3.3 // reject 或者 resolve 其中一個(gè)執(zhí)行過得話,忽略其他的 let called = false; // 規(guī)范 2.3.3,判斷 x 是否為對象或者函數(shù) if (x !== null && (typeof x === "object" || typeof x === "function")) { // 規(guī)范 2.3.3.2,如果不能取出 then,就 reject try { // 規(guī)范 2.3.3.1 let then = x.then; // 如果 then 是函數(shù),調(diào)用 x.then if (typeof then === "function") { // 規(guī)范 2.3.3.3 then.call( x, y => { if (called) return; called = true; // 規(guī)范 2.3.3.3.1 resolutionProcedure(promise2, y, resolve, reject); }, e => { if (called) return; called = true; reject(e); } ); } else { // 規(guī)范 2.3.3.4 resolve(x); } } catch (e) { if (called) return; called = true; reject(e); } } else { // 規(guī)范 2.3.4,x 為基本類型 resolve(x); } }Promise.all實(shí)現(xiàn)原理
MyPromise.all = (arr) => { if (!Array.isArray(arr)) { throw new TypeError("參數(shù)應(yīng)該是一個(gè)數(shù)組!"); }; return new MyPromise(function(resolve, reject) { let i = 0, result = []; next(); function next() { //如果不是MyPromise對象,需要轉(zhuǎn)換 MyPromise.resolve(arr[i]).then(res => { result.push(res); i++; if (i === arr.length) { resolve(result); } else { next(); }; }, reject); }; }) };
參考鏈接:
原生es6封裝一個(gè)Promise對象
你是否在日常開發(fā)中遇到一個(gè)問題,在滾動(dòng)事件中需要做個(gè)復(fù)雜計(jì)算或者實(shí)現(xiàn)一個(gè)按鈕的防二次點(diǎn)擊操作。
這些需求都可以通過函數(shù)防抖動(dòng)來實(shí)現(xiàn)。尤其是第一個(gè)需求,如果在頻繁的事件回調(diào)中做復(fù)雜計(jì)算,很有可能導(dǎo)致頁面卡頓,不如將多次計(jì)算合并為一次計(jì)算,只在一個(gè)精確點(diǎn)做操作。
PS:防抖和節(jié)流的作用都是防止函數(shù)多次調(diào)用。區(qū)別在于,假設(shè)一個(gè)用戶一直觸發(fā)這個(gè)函數(shù),且每次觸發(fā)函數(shù)的間隔小于wait,防抖的情況下只會調(diào)用一次,而節(jié)流的 情況會每隔一定時(shí)間(參數(shù)wait)調(diào)用函數(shù)。
我們先來看一個(gè)袖珍版的防抖理解一下防抖的實(shí)現(xiàn):
// func是用戶傳入需要防抖的函數(shù) // wait是等待時(shí)間 const debounce = (func, wait = 50) => { // 緩存一個(gè)定時(shí)器id let timer = 0 // 這里返回的函數(shù)是每次用戶實(shí)際調(diào)用的防抖函數(shù) // 如果已經(jīng)設(shè)定過定時(shí)器了就清空上一次的定時(shí)器 // 開始一個(gè)新的定時(shí)器,延遲執(zhí)行用戶傳入的方法 return function(...args) { if (timer) clearTimeout(timer) timer = setTimeout(() => { func.apply(this, args) }, wait) } } // 不難看出如果用戶調(diào)用該函數(shù)的間隔小于wait的情況下,上一次的時(shí)間還未到就被清除了,并不會執(zhí)行函數(shù)
這是一個(gè)簡單版的防抖,但是有缺陷,這個(gè)防抖只能在最后調(diào)用。一般的防抖會有immediate選項(xiàng),表示是否立即調(diào)用。這兩者的區(qū)別,舉個(gè)栗子來說:
例如在搜索引擎搜索問題的時(shí)候,我們當(dāng)然是希望用戶輸入完最后一個(gè)字才調(diào)用查詢接口,這個(gè)時(shí)候適用延遲執(zhí)行的防抖函數(shù),它總是在一連串(間隔小于wait的)函數(shù)觸發(fā)之后調(diào)用。
例如用戶給interviewMap點(diǎn)star的時(shí)候,我們希望用戶點(diǎn)第一下的時(shí)候就去調(diào)用接口,并且成功之后改變star按鈕的樣子,用戶就可以立馬得到反饋是否star成功了,這個(gè)情況適用立即執(zhí)行的防抖函數(shù),它總是在第一次調(diào)用,并且下一次調(diào)用必須與前一次調(diào)用的時(shí)間間隔大于wait才會觸發(fā)。
// 這個(gè)是用來獲取當(dāng)前時(shí)間戳的 function now() { return +new Date() } /** * 防抖函數(shù),返回函數(shù)連續(xù)調(diào)用時(shí),空閑時(shí)間必須大于或等于 wait,func 才會執(zhí)行 * * @param {function} func 回調(diào)函數(shù) * @param {number} wait 表示時(shí)間窗口的間隔 * @param {boolean} immediate 設(shè)置為ture時(shí),是否立即調(diào)用函數(shù) * @return {function} 返回客戶調(diào)用函數(shù) */ function debounce (func, wait = 50, immediate = true) { let timer, context, args // 延遲執(zhí)行函數(shù) const later = () => setTimeout(() => { // 延遲函數(shù)執(zhí)行完畢,清空緩存的定時(shí)器序號 timer = null // 延遲執(zhí)行的情況下,函數(shù)會在延遲函數(shù)中執(zhí)行 // 使用到之前緩存的參數(shù)和上下文 if (!immediate) { func.apply(context, args) context = args = null } }, wait) // 這里返回的函數(shù)是每次實(shí)際調(diào)用的函數(shù) return function(...params) { // 如果沒有創(chuàng)建延遲執(zhí)行函數(shù)(later),就創(chuàng)建一個(gè) if (!timer) { timer = later() // 如果是立即執(zhí)行,調(diào)用函數(shù) // 否則緩存參數(shù)和調(diào)用上下文 if (immediate) { func.apply(this, params) } else { context = this args = params } // 如果已有延遲執(zhí)行函數(shù)(later),調(diào)用的時(shí)候清除原來的并重新設(shè)定一個(gè) // 這樣做延遲函數(shù)會重新計(jì)時(shí) } else { clearTimeout(timer) timer = later() } } }
節(jié)流:
/** * underscore 節(jié)流函數(shù),返回函數(shù)連續(xù)調(diào)用時(shí),func 執(zhí)行頻率限定為 次 / wait * * @param {function} func 回調(diào)函數(shù) * @param {number} wait 表示時(shí)間窗口的間隔 * @param {object} options 如果想忽略開始函數(shù)的的調(diào)用,傳入{leading: false}。 * 如果想忽略結(jié)尾函數(shù)的調(diào)用,傳入{trailing: false} * 兩者不能共存,否則函數(shù)不能執(zhí)行 * @return {function} 返回客戶調(diào)用函數(shù) */ _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; // 之前的時(shí)間戳 var previous = 0; // 如果 options 沒傳則設(shè)為空對象 if (!options) options = {}; // 定時(shí)器回調(diào)函數(shù) var later = function() { // 如果設(shè)置了 leading,就將 previous 設(shè)為 0 // 用于下面函數(shù)的第一個(gè) if 判斷 previous = options.leading === false ? 0 : _.now(); // 置空一是為了防止內(nèi)存泄漏,二是為了下面的定時(shí)器判斷 timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { // 獲得當(dāng)前時(shí)間戳 var now = _.now(); // 首次進(jìn)入前者肯定為 true // 如果需要第一次不執(zhí)行函數(shù) // 就將上次時(shí)間戳設(shè)為當(dāng)前的 // 這樣在接下來計(jì)算 remaining 的值時(shí)會大于0 if (!previous && options.leading === false) previous = now; // 計(jì)算剩余時(shí)間 var remaining = wait - (now - previous); context = this; args = arguments; // 如果當(dāng)前調(diào)用已經(jīng)大于上次調(diào)用時(shí)間 + wait // 或者用戶手動(dòng)調(diào)了時(shí)間 // 如果設(shè)置了 trailing,只會進(jìn)入這個(gè)條件 // 如果沒有設(shè)置 leading,那么第一次會進(jìn)入這個(gè)條件 // 還有一點(diǎn),你可能會覺得開啟了定時(shí)器那么應(yīng)該不會進(jìn)入這個(gè) if 條件了 // 其實(shí)還是會進(jìn)入的,因?yàn)槎〞r(shí)器的延時(shí) // 并不是準(zhǔn)確的時(shí)間,很可能你設(shè)置了2秒 // 但是他需要2.2秒才觸發(fā),這時(shí)候就會進(jìn)入這個(gè)條件 if (remaining <= 0 || remaining > wait) { // 如果存在定時(shí)器就清理掉否則會調(diào)用二次回調(diào) if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 判斷是否設(shè)置了定時(shí)器和 trailing // 沒有的話就開啟一個(gè)定時(shí)器 // 并且不能不能同時(shí)設(shè)置 leading 和 trailing timeout = setTimeout(later, remaining); } return result; }; };圖片懶加載與預(yù)加載
懶加載也就是延遲加載
原理:
頁面中的img元素,如果沒有src屬性,瀏覽器就不會發(fā)出請求去下載圖片,只有通過javascript設(shè)置了圖片路徑,瀏覽器才會發(fā)送請求。
懶加載的原理就是先在頁面中把所有的圖片統(tǒng)一使用一張占位圖進(jìn)行占位,把正真的路徑存在元素的“data-url”(這個(gè)名字起個(gè)自己認(rèn)識好記的就行)屬性里,要用的時(shí)候就取出來,再設(shè)置
// 懶加載 function loadImg(src){ let promise = new Promise(function (resolve, reject) { let img = document.createElement("img") img.onload = function () { resolve(img) } img.onerror = function () { reject("圖片加載失敗") } img.src = src }) return promise }
預(yù)加載 提前加載圖片,當(dāng)用戶需要查看時(shí)可直接從本地緩存中渲染
實(shí)現(xiàn)預(yù)加載的三種方法:
用CSS和JavaScript實(shí)現(xiàn)預(yù)加載
僅使用JavaScript實(shí)現(xiàn)預(yù)加載
使用Ajax實(shí)現(xiàn)預(yù)加載
用CSS和JavaScript實(shí)現(xiàn)預(yù)加載
#preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; } #preload-02 { background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px; } #preload-03 { background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px; }
將這三個(gè)ID選擇器應(yīng)用到(X)HTML元素中,我們便可通過CSS的background屬性將圖片預(yù)加載到屏幕外的背景上。只要這些圖片的路徑保持不變,當(dāng)它們在Web頁面的其他地方被調(diào)用時(shí),瀏覽器就會在渲染過程中使用預(yù)加載(緩存)的圖片。簡單、高效,不需要任何JavaScript。
該方法雖然高效,但仍有改進(jìn)余地。使用該法加載的圖片會同頁面的其他內(nèi)容一起加載,增加了頁面的整體加載時(shí)間。為了解決這個(gè)問題,我們增加了一些JavaScript代碼,來推遲預(yù)加載的時(shí)間,直到頁面加載完畢。代碼如下:
function preloader() { if (document.getElementById) { document.getElementById("preload-01").style.background = "url(http://domain.tld/image-01.png) no-repeat -9999px -9999px"; document.getElementById("preload-02").style.background = "url(http://domain.tld/image-02.png) no-repeat -9999px -9999px"; document.getElementById("preload-03").style.background = "url(http://domain.tld/image-03.png) no-repeat -9999px -9999px"; } } function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != "function") { window.onload = func; } else { window.onload = function() { if (oldonload) { oldonload(); } func(); } } } addLoadEvent(preloader);
僅使用JavaScript實(shí)現(xiàn)預(yù)加載
var images = new Array() function preload() { for (i = 0; i < preload.arguments.length; i++) { images[i] = new Image() images[i].src = preload.arguments[i] } } preload( "http://domain.tld/gallery/image-001.jpg", "http://domain.tld/gallery/image-002.jpg", "http://domain.tld/gallery/image-003.jpg" )
使用Ajax實(shí)現(xiàn)預(yù)加載
window.onload = function() { setTimeout(function() { // XHR to request a JS and a CSS var xhr = new XMLHttpRequest(); xhr.open("GET", "http://domain.tld/preload.js"); xhr.send(""); xhr = new XMLHttpRequest(); xhr.open("GET", "http://domain.tld/preload.css"); xhr.send(""); // preload image new Image().src = "http://domain.tld/preload.png"; }, 1000); };
上面代碼預(yù)加載了“preload.js”、“preload.css”和“preload.png”。1000毫秒的超時(shí)是為了防止腳本掛起,而導(dǎo)致正常頁面出現(xiàn)功能問題。
window.onload = function() { setTimeout(function() { // reference to var head = document.getElementsByTagName("head")[0]; // a new CSS var css = document.createElement("link"); css.type = "text/css"; css.rel = "stylesheet"; css.; // a new JS var js = document.createElement("script"); js.type = "text/javascript"; js.src = "http://domain.tld/preload.js"; // preload JS and CSS head.appendChild(css); head.appendChild(js); // preload image new Image().src = "http://domain.tld/preload.png"; }, 1000); };
這里,我們通過DOM創(chuàng)建三個(gè)元素來實(shí)現(xiàn)三個(gè)文件的預(yù)加載。正如上面提到的那樣,使用Ajax,加載文件不會應(yīng)用到加載頁面上。從這點(diǎn)上看,Ajax方法優(yōu)越于JavaScript。
參考鏈接:
Javascript圖片預(yù)加載詳解
借用babel工具可以學(xué)習(xí)一下,es6的class 編譯成es5時(shí),長什么樣
// ES6 class Person{ constructor(name,age){ this.name = name this.age = age } say() { console.log(this.name) } run() { console.log("run fast") } // 靜態(tài)方法,類調(diào)用 static getGirl(){ console.log("girl friend") } }
// ES5 var _createClass = function() { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; // 枚舉 descriptor.enumerable = descriptor.enumerable || false; // 可配置 descriptor.configurable = true; if ("value" in descriptor) // 可寫 descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // 禁止 直接調(diào)用 Person() function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Person = function () { function Person(name, age) { _classCallCheck(this, Person); this.name = name; this.age = age; } _createClass(Person, [{ key: "say", value: function say() { console.log(this.name); } }, { key: "run", value: function run() { console.log("run fast"); } }], [{ key: "getGirl", value: function getGirl() { console.log("girl friend"); } }]); return Person; }();
關(guān)于對象的enumerable、writable、configurable,可以看看Javascript properties are enumerable, writable and configurable
JavaScript的sort方法內(nèi)部使用的什么排序默認(rèn)排序順序是根據(jù)字符串Unicode碼點(diǎn)
函數(shù)式編程函數(shù)式編程的本質(zhì),函數(shù)式編程中的函數(shù)這個(gè)術(shù)語不是指計(jì)算機(jī)中的函數(shù),而是指數(shù)學(xué)中的函數(shù),即自變量的映射。也就是說一個(gè)函數(shù)的值僅決定于函數(shù)參數(shù)的值,不依賴其他狀態(tài)。比如sqrt(x)函數(shù)計(jì)算x的平方根,只要x不變,無論什么時(shí)候調(diào)用,調(diào)用幾次,值都是不變的。
函數(shù)式的最主要的好處是不可變性帶來的。沒有可變的狀態(tài),函數(shù)就是引用透明的沒有副作用。函數(shù)即不依賴外部的狀態(tài)也不修改外部的狀態(tài),函數(shù)調(diào)用的結(jié)果不依賴調(diào)用的時(shí)間和位置,這樣寫的代碼容易進(jìn)行推理,不容易出錯(cuò)。這使得單元測試和調(diào)試更容易。
參考鏈接:
js函數(shù)式編程指南
回調(diào)地獄、代碼的可閱讀性和可維護(hù)性降低
如何實(shí)現(xiàn)一個(gè)可設(shè)置過期時(shí)間的localStorage直接上鏈接:如何給localStorage設(shè)置一個(gè)過期時(shí)間?
用JavaScript的異步實(shí)現(xiàn)sleep函數(shù)async function test() { console.log("Hello") let res = await sleep(1000) console.log(res) } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } test()
參考鏈接:
JavaScript的sleep實(shí)現(xiàn)--Javascript異步編程學(xué)習(xí)
window._pt_lt = new Date().getTime(); window._pt_sp_2 = []; _pt_sp_2.push("setAccount,2953009d"); var _protocol = (("https:" == document.location.protocol) ? " https://" : " http://"); (function() { var atag = document.createElement("script"); atag.type = "text/javascript"; atag.async = true; atag.src = _protocol + "js.ptengine.cn/2953009d.js"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(atag, s); })();淺拷貝和深拷貝的區(qū)別
淺拷貝只是對指針的拷貝,拷貝后兩個(gè)指針指向同一個(gè)內(nèi)存空間,深拷貝不但對指針進(jìn)行拷貝,而且對指針指向的內(nèi)容進(jìn)行拷貝,經(jīng)深拷貝后的指針是指向兩個(gè)不同地址的指針。
參考鏈接:
js淺拷貝和深拷貝
推薦在循環(huán)對象屬性的時(shí)候,使用for...in,在遍歷數(shù)組的時(shí)候的時(shí)候使用for...of。
for…in…遍歷對象會遍歷出對象的所有可枚舉的屬性
for...in循環(huán)出的是key,for...of循環(huán)出的是value
注意,for...of是ES6新引入的特性。修復(fù)了ES5引入的for...in的不足
for...of不能循環(huán)普通的對象,需要通過和Object.keys()搭配使用
cookie和localStorage的區(qū)別特性 | cookie | sessionStorage | localStorage |
---|---|---|---|
數(shù)據(jù)生命期 | 生成時(shí)就會被指定一個(gè)maxAge值,這就是cookie的生存周期,在這個(gè)周期內(nèi)cookie有效,默認(rèn)關(guān)閉瀏覽器失效 | 頁面會話期間可用 | 除非數(shù)據(jù)被清除,否則一直存在 |
存放數(shù)據(jù)大小 | 4K左右(因?yàn)槊看蝖ttp請求都會攜帶cookie) | 一般5M或更大 | |
與服務(wù)器通信 | 由對服務(wù)器的請求來傳遞,每次都會攜帶在HTTP頭中,如果使用cookie保存過多數(shù)據(jù)會帶來性能問題 | 數(shù)據(jù)不是由每個(gè)服務(wù)器請求傳遞的,而是只有在請求時(shí)使用數(shù)據(jù),不參與和服務(wù)器的通信 | |
易用性 | cookie需要自己封裝setCookie,getCookie | 可以用源生接口,也可再次封裝來對Object和Array有更好的支持 | |
共同點(diǎn) | 都是保存在瀏覽器端,和服務(wù)器端的session機(jī)制不同 |
時(shí)間一樣。引用類型的變量都是堆內(nèi)存。堆內(nèi)存就像書架一樣,只要你知道書名,就能直接找到對應(yīng)的書。
內(nèi)存空間var a = {b: 1} 存放在哪里?
var a = {b: {c: 1}}存放在哪里?
var a = {name: "前端開發(fā)"}; var b = a; a = null, 那么b輸出什么?
js變量可以用來保存兩種類型的值:基本類型值和引用類型值。在ES6之前共有6種數(shù)據(jù)類型:Undefined、Null、Boolean、Number,String和Object,其中前5種是基本類型值。
基本類型值在內(nèi)存中占據(jù)固定大小的空間,因此被保存在棧內(nèi)存中。
從一個(gè)變量向另一個(gè)變量復(fù)制基本類型的值,會創(chuàng)建這個(gè)值的一個(gè)副本。
引用類型的值是對象,保存在堆內(nèi)存中。
包含引用類型值的變量實(shí)際上包含的并不是對象本身,而是一個(gè)指向該對象的指針
理解隊(duì)列數(shù)據(jù)結(jié)構(gòu)的目的主要是為了清晰的明白事件循環(huán)(Event Loop)的機(jī)制到底是怎么回事。
jquery $(document).ready() 與window.onload的區(qū)別1.執(zhí)行時(shí)間
window.onload必須等到頁面內(nèi)包括圖片的所有元素加載完畢后才能執(zhí)行。
$(document).ready()是DOM結(jié)構(gòu)繪制完畢后就執(zhí)行,不必等到加載完畢。
2.編寫個(gè)數(shù)不同
window.onload不能同時(shí)編寫多個(gè),如果有多個(gè)window.onload方法,只會執(zhí)行一個(gè)
$(document).ready()可以同時(shí)編寫多個(gè),并且都可以得到執(zhí)行
3.簡化寫法
window.onload沒有簡化寫法一個(gè)是數(shù)組中所有數(shù)都出現(xiàn)了兩次,只有一個(gè)元素只出現(xiàn)了一次,找出這個(gè)數(shù)
$(document).ready(function(){})可以簡寫成$(function(){});
let arr = [1,1,3,4,3,5,6,8,6,5,8] function get() { let num = 0; arr.forEach(item => { num = num^item // 異或運(yùn)算 }) console.log(num) } get()
js異或運(yùn)算符^小技巧
Vue Vue 生命周期1.beforcreate
2.created
3.beformount
4.mounted
5.beforeUpdate
6.updated
7.actived
8.deatived
9.beforeDestroy
10.destroyed
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/101051.html
摘要:一些知識點(diǎn)有哪些方法方法前端從入門菜鳥到實(shí)踐老司機(jī)所需要的資料與指南合集前端掘金前端從入門菜鳥到實(shí)踐老司機(jī)所需要的資料與指南合集歸屬于筆者的前端入門與最佳實(shí)踐。 工欲善其事必先利其器-前端實(shí)習(xí)簡歷篇 - 掘金 有幸認(rèn)識很多在大廠工作的學(xué)長,在春招正式開始前為我提供很多內(nèi)部推薦的機(jī)會,非常感謝他們對我的幫助。現(xiàn)在就要去北京了,對第一份正式的實(shí)習(xí)工作也充滿期待,也希望把自己遇到的一些問題和...
摘要:最近的一次更新的變量有效,并且會作用于全部的引用的處理方式和相同,變量值輸出時(shí)根據(jù)之前最近的一次定義計(jì)算,每次引用最近的定義有效嵌套三種預(yù)編譯器的選擇器嵌套在使用上來說沒有任何區(qū)別,甚至連引用父級選擇器的標(biāo)記也相同。 面試匯總一:2018大廠高級前端面試題匯總 高級面試:【半月刊】前端高頻面試題及答案匯總 css內(nèi)容 響應(yīng)式布局 當(dāng)前主流的三種預(yù)編譯器比較 CSS預(yù)處理器用一種專門的...
摘要:可以在該鉤子中進(jìn)一步地更改狀態(tài),不會觸發(fā)附加的重渲染過程。我工作中只用到,對和不怎么熟與的區(qū)別相同點(diǎn)都支持指令內(nèi)置指令和自定義指令都支持過濾器內(nèi)置過濾器和自定義過濾器都支持雙向數(shù)據(jù)綁定都不支持低端瀏覽器。 看看面試題,只是為了查漏補(bǔ)缺,看看自己那些方面還不懂。切記不要以為背了面試題,就萬事大吉了,最好是理解背后的原理,這樣面試的時(shí)候才能侃侃而談。不然,稍微有水平的面試官一看就能看出,是...
摘要:前記為了準(zhǔn)備春招面試,對自己的知識點(diǎn)進(jìn)行一個(gè)總結(jié)積累,第一篇是關(guān)于方面的知識點(diǎn),后續(xù)如果遇見新題會進(jìn)行繼續(xù)的補(bǔ)充什么是語義化,有什么好處語義化簡單來說就是,段落使用,側(cè)邊欄用,主要內(nèi)容使用。不存在或形式不正確會導(dǎo)致文檔以混雜模式呈現(xiàn)。 前記 為了準(zhǔn)備春招面試,對自己的知識點(diǎn)進(jìn)行一個(gè)總結(jié)積累,第一篇是關(guān)于HTML方面的知識點(diǎn),后續(xù)如果遇見新題會進(jìn)行繼續(xù)的補(bǔ)充 什么是 HTML 語義化,有...
摘要:前言一直混跡社區(qū)突然發(fā)現(xiàn)自己收藏了不少好文但是管理起來有點(diǎn)混亂所以將前端主流技術(shù)做了一個(gè)書簽整理不求最多最全但求最實(shí)用。 前言 一直混跡社區(qū),突然發(fā)現(xiàn)自己收藏了不少好文但是管理起來有點(diǎn)混亂; 所以將前端主流技術(shù)做了一個(gè)書簽整理,不求最多最全,但求最實(shí)用。 書簽源碼 書簽導(dǎo)入瀏覽器效果截圖showImg(https://segmentfault.com/img/bVbg41b?w=107...
閱讀 1305·2021-10-08 10:05
閱讀 4127·2021-09-22 15:54
閱讀 3113·2021-08-27 16:18
閱讀 3112·2019-08-30 15:55
閱讀 1445·2019-08-29 12:54
閱讀 2754·2019-08-26 11:42
閱讀 550·2019-08-26 11:39
閱讀 2135·2019-08-26 10:11