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