網上找到的各類面試題整理,長期更新。大部分答案整理來自網絡,有問題的地方,但願你們能指出,及時修改;技術更新迭代,也會及時更新
博客原地址: https://finget.github.io/2019...
1.清理 HTML 文檔javascript
HTML,即超文本標記語言,幾乎是全部網站的支柱。HTML 爲網頁帶來標題、子標題、列表和其它一些文檔結構的格式。在最近更新的 HTML5 中,甚至能夠建立圖表。css
HTML 很容易被網絡爬蟲識別,所以搜索引擎能夠根據網站的內容在必定程度上實時更新。在寫 HTML 的時候,你應該嘗試讓它簡潔而有效。此外,在 HTML 文檔中引用外部資源的時候也須要遵循一些最佳實踐方法。html
a.恰當放置 CSS前端
Web 設計者喜歡在網頁創建起主要的 HTML 骨架以後再來建立樣式表。這樣一來,網頁中的樣式表每每會放在 HTML 的後面,接近文檔結束的地方。然而推薦的作法是把 CSS 放在 HTML 的上面部分,文檔頭以內,這能夠確保正常的渲染過程。vue
這個策略不能提升網站的加載速度,但它不會讓訪問者長時間看着空白屏幕或者無格式的文本(FOUT)等待。若是網頁大部分可見元素已經加載出來了,訪問者才更有可能等待加載整個頁面,從而帶來對前端的優化效果。這就是知覺性能html5
b.正確放置 Javascriptjava
另外一方面,若是將 JavaScript 放置在 head 標籤內或 HTML 文檔的上部,這會阻塞 HTML 和 CSS 元素的加載過程。這個錯誤會致使頁面加載時間增加,增長用戶等待時間,容易讓人感到不耐煩而放棄對網站的訪問。不過,您能夠經過將 JavaScript 屬性置於 HTML 底部來避免此問題。node
此外,在使用 JavaScript 時,人們一般喜歡用異步腳本加載。這會阻止<script>
標籤在 HTML 中的呈現過程,如,在文檔中間的狀況。react
雖然對於網頁設計師來講, HTML 是最值得使用的工具之一,但它一般要與 CSS 和 JavaScript 一塊兒使用,這可能會致使網頁瀏覽速度減慢。 雖然 CSS 和 JavaScript 有利於網頁優化,但使用時也要注意一些問題。使用 CSS 和 JavaScript 時,要避免嵌入代碼。由於當您嵌入代碼時,要將 CSS 放置在樣式標記中,並在腳本標記中使用 JavaScript,這會增長每次刷新網頁時必須加載的 HTML 代碼量。jquery
2.優化 CSS 性能
CSS,即級聯樣式表,能從 HTML 描述的內容生成專業而又整潔的文件。不少 CSS 須要經過 HTTP 請求來引入(除非使用內聯 CSS),因此你要努力去除累贅的 CSS 文件,但要注意保留其重要特徵。
若是你的 Banner、插件和佈局樣式是使用 CSS 保存在不一樣的文件內,那麼,訪問者的瀏覽器每次訪問都會加載不少文件。雖然如今 HTTP/2 的存在,減小了這種問題的發生,可是在外部資源加載的狀況下,仍會花費較長時間。要了解如何減小 HTTP 請求以大幅度縮減加載時間,請閱讀WordPress 性能。
此外,很多網站管理員在網頁中錯誤的使用 @import 指令 來引入外部樣式表。這是一個過期的方法,它會阻止瀏覽並行下載。link 標籤纔是最好的選擇,它也能提升網站的前端性能。多說一句,經過 link 標籤請求加載的外部樣式表不會阻止並行下載。
3.減小外部HTTP請求
在不少狀況下,網站的大部分加載時間來自於外部的 Http 請求。外部資源的加載速度隨着主機提供商的服務器架構、地點等不一樣而不一樣。減小外部請求要作的第一步就是簡略地檢查網站。研究你網站的每一個組成部分,消除任何影響訪問者體驗很差的成分。這些成分多是:
在你去掉這些多餘的成分以後,再對剩下的內容進行整理,如,壓縮工具、CDN 服務和預獲取(prefetching)等,這些都是管理 HTTP 請求的最佳選擇。除此以外,減小DNS路由查找教程會教你如何一步一步的減小外部 HTTP 請求。
4.壓縮 CSS, JS 和 HTML
壓縮技術能夠從文件中去掉多餘的字符。你在編輯器中寫代碼的時候,會使用縮進和註釋,這些方法無疑會讓你的代碼簡潔並且易讀,但它們也會在文檔中添加多餘的字節。
預先獲取能夠在真正須要以前經過取得必需的資源和相關數據來改善訪問用戶的瀏覽體驗,主要有3類預先獲取:
在你離開當前 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 代碼。
參考連接:
詳細解讀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
在實現websocket連線過程當中,須要經過瀏覽器發出websocket連線請求,而後服務器發出迴應,這個過程一般稱爲「握手」 (handshaking)。
客戶端請求web socket鏈接時,會向服務器端發送握手請求
請求頭大體內容:
請求包說明:
服務端響應以下:
應答包說明:
*必須包括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 請求不能發送
跨域解決方案
最容易想到的解決方案是:
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爲永久保存。
Http 2.0協議簡介
HTTP 2.0 詳細介紹,http2.0詳細介紹
HTTP/2.0 相比1.0有哪些重大改進
我能想到的只有Promise.all()
,歡迎補充
<b>
粗體文本,<strong>
用於強調文本,他們的樣式是同樣的
有一種說法,是<strong>
貌似在盲人用的機器上會讀兩遍。由於沒有對應的測試條件,因此沒作驗證。
header('Access-Control-Allow-Origin:*');
CSRF,全稱爲Cross-Site Request Forgery,跨站請求僞造,是一種網絡攻擊方式,它能夠在用戶絕不知情的狀況下,以用戶的名義僞造請求發送給被攻擊站點,從而在未受權的狀況下進行權限保護內的操做。
具體來說,能夠這樣理解CSRF。攻擊者借用用戶的名義,向某一服務器發送惡意請求,對服務器來說,這一請求是徹底合法的,但攻擊者確完成了一個惡意操做,好比以用戶的名義發送郵件,盜取帳號,購買商品等等
通常網站防護CSRF攻擊的方案:
(1)驗證token值。
(2)驗證HTTP頭的Referer。
(3)在HTTP頭中自定義屬性並驗證
(4)服務器端表單hash認證
在全部的表單裏面隨機生成一個hash,server在表單處理時去驗證這個hash值是否正確,這樣工做量比較大
// 第一種 .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 }
當給父元素設置"overflow:hidden"時,實際上建立了一個超級屬性BFC,此超級屬性反過來決定了"height:auto"是如何計算的。在「BFC佈局規則」中提到:計算BFC的高度時,浮動元素也參與計算。所以,父元素在計算其高度時,加入了浮動元素的高度,「順便」達成了清除浮動的目標,因此父元素就包裹住了子元素。
BFC(Block Formatting Context),塊級格式化上下文,是Web頁面中盒模型佈局的CSS渲染模式。它的定位體系屬於常規文檔流。
原理(渲染規則):
<!-- 邊距重疊 --> <section id="margin"> <style> #margin{ background: green; overflow: hidden; } #margin p{ background: red; margin: 10px 0; } </style> <p>1</p> <!-- 在增長一個BFC父級就能夠消除邊距重疊 --> <div style="overflow: hidden;"> <p>2</p> </div> <p>3</p> </section>
<!-- 左右佈局 BFC的區域不會與浮動元素的box重疊--> <section id="layout"> <style> #layout .left{ float: left; width: 200px; height: 300px; background: pink; } #layout .right{ height: 500px; background: yellow; overflow: hidden; } </style> <div class="left"></div> <div class="right"></div> </section>
<!-- 計算BFC高度時,浮動元素也會參與計算 清除浮動 --> <section id="float"> <style> #float{ background: red; overflow: auto; } #float .float-left{ float: left; } </style> <div class="float-left">我是浮動元素</div> </section>
怎麼建立BFC:
浮動,絕對定位元素,inline-blocks, table-cells, table-captions,和overflow的值不爲visible的元素,(除了這個值已經被傳到了視口的時候)將建立一個新的塊級格式化上下文。
上面的引述幾乎總結了一個BFC是怎樣造成的。可是讓咱們以另外一種方式來從新定義以便能更好的去理解.
參考連接:
理解CSS中BFC
這個直接看 阮一峯:Flex 佈局教程
單詞sticky的中文意思是「粘性的」,position:sticky表現也符合這個粘性的表現。基本上,能夠看出是position:relative和position:fixed的結合體——當元素在屏幕內,表現爲relative,就要滾出顯示器屏幕的時候,表現爲fixed。
詳細講解的仍是看大神的吧,張鑫旭:position:sticky
原型與原型鏈,做用域及閉包,異步和單線程。
三座大山,真不是一兩句能夠說清楚的,只有靠你們多看,多用,多理解,放點連接吧。
原型,原型鏈,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
事件循環機制
// setTimeout中的回調函數纔是進入任務隊列的任務 setTimeout(function() { console.log('xxxx'); }) // 很是多的同窗對於setTimeout的理解存在誤差。因此大概說一下誤解: // setTimeout做爲一個任務分發器,這個函數會當即執行,而它所要分發的任務,也就是它的第一個參數,纔是延遲執行
promise裏面的是宏任務,then後面的是微任務。
這個問題本質就是爲啥須要異步。若是js不是異步的話,因爲js代碼自己是自上而下執行的,那麼若是上一行代碼須要執行好久,下面的代碼就會被阻塞,對用戶來講,就是」卡死」,這樣的話,會形成不好的用戶體驗。
你可能知道,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); }
f1().then(f2).then(f3);
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。
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(),保存了方法,並無直接調用它
<input type="file" name="file" onchange="showPreview(this)" /> <img id="portrait" src="" width="70" height="75"> 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的指向問題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 一旦綁定了上下文,就不會被任何代碼改變。
理解 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 的執行順序
直接粘貼大神的代碼:
// 三種狀態 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); } }
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的)函數觸發以後調用。當即執行
的防抖函數,它老是在第一次調用,而且下一次調用必須與前一次調用的時間間隔大於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 }
預加載 提早加載圖片,當用戶須要查看時可直接從本地緩存中渲染
實現預加載的三種方法:
#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);
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" )
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 <head> var head = document.getElementsByTagName('head')[0]; // a new CSS var css = document.createElement('link'); css.type = "text/css"; css.rel = "stylesheet"; css.href = "http://domain.tld/preload.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
默認排序順序是根據字符串Unicode碼點
函數式編程的本質,函數式編程中的函數這個術語不是指計算機中的函數,而是指數學中的函數,即自變量的映射。也就是說一個函數的值僅決定於函數參數的值,不依賴其餘狀態。好比sqrt(x)函數計算x的平方根,只要x不變,不管何時調用,調用幾回,值都是不變的。
函數式的最主要的好處是不可變性帶來的。沒有可變的狀態,函數就是引用透明的沒有反作用。函數即不依賴外部的狀態也不修改外部的狀態,函數調用的結果不依賴調用的時間和位置,這樣寫的代碼容易進行推理,不容易出錯。這使得單元測試和調試更容易。
參考連接:
js函數式編程指南
回調地獄、代碼的可閱讀性和可維護性下降
直接上連接:如何給localStorage設置一個過時時間?
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淺拷貝和深拷貝
特性 | 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種是基本類型值。
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()
1.beforcreate
2.created
3.beformount
4.mounted
5.beforeUpdate
6.updated
7.actived
8.deatived
9.beforeDestroy
10.destroyed
JavaScript從初級往高級走系列————Virtual Dom
Vue3基於Proxy 的新數據監聽系統,全語音特性支持 + 更好的性能
Vue2.x用的是基於ES5的getter/setter,也就是Object.defineProperty這個API。
每一個vue 組件都會代理它所包含的 data、props、computed,這些代理都是經過Object.defineProperty實現的,大量的Object.defineProperty是很大的性能消耗
利用Proxy減小組件實例初始化開銷,暴露給用戶的這個this,實際上是一個真正的組件實例的一個Proxy
基於Proxy的監聽是所謂的Lazy by default,只有當一個數據被用到的時候纔會監聽
原理簡述:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>雙向綁定</title> </head> <body> 手寫一個簡單雙向綁定<br/> <input type="text" id="model"><br/> <div id="modelText"></div> </body> <script> var model = document.querySelector("#model"); var modelText = document.querySelector("#modelText"); var defaultName = "defaultName"; var userInfo = {} model.value = defaultName; Object.defineProperty(userInfo, "name", { get: function () { return defaultName; }, set: function (value) { defaultName = value; model.value = value; console.log("-----value"); console.log(value); modelText.textContent = value; } }) userInfo.name = "new value"; var isEnd = true; model.addEventListener("keyup", function () { if (isEnd) { userInfo.name = this.value; } }, false) //加入監聽中文輸入事件 model.addEventListener("compositionstart", function () { console.log("開始輸入中文"); isEnd = false; }) model.addEventListener("compositionend", function () { isEnd = true; console.log("結束輸入中文"); }) </script> </html>
參考連接:
前端路由簡介以及vue-router實現原理
【源碼拾遺】從vue-router看前端路由的兩種實現
淺談vue-router原理
<router-link>
組件支持用戶在具備路由功能的應用中 (點擊) 導航。 經過 to 屬性指定目標地址,默認渲染成帶有正確連接的 <a>
標籤,能夠經過配置 tag 屬性生成別的標籤.。另外,當目標路由成功激活時,連接元素自動設置一個表示激活的 CSS 類名。
<router-link>
比起寫死的 <a href="...">
會好一些,理由以下:
router-link
會守衛點擊事件,讓瀏覽器再也不從新加載頁面。base
選項以後,全部的 to
屬性都不須要寫 (基路徑) 了。參考連接:
react-router從Link組件和a標籤的區別提及
對於非UI控件來講,不存在雙向,只有單向。只有UI控件纔有雙向的問題。
getDefaultProps
getInitialState
componentWillMount
render
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
在代碼中調用setState函數以後,React 會將傳入的參數對象與組件當前的狀態合併,而後觸發所謂的調和過程(Reconciliation)。通過調和過程,React 會以相對高效的方式根據新的狀態構建 React 元素樹而且着手從新渲染整個UI界面。在 React 獲得元素樹以後,React 會自動計算出新的樹與老樹的節點差別,而後根據差別對界面進行最小化重渲染。在差別計算算法中,React 可以相對精確地知道哪些位置發生了改變以及應該如何改變,這就保證了按需更新,而不是所有從新渲染。
簡單而言,React Element 是描述屏幕上所見內容的數據結構,是對於 UI 的對象表述。典型的 React Element 就是利用 JSX 構建的聲明式代碼片而後被轉化爲createElement的調用組合。而 React Component 則是能夠接收參數輸入而且返回某個React Element的函數或者類。更多介紹能夠參考React Elements vs React Components。
在組件須要包含內部狀態或者使用到生命週期函數的時候使用 Class Component ,不然使用函數式組件。
Refs 是 React 提供給咱們的安全訪問 DOM 元素或者某個組件實例的句柄。咱們能夠爲元素添加ref屬性而後在回調函數中接受該元素在 DOM 樹中的句柄,該值會做爲回調函數的第一個參數返回:
class CustomForm extends Component { handleSubmit = () => { console.log("Input Value: ", this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' ref={(input) => this.input = input} /> <button type='submit'>Submit</button> </form> ) } }
上述代碼中的input域包含了一個ref屬性,該屬性聲明的回調函數會接收input對應的 DOM 元素,咱們將其綁定到this指針以便在其餘的類函數中使用。另外值得一提的是,refs 並非類組件的專屬,函數式組件一樣可以利用閉包暫存其值:
function CustomForm ({handleSubmit}) { let inputElement return ( <form onSubmit={() => handleSubmit(inputElement.value)}> <input type='text' ref={(input) => inputElement = input} /> <button type='submit'>Submit</button> </form> ) }
Keys 是 React 用於追蹤哪些列表中元素被修改、被添加或者被移除的輔助標識。
render () { return ( <ul> {this.state.todoItems.map(({task, uid}) => { return <li key={uid}>{task}</li> })} </ul> ) }
在開發過程當中,咱們須要保證某個元素的 key 在其同級元素中具備惟一性。在 React Diff 算法中 React 會藉助元素的 Key 值來判斷該元素是新近建立的仍是被移動而來的元素,從而減小沒必要要的元素重渲染。此外,React 還須要藉助 Key 值來判斷元素與本地狀態的關聯關係,所以咱們毫不可忽視轉換函數中 Key 的重要性。
React 的核心組成之一就是可以維持內部狀態的自治組件,不過當咱們引入原生的HTML表單元素時(input,select,textarea 等),咱們是否應該將全部的數據託管到 React 組件中仍是將其仍然保留在 DOM 元素中呢?這個問題的答案就是受控組件與非受控組件的定義分割。受控組件(Controlled Component)代指那些交由 React 控制而且全部的表單數據統一存放的組件。譬以下面這段代碼中username變量值並無存放到DOM元素中,而是存放在組件狀態數據中。任什麼時候候咱們須要改變username變量值時,咱們應當調用setState函數進行修改。
class ControlledForm extends Component { state = { username: '' } updateUsername = (e) => { this.setState({ username: e.target.value, }) } handleSubmit = () => {} render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' value={this.state.username} onChange={this.updateUsername} /> <button type='submit'>Submit</button> </form> ) } }
而非受控組件(Uncontrolled Component)則是由DOM存放表單數據,並不是存放在 React 組件中。咱們能夠使用 refs 來操控DOM元素:
class UnControlledForm extends Component { handleSubmit = () => { console.log("Input Value: ", this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' ref={(input) => this.input = input} /> <button type='submit'>Submit</button> </form> ) } }
居然非受控組件看上去更好實現,咱們能夠直接從 DOM 中抓取數據,而不須要添加額外的代碼。不過實際開發中咱們並不提倡使用非受控組件,由於實際狀況下咱們須要更多的考慮表單驗證、選擇性的開啓或者關閉按鈕點擊、強制輸入格式等功能支持,而此時咱們將數據託管到 React 中有助於咱們更好地以聲明式的方式完成這些功能。引入 React 或者其餘 MVVM 框架最初的緣由就是爲了將咱們從繁重的直接操做 DOM 中解放出來。
咱們應當將AJAX 請求放到 componentDidMount 函數中執行,主要緣由有下:
shouldComponentUpdate容許咱們手動地判斷是否要進行組件更新,根據組件的應用場景設置函數的合理返回值可以幫咱們避免沒必要要的更新。
一般狀況下咱們會使用 Webpack 的 DefinePlugin 方法來將 NODE_ENV 變量值設置爲 production。編譯版本中 React 會忽略 propType 驗證以及其餘的告警信息,同時還會下降代碼庫的大小,React 使用了 Uglify 插件來移除生產環境下沒必要要的註釋等信息。
props.children並不必定是數組類型,譬以下面這個元素:
<Parent> <h1>Welcome.</h1> </Parent>
爲了解決跨瀏覽器兼容性問題,React 會將瀏覽器原生事件(Browser Native Event)封裝爲合成事件(SyntheticEvent)傳入設置的事件處理器中。這裏的合成事件提供了與原生事件相同的接口,不過它們屏蔽了底層瀏覽器的細節差別,保證了行爲的一致性。另外有意思的是,React 並無直接將事件附着到子元素上,而是以單一事件監聽器的方式將全部的事件發送到頂層進行處理。這樣 React 在更新 DOM 的時候就不須要考慮如何去處理附着在 DOM 上的事件監聽器,最終達到優化性能的目的。
createElement 函數是 JSX 編譯以後使用的建立 React Element 的函數,而 cloneElement 則是用於複製某個元素並傳入新的 Props。
該函數會在setState函數調用完成而且組件開始重渲染的時候被調用,咱們能夠用該函數來監聽渲染是否完成:
this.setState( { username: 'tylermcginnis33' }, () => console.log('setState has finished and the component has re-rendered.') )
this.setState((prevState, props) => { return { streak: prevState.streak + props.count } })
這段代碼沒啥問題,不過只是不太經常使用罷了,詳細能夠參考React中setState同步更新策略
參考連接:
參考連接:
Express框架詳解
深刻理解express框架
express框架的簡單實現
阮一峯:JavaScript 運行機制詳解:再談Event Loop
建立了一個前端學習交流羣,感興趣的朋友,一塊兒來嗨呀!