国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

長期維護(hù)更新,前端面試題整理

Xufc / 3147人閱讀

摘要:網(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í),人們通常喜歡用異步腳本加載。這會阻止

壓縮技術(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 到底是啥

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 - 定義文檔/資源的名稱

講tcp/ip網(wǎng)絡(luò)層、三次握手,為什么不能兩次握手
客服端和服務(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有那些缺點(diǎn)?

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)

通過什么做到并發(fā)請求

我能想到的只有Promise.all(),歡迎補(bǔ)充

b和strong的區(qū)別

粗體文本, 用于強(qiáng)調(diào)文本,他們的樣式是一樣的
有一種說法,是貌似在盲人用的機(jī)器上會讀兩遍。因?yàn)闆]有對應(yīng)的測試條件,所以沒做驗(yàn)證。

Access-Control-Allow-Origin在服務(wù)端哪里配置

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

2

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

講flex,手寫出flex常用的屬性,并且講出作用

這個(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會讓出線程,阻塞后面的代碼,那么上面例子中, async2script start 誰先打印呢?
是從左向右執(zhí)行,一旦碰到await直接跳出,阻塞 async2() 的執(zhí)行?
還是從右向左,先執(zhí)行async2后,發(fā)現(xiàn)有await關(guān)鍵字,于是讓出線程,阻塞代碼呢?
實(shí)踐的結(jié)論是,從右向左的。先打印async2,后打印的 script start。
之所以提一嘴,是因?yàn)槲医?jīng)常看到這樣的說法,「一旦遇到await就立刻讓出線程,阻塞后面的代碼」。

Promise 和 async/await 和 callback的區(qū)別

我的理解: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對象

手寫函數(shù)防抖和函數(shù)節(jié)流

你是否在日常開發(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ù)加載詳解

使用es5實(shí)現(xiàn)es6的class

借用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)于對象的enumerablewritableconfigurable,可以看看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)函數(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í)

手寫實(shí)現(xiàn)jsonp
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淺拷貝和深拷貝

for..in 和 for..of 的區(qū)別

推薦在循環(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ī)制不同
JS執(zhí)行過程中分為哪些階段

數(shù)組里面有10萬個(gè)數(shù)據(jù),取第一個(gè)元素和第10萬個(gè)元素的時(shí)間相差多少

時(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沒有簡化寫法 
$(document).ready(function(){})可以簡寫成$(function(){});
一個(gè)是數(shù)組中所有數(shù)都出現(xiàn)了兩次,只有一個(gè)元素只出現(xiàn)了一次,找出這個(gè)數(shù)
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

vue里面的虛擬dom是怎么回

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/101051.html

相關(guān)文章

  • 前端開發(fā)-從入門到Offer - 收藏集 - 掘金

    摘要:一些知識點(diǎn)有哪些方法方法前端從入門菜鳥到實(shí)踐老司機(jī)所需要的資料與指南合集前端掘金前端從入門菜鳥到實(shí)踐老司機(jī)所需要的資料與指南合集歸屬于筆者的前端入門與最佳實(shí)踐。 工欲善其事必先利其器-前端實(shí)習(xí)簡歷篇 - 掘金 有幸認(rèn)識很多在大廠工作的學(xué)長,在春招正式開始前為我提供很多內(nèi)部推薦的機(jī)會,非常感謝他們對我的幫助。現(xiàn)在就要去北京了,對第一份正式的實(shí)習(xí)工作也充滿期待,也希望把自己遇到的一些問題和...

    sf_wangchong 評論0 收藏0
  • 前端工程師面試必備(持續(xù)更新中)

    摘要:最近的一次更新的變量有效,并且會作用于全部的引用的處理方式和相同,變量值輸出時(shí)根據(jù)之前最近的一次定義計(jì)算,每次引用最近的定義有效嵌套三種預(yù)編譯器的選擇器嵌套在使用上來說沒有任何區(qū)別,甚至連引用父級選擇器的標(biāo)記也相同。 面試匯總一:2018大廠高級前端面試題匯總 高級面試:【半月刊】前端高頻面試題及答案匯總 css內(nèi)容 響應(yīng)式布局 當(dāng)前主流的三種預(yù)編譯器比較 CSS預(yù)處理器用一種專門的...

    jubincn 評論0 收藏0
  • Vue面試中,經(jīng)常會被問到的面試/Vue知識點(diǎn)整理

    摘要:可以在該鉤子中進(jìn)一步地更改狀態(tài),不會觸發(fā)附加的重渲染過程。我工作中只用到,對和不怎么熟與的區(qū)別相同點(diǎn)都支持指令內(nèi)置指令和自定義指令都支持過濾器內(nèi)置過濾器和自定義過濾器都支持雙向數(shù)據(jù)綁定都不支持低端瀏覽器。 看看面試題,只是為了查漏補(bǔ)缺,看看自己那些方面還不懂。切記不要以為背了面試題,就萬事大吉了,最好是理解背后的原理,這樣面試的時(shí)候才能侃侃而談。不然,稍微有水平的面試官一看就能看出,是...

    mengbo 評論0 收藏0
  • HTML面試整理總結(jié)

    摘要:前記為了準(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 語義化,有...

    Chao 評論0 收藏0
  • 前端最實(shí)用書簽(持續(xù)更新)

    摘要:前言一直混跡社區(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...

    sshe 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<