国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

腳撕專有阿里釘釘前端面試題

共 54275字,需瀏覽 109分鐘

 ·

2021-07-29 11:24

作者:子弈,原文鏈接:https://juejin.cn/post/6987549240436195364,如需轉(zhuǎn)載請聯(lián)系作者授權(quán)

前言

最近用團(tuán)隊(duì)的賬號(hào)發(fā)了一篇文章專有釘釘前端面試指南[1],初衷是希望給大家傳遞一些可能沒有接觸過的知識(shí),其中某些知識(shí)可能也超出了前端的范疇,本質(zhì)是希望給大家提供一些掃盲的思路。但是文章的評論使我意識(shí)到大家對于這個(gè)文章的抵觸心情非常大。我有很認(rèn)真的看大家的每一條評論,然后可能過多的解釋也沒有什么用。我自己也反思可能文章就不應(yīng)該以面試為標(biāo)題進(jìn)行傳播,因?yàn)槊嬖嚨脑捤鸵馕吨毼灰约肮ぷ飨⑾⑾嚓P(guān),更何況我還是以團(tuán)隊(duì)的名義去發(fā)這個(gè)文章。在這里,先跟這些讀完文章體驗(yàn)不是很好的同學(xué)道個(gè)歉。

以前寫文章感覺都很開心,寫完發(fā)完感覺都能給大家?guī)硪恍┬碌妮斎?。但是這一次,我感覺挺難受的,也確實(shí)反思了很多,感覺自己以這樣的方式傳播可能有些問題,主要如下:

  • 題目取的不對,不應(yīng)該拿面試作為標(biāo)題,題目就應(yīng)該是“腳撕專有釘釘前端面試題”

  • 如果作為面試題,其中某些問題問的太大,范圍太廣,確實(shí)不適合面試者進(jìn)行回答

  • 如果作為面試題,其中某些問題問的不夠?qū)I(yè),甚至是有歧義

  • 給出了面試題,就應(yīng)該給出面試題的答案,這樣才是真正幫助到大家掃盲

  • ...

這里不再過多解釋和糾結(jié)面試題的問題了,因?yàn)槲腋杏X不管在評論中做什么解釋,不認(rèn)可的同學(xué)還是會(huì)一如既往的懟上來(挺好的,如果懟完感覺自己還能釋放一些小壓力,或許還能適當(dāng)?shù)慕o子弈增加一些蒼白解釋的動(dòng)力)。當(dāng)然我也很開心很多同學(xué)在評論中求答案,接下來我會(huì)好好認(rèn)真做一期答案,希望能夠給大家?guī)硪恍┬碌妮斎?,?dāng)然答案不可能一下子做完,也不一定全面或者讓大家感覺滿意,或許大家這次的評論又能給我?guī)硪恍W(xué)習(xí)的機(jī)會(huì)。

溫馨提示:這里盡量多給出一些知識(shí)點(diǎn),所以不會(huì)針對問題進(jìn)行機(jī)械式的回答,可能更多的需要大家自行理解和抽象。其中大部分面試題可能會(huì)已文章鏈接的形式出現(xiàn),或許是我自己以前寫過的文章,或者是我覺得別人寫的不錯(cuò)的文章。

基礎(chǔ)知識(shí)

基礎(chǔ)知識(shí)主要包含以下幾個(gè)方面:

  • 基礎(chǔ):計(jì)算機(jī)原理、編譯原理、數(shù)據(jù)結(jié)構(gòu)、算法、設(shè)計(jì)模式、編程范式等基本知識(shí)了解

  • 語法:JavaScript、ECMAScript、CSS、TypeScript、HTML、Node.js 等語法的了解和使用

  • 框架:React、Vue、Egg、Koa、Express、Webpack 等原理的了解和使用

  • 工程:編譯工具、格式工具、Git、NPM、單元測試、Nginx、PM2、CI / CD 了解和使用

  • 網(wǎng)絡(luò):HTTP、TCP、UDP、WebSocket、Cookie、Session、跨域、緩存、協(xié)議的了解

  • 性能:編譯性能、監(jiān)控、白屏檢測、SEO、Service Worker 等了解

  • 插件:Chrome 、Vue CLI 、Webpack 等插件設(shè)計(jì)思路的理解

  • 系統(tǒng):Mac、Windows、Linux 系統(tǒng)配置的實(shí)踐

  • 后端:Redis 緩存、數(shù)據(jù)庫、Graphql、SSR、模板引擎等了解和使用

基礎(chǔ)

1、列舉你所了解的計(jì)算機(jī)存儲(chǔ)設(shè)備類型?

現(xiàn)代計(jì)算機(jī)以存儲(chǔ)器為中心,主要由 CPU、I / O 設(shè)備以及主存儲(chǔ)器三大部分組成。各個(gè)部分之間通過總線進(jìn)行連接通信,具體如下圖所示:

image.png

上圖是一種多總線結(jié)構(gòu)的示意圖,CPU、主存以及 I / O 設(shè)備之間的所有數(shù)據(jù)都是通過總線進(jìn)行并行傳輸,使用局部總線是為了提高 CPU 的吞吐量(CPU 不需要直接跟 I / O 設(shè)備通信),而使用高速總線(更貼近 CPU)和 DMA 總線則是為了提升高速 I / O 設(shè)備(外設(shè)存儲(chǔ)器、局域網(wǎng)以及多媒體等)的執(zhí)行效率。

主存包括隨機(jī)存儲(chǔ)器 RAM 和只讀存儲(chǔ)器 ROM,其中 ROM 又可以分為 MROM(一次性)、PROM、EPROM、EEPROM 。ROM 中存儲(chǔ)的程序(例如啟動(dòng)程序、固化程序)和數(shù)據(jù)(例如常量數(shù)據(jù))在斷電后不會(huì)丟失。RAM 主要分為靜態(tài) RAM(SRAM) 和動(dòng)態(tài) RAM(DRAM) 兩種類型(DRAM 種類很多,包括 SDRAM、RDRAM、CDRAM 等),斷電后數(shù)據(jù)會(huì)丟失,主要用于存儲(chǔ)臨時(shí)程序或者臨時(shí)變量數(shù)據(jù)。DRAM 一般訪問速度相對較慢。由于現(xiàn)代 CPU 讀取速度要求相對較高,因此在 CPU 內(nèi)核中都會(huì)設(shè)計(jì) L1、L2 以及 L3 級別的多級高速緩存,這些緩存基本是由 SRAM 構(gòu)成,一般訪問速度較快。

2、一般代碼存儲(chǔ)在計(jì)算機(jī)的哪個(gè)設(shè)備中?代碼在 CPU 中是如何運(yùn)行的?

高級程序設(shè)計(jì)語言不能直接被計(jì)算機(jī)理解并執(zhí)行,需要通過翻譯程序?qū)⑵滢D(zhuǎn)換成特定處理器上可執(zhí)行的指令,計(jì)算機(jī) CPU 的簡單工作原理如下所示:

image.png

CPU 主要由控制單元、運(yùn)算單元和存儲(chǔ)單元組成(注意忽略了中斷系統(tǒng)),各自的作用如下:

  • 控制單元:在節(jié)拍脈沖的作用下,將程序計(jì)數(shù)器(Program Counter,PC)指向的主存或者多級高速緩存中的指令地址送到地址總線,接著獲取指令地址所對應(yīng)的指令并放入指令寄存器 (Instruction Register,IR)中,然后通過指令譯碼器(Instruction Decoder,ID)分析指令需要進(jìn)行的操作,最后通過操作控制器(Operation Controller,OC)向其他設(shè)備發(fā)出微操作控制信號(hào)。

  • 運(yùn)算單元:如果控制單元發(fā)出的控制信號(hào)存在算術(shù)運(yùn)算(加、減、乘、除、增 1、減 1、取反等)或者邏輯運(yùn)算(與、或、非、異或),那么需要通過運(yùn)算單元獲取存儲(chǔ)單元的計(jì)算數(shù)據(jù)進(jìn)行處理。

  • 存儲(chǔ)單元:包括片內(nèi)緩存和寄存器組,是 CPU 中臨時(shí)數(shù)據(jù)的存儲(chǔ)地方。CPU 直接訪問主存數(shù)據(jù)大概需要花費(fèi)數(shù)百個(gè)機(jī)器周期,而訪問寄存器或者片內(nèi)緩存只需要若干個(gè)或者幾十個(gè)機(jī)器周期,因此會(huì)使用內(nèi)部寄存器或緩存來存儲(chǔ)和獲取臨時(shí)數(shù)據(jù)(即將被運(yùn)算或者運(yùn)算之后的數(shù)據(jù)),從而提高 CPU 的運(yùn)行效率。

除此之外,計(jì)算機(jī)系統(tǒng)執(zhí)行程序指令時(shí)需要花費(fèi)時(shí)間,其中取出一條指令并執(zhí)行這條指令的時(shí)間叫指令周期。指令周期可以分為若干個(gè)階段(取指周期、間址周期、執(zhí)行周期和中斷周期),每個(gè)階段主要完成一項(xiàng)基本操作,完成基本操作的時(shí)間叫機(jī)器周期。機(jī)器周期是時(shí)鐘周期的分頻,例如最經(jīng)典的 8051 單片機(jī)的機(jī)器周期為 12 個(gè)時(shí)鐘周期。時(shí)鐘周期是 CPU 工作的基本時(shí)間單位,也可以稱為節(jié)拍脈沖或 T 周期(CPU 主頻的倒數(shù)) 。假設(shè) CPU 的主頻是 1 GHz(1 Hz 表示每秒運(yùn)行 1 次),那么表示時(shí)鐘周期為 1 / 109 s。理論上 CPU 的主頻越高,程序指令執(zhí)行的速度越快。

3、什么是指令和指令集?

上圖右側(cè)主存中的指令是 CPU 可以支持的處理命令,一般包含算術(shù)指令(加和減)、邏輯指令(與、或和非)、數(shù)據(jù)指令(移動(dòng)、輸入、刪除、加載和存儲(chǔ))、流程控制指令以及程序結(jié)束指令等,由于 CPU 只能識(shí)別二進(jìn)制碼,因此指令是由二進(jìn)制碼組成。除此之外,指令的集合稱為指令集(例如匯編語言就是指令集的一種表現(xiàn)形式),常見的指令集有精簡指令集(ARM)和復(fù)雜指令集(Inter X86)。一般指令集決定了 CPU 處理器的硬件架構(gòu),規(guī)定了處理器的相應(yīng)操作。

4、復(fù)雜指令集和精簡指令集有什么區(qū)別?

5、JavaScript 是如何運(yùn)行的?解釋型語言和編譯型語言的差異是什么?

早期的計(jì)算機(jī)只有機(jī)器語言時(shí),程序設(shè)計(jì)必須用二進(jìn)制數(shù)(0 和 1)來編寫程序,并且要求程序員對計(jì)算機(jī)硬件和指令集非常了解,編程的難度較大,操作極易出錯(cuò)。為了解決機(jī)器語言的編程問題,慢慢開始出現(xiàn)了符號(hào)式的匯編語言(采用 ADD、SUB、MUL、DIV 等符號(hào)代表加減乘除)。為了使得計(jì)算機(jī)可以識(shí)別匯編語言,需要將匯編語言翻譯成機(jī)器能夠識(shí)別的機(jī)器語言(處理器的指令集):

image.png

由于每一種機(jī)器的指令系統(tǒng)不同,需要不同的匯編語言程序與之匹配,因此程序員往往需要針對不同的機(jī)器了解其硬件結(jié)構(gòu)和指令系統(tǒng)。為了可以抹平不同機(jī)器的指令系統(tǒng),使得程序員可以更加關(guān)注程序設(shè)計(jì)本身,先后出現(xiàn)了各種面向問題的高級程序設(shè)計(jì)語言,例如 BASIC 和 C,具體過程如下圖所示:

image.png

高級程序語言會(huì)先翻譯成匯編語言或者其他中間語言,然后再根據(jù)不同的機(jī)器翻譯成機(jī)器語言進(jìn)行執(zhí)行。除此之外,匯編語言虛擬機(jī)和機(jī)器語言機(jī)器之間還存在一層操作系統(tǒng)虛擬機(jī),主要用于控制和管理操作系統(tǒng)的全部硬件和軟件資源(隨著超大規(guī)模集成電路技術(shù)的不斷發(fā)展,一些操作系統(tǒng)的軟件功能逐步由硬件來替換,例如目前的操作系統(tǒng)已經(jīng)實(shí)現(xiàn)了部分程序的固化,簡稱固件,將程序永久性的存儲(chǔ)在 ROM 中)。機(jī)器語言機(jī)器還可以繼續(xù)分解成微程序機(jī)器,將每一條機(jī)器指令翻譯成一組微指令(微程序)進(jìn)行執(zhí)行。

上述虛擬機(jī)所提供的語言轉(zhuǎn)換程序被稱為編譯器,主要作用是將某種語言編寫的源程序轉(zhuǎn)換成一個(gè)等價(jià)的機(jī)器語言程序,編譯器的作用如下圖所示:

image.png

例如 C 語言,可以先通過 gcc 編譯器生成 Linux 和 Windows 下的目標(biāo) .o 和 .obj 文件(object 文件,即目標(biāo)文件),然后將目標(biāo)文件與底層系統(tǒng)庫文件、應(yīng)用程序庫文件以及啟動(dòng)文件鏈接成可執(zhí)行文件在目標(biāo)機(jī)器上執(zhí)行。

溫馨提示:感興趣的同學(xué)可以了解一下 ARM 芯片的程序運(yùn)行原理,包括使用 IDE 進(jìn)行程序的編譯(IDE 內(nèi)置編譯器,主流編譯器包含 ARMCC、IAR 以及 GCC FOR ARM 等,其中一些編譯器僅僅隨著 IDE 進(jìn)行捆綁發(fā)布,不提供獨(dú)立使用的能力,而一些編譯器則隨著 IDE 進(jìn)行發(fā)布的同時(shí),還提供命令行接口的獨(dú)立使用方式)、通過串口進(jìn)行程序下載(下載到芯片的代碼區(qū)初始啟動(dòng)地址映射的存儲(chǔ)空間地址)、啟動(dòng)的存儲(chǔ)空間地址映射(包括系統(tǒng)存儲(chǔ)器、閃存 FLASH、內(nèi)置 SRAM 等)、芯片的程序啟動(dòng)模式引腳 BOOT 的設(shè)置(例如調(diào)試代碼時(shí)常常選擇內(nèi)置 SRAM、真正程序運(yùn)行的時(shí)候選擇閃存 FLASH)等。

如果某種高級語言或者應(yīng)用語言(例如用于人工智能的計(jì)算機(jī)設(shè)計(jì)語言)轉(zhuǎn)換的目標(biāo)語言不是特定計(jì)算機(jī)的匯編語言,而是面向另一種高級程序語言(很多研究性的編譯器將 C 作為目標(biāo)語言),那么還需要將目標(biāo)高級程序語言再進(jìn)行一次額外的編譯才能得到最終的目標(biāo)程序,這種編譯器可稱為源到源的轉(zhuǎn)換器。

除此之外,有些程序設(shè)計(jì)語言將編譯的過程和最終轉(zhuǎn)換成目標(biāo)程序進(jìn)行執(zhí)行的過程混合在一起,這種語言轉(zhuǎn)換程序通常被稱為解釋器,主要作用是將某種語言編寫的源程序作為輸入,將該源程序執(zhí)行的結(jié)果作為輸出,解釋器的作用如下圖所示:

image.png

解釋器和編譯器有很多相似之處,都需要對源程序進(jìn)行分析,并轉(zhuǎn)換成目標(biāo)機(jī)器可識(shí)別的機(jī)器語言進(jìn)行執(zhí)行。只是解釋器是在轉(zhuǎn)換源程序的同時(shí)立馬執(zhí)行對應(yīng)的機(jī)器語言(轉(zhuǎn)換和執(zhí)行的過程不分離),而編譯器得先把源程序全部轉(zhuǎn)換成機(jī)器語言并產(chǎn)生目標(biāo)文件,然后將目標(biāo)文件寫入相應(yīng)的程序存儲(chǔ)器進(jìn)行執(zhí)行(轉(zhuǎn)換和執(zhí)行的過程分離)。例如 Perl、Scheme、APL 使用解釋器進(jìn)行轉(zhuǎn)換, C、C++ 則使用編譯器進(jìn)行轉(zhuǎn)換,而 Java 和 JavaScript 的轉(zhuǎn)換既包含了編譯過程,也包含了解釋過程。

6、簡單描述一下 Babel 的編譯過程?

7、JavaScript 中的數(shù)組和函數(shù)在內(nèi)存中是如何存儲(chǔ)的?

JavaScript 中的數(shù)組存儲(chǔ)大致需要分為兩種情況:

  • 同種類型數(shù)據(jù)的數(shù)組分配連續(xù)的內(nèi)存空間

  • 存在非同種類型數(shù)據(jù)的數(shù)組使用哈希映射分配內(nèi)存空間

溫馨提示:可以想象一下連續(xù)的內(nèi)存空間只需要根據(jù)索引(指針)直接計(jì)算存儲(chǔ)位置即可。如果是哈希映射那么首先需要計(jì)算索引值,然后如果索引值有沖突的場景下還需要進(jìn)行二次查找(需要知道哈希的存儲(chǔ)方式)。

8、瀏覽器和 Node.js 中的事件循環(huán)機(jī)制有什么區(qū)別?

閱讀鏈接:面試分享:兩年工作經(jīng)驗(yàn)成功面試阿里 P6 總結(jié)[2] - 了解 Event Loop 嗎?

9、ES6 Modules 相對于 CommonJS 的優(yōu)勢是什么?

10、高級程序設(shè)計(jì)語言是如何編譯成機(jī)器語言的?

11、編譯器一般由哪幾個(gè)階段組成?數(shù)據(jù)類型檢查一般在什么階段進(jìn)行?

12、編譯過程中虛擬機(jī)的作用是什么?

13、什么是中間代碼(IR),它的作用是什么?

14、什么是交叉編譯?

編譯器的設(shè)計(jì)是一個(gè)非常龐大和復(fù)雜的軟件系統(tǒng)設(shè)計(jì),在真正設(shè)計(jì)的時(shí)候需要解決兩個(gè)相對重要的問題:

  • 如何分析不同高級程序語言設(shè)計(jì)的源程序

  • 如何將源程序的功能等價(jià)映射到不同指令系統(tǒng)的目標(biāo)機(jī)器

為了解決上述兩項(xiàng)問題,編譯器的設(shè)計(jì)最終被分解成前端(注意這里所說的不是 Web 前端)和后端兩個(gè)編譯階段,前端用于解決第一個(gè)問題,而后端用于解決第二個(gè)問題,具體如下圖所示:

image.png

上圖中的中間表示(Intermediate Representation,IR)是程序結(jié)構(gòu)的一種表現(xiàn)方式,它會(huì)比 AST(后續(xù)講解)更加接近匯編語言或者指令集,同時(shí)也會(huì)保留源程序中的一些高級信息,除此之外 ,它的種類很多,包括三地址碼(Three Address Code, TAC)[3]、靜態(tài)單賦值形式(Static Single Assignment Form, SSA)[4]以及基于棧的 IR 等,具體作用包括:

  • 靠近前端部分主要適配不同的源程序,靠近后端部分主要適配不同的指令集,更易于編譯器的錯(cuò)誤調(diào)試,容易識(shí)別是 IR 之前還是之后出問題

  • 如下左圖所示,如果沒有 IR,那么源程序到指令集之間需要進(jìn)行一一適配,而有了中間表示,則可以使得編譯器的職責(zé)更加分離,源程序的編譯更多關(guān)注如何轉(zhuǎn)換成 IR,而不是去適配不同的指令集

  • IR 本身可以做到多趟迭代從而優(yōu)化源程序,在每一趟迭代的過程中可以研究代碼并記錄優(yōu)化的細(xì)節(jié),方便后續(xù)的迭代查找并利用這些優(yōu)化信息,最終可以高效輸出更優(yōu)的目標(biāo)程序

image.png

由于 IR 可以進(jìn)行多趟迭代進(jìn)行程序優(yōu)化,因此在編譯器中可插入一個(gè)新的優(yōu)化階段,如下圖所示:

image.png

優(yōu)化器可以對 IR 處理一遍或者多遍,從而生成更快執(zhí)行速度(例如找到循環(huán)中不變的計(jì)算并對其進(jìn)行優(yōu)化從而減少運(yùn)算次數(shù))或者更小體積的目標(biāo)程序,也可能用于產(chǎn)生更少異常或者更低功耗的目標(biāo)程序。除此之外,前端和后端內(nèi)部還可以細(xì)分為多個(gè)處理步驟,具體如下圖所示:

image.png

優(yōu)化器中的每一遍優(yōu)化處理都可以使用一個(gè)或多個(gè)優(yōu)化技術(shù)來改進(jìn)代碼,每一趟處理最終都是讀寫 IR 的操作,這樣不僅僅可以使得優(yōu)化可以更加高效,同時(shí)也可以降低優(yōu)化的復(fù)雜度,還提高了優(yōu)化的靈活性,可以使得編譯器配置不同的優(yōu)化選項(xiàng),達(dá)到組合優(yōu)化的效果。

15、發(fā)布 / 訂閱模式和觀察者模式的區(qū)別是什么?

閱讀鏈接:基于 Vue 實(shí)現(xiàn)一個(gè)簡易 MVVM [5]- 觀察者模式和發(fā)布/訂閱模式

16、裝飾器模式一般會(huì)在什么場合使用?

17、談?wù)勀銓Υ笮晚?xiàng)目的代碼解耦設(shè)計(jì)理解?什么是 Ioc?一般 DI 采用什么設(shè)計(jì)模式實(shí)現(xiàn)?

18、列舉你所了解的編程范式?

編程范式(Programming paradigm)是指計(jì)算機(jī)編程的基本風(fēng)格或者典型模式,可以簡單理解為編程學(xué)科中實(shí)踐出來的具有哲學(xué)和理論依據(jù)的一些經(jīng)典原型。常見的編程范式有:

  • 面向過程(Process Oriented Programming,POP)

  • 面向?qū)ο螅∣bject Oriented Programming,OOP)

  • 面向接口(Interface Oriented Programming, IOP)

  • 面向切面(Aspect Oriented Programming,AOP)

  • 函數(shù)式(Funtional Programming,F(xiàn)P)

  • 響應(yīng)式(Reactive Programming,RP)

  • 函數(shù)響應(yīng)式(Functional Reactive Programming,F(xiàn)RP)

閱讀鏈接::如果你對于編程范式的定義相對模糊,可以繼續(xù)閱讀 What is the precise definition of programming paradigm?[6] 了解更多。

不同的語言可以支持多種不同的編程范式,例如 C 語言支持 POP 范式,C++ 和 Java 語言支持 OOP 范式,Swift 語言則可以支持 FP 范式,而 Web 前端中的 JavaScript 可以支持上述列出的所有編程范式。

19、什么是面向切面(AOP)的編程?

20、什么是函數(shù)式編程?

顧名思義,函數(shù)式編程是使用函數(shù)來進(jìn)行高效處理數(shù)據(jù)或數(shù)據(jù)流的一種編程方式。在數(shù)學(xué)中,函數(shù)的三要素是定義域、值域和**對應(yīng)關(guān)系。假設(shè) A、B 是非空數(shù)集,對于集合 A 中的任意一個(gè)數(shù) x,在集合 B 中都有唯一確定的數(shù) f(x) 和它對應(yīng),那么可以將 f 稱為從 A 到 B 的一個(gè)函數(shù),記作:y = f(x)。在函數(shù)式編程中函數(shù)的概念和數(shù)學(xué)函數(shù)的概念類似,主要是描述形參 x 和返回值 y 之間的對應(yīng)關(guān)系,**如下圖所示:

溫馨提示:圖片來自于簡明 JavaScript 函數(shù)式編程——入門篇[7]

在實(shí)際的編程中,可以將各種明確對應(yīng)關(guān)系的函數(shù)進(jìn)行傳遞、組合從而達(dá)到處理數(shù)據(jù)的最終目的。在此過程中,我們的關(guān)注點(diǎn)不在于如何去實(shí)現(xiàn)**對應(yīng)關(guān)系,**而在于如何將各種已有的對應(yīng)關(guān)系進(jìn)行高效聯(lián)動(dòng),從而可快速進(jìn)行數(shù)據(jù)轉(zhuǎn)換,達(dá)到最終的數(shù)據(jù)處理目的,提供開發(fā)效率。

簡單示例

盡管你對函數(shù)式編程的概念有所了解,但是你仍然不知道函數(shù)式編程到底有什么特點(diǎn)。這里我們?nèi)匀荒?OOP 編程范式來舉例,假設(shè)希望通過 OOP 編程來解決數(shù)學(xué)的加減乘除問題:

class MathObject {

  constructor(private value: number) {}

  public add(num: number): MathObject {

    this.value += num;

    return this;

  }

  public multiply(num: number): MathObject {

    this.value *= num;

    return this;

  }

  public getValue(): number {

    return this.value;

  }

}



const a = new MathObject(1);

a.add(1).multiply(2).add(a.multiply(2).getValue());

我們希望通過上述程序來解決 (1 + 2) * 2 + 1 * 2 的問題,但實(shí)際上計(jì)算出來的結(jié)果是 24,因?yàn)樵诖a內(nèi)部有一個(gè) this.value 的狀態(tài)值需要跟蹤,這會(huì)使得結(jié)果不符合預(yù)期。  接下來我們采用函數(shù)式編程的方式:

function add(a: number, b: number): number {

  return a + b;

}



function multiply(a: number, b: number): number {

  return a * b;

}



const a: number = 1;

const b: number = 2;



add(multiply(add(a, b), b), multiply(a, b));

以上程序計(jì)算的結(jié)果是 8,完全符合預(yù)期。我們知道了 add 和 multiply 兩個(gè)函數(shù)的實(shí)際對應(yīng)關(guān)系,通過將對應(yīng)關(guān)系進(jìn)行有效的組合和傳遞,達(dá)到了最終的計(jì)算結(jié)果。除此之外,這兩個(gè)函數(shù)還可以根據(jù)數(shù)學(xué)定律得出更優(yōu)雅的組合方式:

add(multiply(add(a, b), b), multiply(a, b));



// 根據(jù)數(shù)學(xué)定律分配律:a * b  +  a * c = a * (b + c),得出:

// (a + b) * b + a * b = (2a + b) * b



// 簡化上述函數(shù)的組合方式

multiply(add(add(a, a), b), b);

我們完全不需要追蹤類似于 OOP 編程范式中可能存在的內(nèi)部狀態(tài)數(shù)據(jù),事實(shí)上對于數(shù)學(xué)定律中的結(jié)合律、交換律、同一律以及分配律,上述的函數(shù)式編程代碼足可以勝任。

原則

通過上述簡單的例子可以發(fā)現(xiàn),要實(shí)現(xiàn)高可復(fù)用的函數(shù)**(對應(yīng)關(guān)系)**,一定要遵循某些特定的原則,否則在使用的時(shí)候可能無法進(jìn)行高效的傳遞和組合,例如

  • 高內(nèi)聚低耦合

  • 最小意外原則

  • 單一職責(zé)原則

  • ...

如果你之前經(jīng)常進(jìn)行無原則性的代碼設(shè)計(jì),那么在設(shè)計(jì)過程中可能會(huì)出現(xiàn)各種出乎意料的問題(這是為什么新手老是出現(xiàn)一些稀奇古怪問題的主要原因)。函數(shù)式編程可以有效的通過一些原則性的約束使你設(shè)計(jì)出更加健壯和優(yōu)雅的代碼,并且在不斷的實(shí)踐過程中進(jìn)行經(jīng)驗(yàn)式疊加,從而提高開發(fā)效率。

特點(diǎn)

雖然我們在使用函數(shù)的過程中更多的不再關(guān)注函數(shù)如何實(shí)現(xiàn)(對應(yīng)關(guān)系),但是真正在使用和設(shè)計(jì)函數(shù)的時(shí)候需要注意以下一些特點(diǎn):

  • 聲明式(Declarative Programming)

  • 一等公民(First Class Function)

  • 純函數(shù)(Pure Function)

  • 無狀態(tài)和數(shù)據(jù)不可變(Statelessness and Immutable Data)

  • ...

聲明式

我們以前設(shè)計(jì)的代碼通常是命令式編程方式,這種編程方式往往注重具體的實(shí)現(xiàn)的過程(對應(yīng)關(guān)系),而函數(shù)式編程則采用聲明式的編程方式,往往注重如何去組合已有的**對應(yīng)關(guān)系。**簡單舉個(gè)例子:

// 命令式

const array = [0.81.72.53.4];

const filterArray = [];



for (let i = 0; i < array.length; i++) {

  const integer = Math.floor(array[i]);

  if (integer < 2) {

    continue;

  }

  filterArray.push(integer);

}



// 聲明式

// map 和 filter 不會(huì)修改原有數(shù)組,而是產(chǎn)生新的數(shù)組返回

[0.81.72.53.4].map((item) => Math.floor(item)).filter((item) => item > 1);

命令式代碼一步一步的告訴計(jì)算機(jī)需要執(zhí)行哪些語句,需要關(guān)心變量的實(shí)例化情況、循環(huán)的具體過程以及跟蹤變量狀態(tài)的變化過程。聲明式代碼更多的不再關(guān)心代碼的具體執(zhí)行過程,而是采用表達(dá)式的組合變換去處理問題,不再強(qiáng)調(diào)怎么做,而是指明**做什么。**聲明式編程方式可以將我們設(shè)計(jì)代碼的關(guān)注點(diǎn)徹底從過程式解放出來,從而提高開發(fā)效率。

一等公民

在 JavaScript 中,函數(shù)的使用非常靈活,例如可以對函數(shù)進(jìn)行以下操作:

interface IHello {

  (name: string): string;

  key?: string;

  arr?: number[];

  fn?(name: string): string;

}



// 函數(shù)聲明提升

console.log(hello instanceof Object); // true



// 函數(shù)聲明提升

// hello 和其他引用類型的對象一樣,都有屬性和方法

hello.key = 'key';

hello.arr = [12];

hello.fn = function (name: string{

  return `hello.fn, ${name}`;

};



// 函數(shù)聲明提升

// 注意函數(shù)表達(dá)式不能在聲明前執(zhí)行,例如不能在這里使用 helloCopy('world')

hello('world');



// 函數(shù)

// 創(chuàng)建新的函數(shù)對象,將函數(shù)的引用指向變量 hello

// hello 僅僅是變量的名稱

function hello(name: string): string {

  return `hello, ${name}`;

}



console.log(hello.key); // key

console.log(hello.arr); // [1,2]

console.log(hello.name); // hello



// 函數(shù)表達(dá)式

const helloCopy: IHello = hello;

helloCopy('world');



function transferHello(name: string, hello: Hello{

  return hello('world');

}



// 把函數(shù)對象當(dāng)作實(shí)參傳遞

transferHello('world', helloCopy);



// 把匿名函數(shù)當(dāng)作實(shí)參傳遞

transferHello('world'function (name: string{

  return `hello, ${name}`;

});



通過以上示例可以看出,函數(shù)繼承至對象并擁有對象的特性。在 JavaScript 中可以對函數(shù)進(jìn)行參數(shù)傳遞、變量賦值或數(shù)組操作等等,因此把函數(shù)稱為一等公民。函數(shù)式編程的核心就是對函數(shù)進(jìn)行組合或傳遞,JavaScript 中函數(shù)這種靈活的特性是滿足函數(shù)式編程的重要條件。

純函數(shù)

純函數(shù)是是指在相同的參數(shù)調(diào)用下,函數(shù)的返回值唯一不變。這跟數(shù)學(xué)中函數(shù)的映射關(guān)系類似,同樣的 x 不可能映射多個(gè)不同的 y。使用函數(shù)式編程會(huì)使得函數(shù)的調(diào)用非常穩(wěn)定,從而降低 Bug 產(chǎn)生的機(jī)率。當(dāng)然要實(shí)現(xiàn)純函數(shù)的這種特性,需要函數(shù)不能包含以下一些副作用:

  • 操作 Http 請求

  • 可變數(shù)據(jù)(包括在函數(shù)內(nèi)部改變輸入?yún)?shù))

  • DOM 操作

  • 打印日志

  • 訪問系統(tǒng)狀態(tài)

  • 操作文件系統(tǒng)

  • 操作數(shù)據(jù)庫

  • ...

從以上常見的一些副作用可以看出,純函數(shù)的實(shí)現(xiàn)需要遵循最小意外原則,為了確保函數(shù)的穩(wěn)定唯一的輸入和輸出,盡量應(yīng)該避免與函數(shù)外部的環(huán)境進(jìn)行任何交互行為,從而防止外部環(huán)境對函數(shù)內(nèi)部產(chǎn)生無法預(yù)料的影響。純函數(shù)的實(shí)現(xiàn)應(yīng)該自給自足,舉幾個(gè)例子:

// 如果使用 const 聲明 min 變量(基本數(shù)據(jù)類型),則可以保證以下函數(shù)的純粹性

let min: number = 1;



// 非純函數(shù)

// 依賴外部環(huán)境變量 min,一旦 min 發(fā)生變化則輸入和返回不唯一

function isEqual(num: number): boolean {

  return num === min;

}



// 純函數(shù)

function isEqual(num: number): boolean {

  return num === 1;

}



// 非純函數(shù)

function request<TS>(url: string, params: T): Promise<S{

  // 會(huì)產(chǎn)生請求成功和請求失敗兩種結(jié)果,返回的結(jié)果可能不唯一

  return $.getJson(url, params);

}



// 純函數(shù)

function request<TS>(url: string, params: T) : () => Promise<S{

  return function({

    return $.getJson(url, params);

  }

}

純函數(shù)的特性使得函數(shù)式編程具備以下特性:

  • 可緩存性(Cacheable)

  • 可移植性(Portable)

  • 可測試性(Testable)

可緩存性和可測試性基于純函數(shù)輸入輸出唯一不變的特性,可移植性則主要基于純函數(shù)不依賴外部環(huán)境的特性。這里舉一個(gè)可緩存的例子:

interface ICache<T> {

  [arg: string]: T;

}



interface ISquare<T> {

  (x: T): T;

}



// 簡單的緩存函數(shù)(忽略通用性和健壯性)

function memoize<T>(fn: ISquare<T>): ISquare<T{

  const cache: ICache<T> = {};

  return function (x: T{

    const arg: string = JSON.stringify(x);

    cache[arg] = cache[arg] || fn.call(fn, x);

    return cache[arg];

  };

}



// 純函數(shù)

function square(x: number): number {

  return x * x;

}



const memoSquare = memoize<number>(square);

memoSquare(4);



// 不會(huì)再次調(diào)用純函數(shù) square,而是直接從緩存中獲取值

// 由于輸入和輸出的唯一性,獲取緩存結(jié)果可靠穩(wěn)定

// 提升代碼的運(yùn)行效率

memoSquare(4);

無狀態(tài)和數(shù)據(jù)不可變

在函數(shù)式編程的簡單示例中已經(jīng)可以清晰的感受到函數(shù)式編程絕對不能依賴內(nèi)部狀態(tài),而在純函數(shù)中則說明了函數(shù)式編程不能依賴外部的環(huán)境或狀態(tài),因?yàn)橐坏┮蕾嚨臓顟B(tài)變化,不能保證函數(shù)根據(jù)對應(yīng)關(guān)系所計(jì)算的返回值因?yàn)闋顟B(tài)的變化仍然保持不變。

這里單獨(dú)講解一下數(shù)據(jù)不可變,在 JavaScript 中有很多數(shù)組操作的方法,舉個(gè)例子:

const arr = [123];



console.log(arr.slice(02)); // [1, 2]

console.log(arr); // [1, 2, 3]

console.log(arr.slice(02)); // [1, 2]

console.log(arr); // [1, 2, 3]



console.log(arr.splice(01)); // [1]

console.log(arr); // [2, 3]

console.log(arr.splice(01)); // [2]

console.log(arr); // [3]

這里的 slice 方法多次調(diào)用都不會(huì)改變原有數(shù)組,且會(huì)產(chǎn)生相同的輸出。而 splice 每次調(diào)用都在修改原數(shù)組,且產(chǎn)生的輸出也不相同。  在函數(shù)式編程中,這種會(huì)改變原有數(shù)據(jù)的函數(shù)已經(jīng)不再是純函數(shù),應(yīng)該盡量避免使用。

閱讀鏈接:如果想要了解更深入的函數(shù)式編程知識(shí)點(diǎn),可以額外閱讀函數(shù)式編程指北[8]。

21、響應(yīng)式編程的使用場景有哪些?

響應(yīng)式編程是一種基于觀察者(發(fā)布 / 訂閱)模式并且面向異步(Asynchronous)數(shù)據(jù)流(Data Stream)和變化傳播的聲明式編程范式。響應(yīng)式編程主要適用的場景包含:

  • 用戶和系統(tǒng)發(fā)起的連續(xù)事件處理,例如鼠標(biāo)的點(diǎn)擊、鍵盤的按鍵或者通信設(shè)備發(fā)起的信號(hào)等

  • 非可靠的網(wǎng)絡(luò)或者通信處理(例如 HTTP 網(wǎng)絡(luò)的請求重試)

  • 連續(xù)的異步 IO 處理

  • 復(fù)雜的繼發(fā)事務(wù)處理(例如一次事件涉及到多個(gè)繼發(fā)的網(wǎng)絡(luò)請求)

  • 高并發(fā)的消息處理(例如 IM 聊天)

  • ...

語法

22、如何實(shí)現(xiàn)一個(gè)上中下三行布局,頂部和底部最小高度是 100px,中間自適應(yīng)?

23、如何判斷一個(gè)元素 CSS 樣式溢出,從而可以選擇性的加 title 或者 Tooltip?

24、如何讓 CSS 元素左側(cè)自動(dòng)溢出(... 溢出在左側(cè))?

The direction CSS property sets the direction of text, table columns, and horizontal overflow. Use rtl for languages written from right to left (like Hebrew or Arabic), and ltr for those written from left to right (like English and most other languages).

具體查看:https://developer.mozilla.org/en-US/docs/Web/CSS/direction

25、什么是沙箱?瀏覽器的沙箱有什么作用?

26、如何處理瀏覽器中表單項(xiàng)的密碼自動(dòng)填充問題?

27、Hash 和 History 路由的區(qū)別和優(yōu)缺點(diǎn)?

28、JavaScript 中對象的屬性描述符有哪些?分別有什么作用?

29、JavaScript 中 console 有哪些 api ?


The console object provides access to the browser's debugging console (e.g. the Web console[9] in Firefox). The specifics of how it works varies from browser to browser, but there is a de facto set of features that are typically provided.

這里列出一些我常用的 API:

  • console.log

  • console.error

  • console.time

  • console.timeEnd

  • console.group

具體查看:https://developer.mozilla.org/en-US/docs/Web/API/console

30、 簡單對比一下 Callback、Promise、Generator、Async 幾個(gè)異步 API 的優(yōu)劣?

在 JavaScript 中利用事件循環(huán)機(jī)制[10](Event Loop)可以在單線程中實(shí)現(xiàn)非阻塞式、異步的操作。例如

  • Node.js 中的 Callback、EventEmitter[11]、Stream[12]

  • ES6 中的 Promise[13]、Generator[14]

  • ES2017 中的 Async[15]

  • 三方庫 RxJS、Q[16] 、Co、[17]Bluebird[18]

我們重點(diǎn)來看一下常用的幾種編程方式(Callback、Promise、Generator、Async)在語法糖上帶來的優(yōu)劣對比。

Callback

Callback(回調(diào)函數(shù))是在 Web 前端開發(fā)中經(jīng)常會(huì)使用的編程方式。這里舉一個(gè)常用的定時(shí)器示例:

export interface IObj {

  value: string;

  deferExec(): void;

  deferExecAnonymous(): void;

  console(): void;

}



export const obj: IObj = {

  value: 'hello',



  deferExecBind() {

    // 使用箭頭函數(shù)可達(dá)到一樣的效果

    setTimeout(this.console.bind(this), 1000);

  },



  deferExec() {

    setTimeout(this.console, 1000);

  },



  console() {

    console.log(this.value);

  },

};



obj.deferExecBind(); // hello

obj.deferExec(); // undefined

回調(diào)函數(shù)經(jīng)常會(huì)因?yàn)檎{(diào)用環(huán)境的變化而導(dǎo)致 this 的指向性變化。除此之外,使用回調(diào)函數(shù)來處理多個(gè)繼發(fā)的異步任務(wù)時(shí)容易導(dǎo)致回調(diào)地獄(Callback Hell):

fs.readFile(fileA, 'utf-8'function (err, data{

  fs.readFile(fileB, 'utf-8'function (err, data{

    fs.readFile(fileC, 'utf-8'function (err, data{

      fs.readFile(fileD, 'utf-8'function (err, data{

        // 假設(shè)在業(yè)務(wù)中 fileD 的讀寫依次依賴 fileA、fileB 和 fileC

        // 或者經(jīng)常也可以在業(yè)務(wù)中看到多個(gè) HTTP 請求的操作有前后依賴(繼發(fā) HTTP 請求)

        // 這些異步任務(wù)之間縱向嵌套強(qiáng)耦合,無法進(jìn)行橫向復(fù)用

        // 如果某個(gè)異步發(fā)生變化,那它的所有上層或下層回調(diào)可能都需要跟著變化(比如 fileA 和 fileB 的依賴關(guān)系倒置)

        // 因此稱這種現(xiàn)象為 回調(diào)地獄

        // ....

      });

    });

  });

});

回調(diào)函數(shù)不能通過 return 返回?cái)?shù)據(jù),比如我們希望調(diào)用帶有回調(diào)參數(shù)的函數(shù)并返回異步執(zhí)行的結(jié)果時(shí),只能通過再次回調(diào)的方式進(jìn)行參數(shù)傳遞:

// 希望延遲 3s 后執(zhí)行并拿到結(jié)果

function getAsyncResult(result: number{

  setTimeout(() => {

    return result * 3;

  }, 1000);

}



// 盡管這是常規(guī)的編程思維方式

const result = getAsyncResult(3000);

// 但是打印 undefined

console.log('result: ', result);



function getAsyncResultWithCb(result: number, cb: (result: number) => void{

  setTimeout(() => {

    cb(result * 3);

  }, 1000);

}



// 通過回調(diào)的形式獲取結(jié)果

getAsyncResultWithCb(3000(result) => {

  console.log('result: ', result); // 9000

});



對于 JavaScript 中標(biāo)準(zhǔn)的異步 API 可能無法通過在外部進(jìn)行 try...catch... 的方式進(jìn)行錯(cuò)誤捕獲:

try {

  setTimeout(() => {

    // 下述是異常代碼

    // 你可以在回調(diào)函數(shù)的內(nèi)部進(jìn)行 try...catch...

    console.log(a.b.c)

  }, 1000)



catch(err) {

  // 這里不會(huì)執(zhí)行

  // 進(jìn)程會(huì)被終止

  console.error(err)

}

上述示例講述的都是 JavaScript 中標(biāo)準(zhǔn)的異步 API ,如果使用一些三方的異步 API 并且提供了回調(diào)能力時(shí),這些 API 可能是非受信的,在真正使用的時(shí)候會(huì)因?yàn)?strong>執(zhí)行反轉(zhuǎn)(回調(diào)函數(shù)的執(zhí)行權(quán)在三方庫中)導(dǎo)致以下一些問題:

  • 使用者的回調(diào)函數(shù)設(shè)計(jì)沒有進(jìn)行錯(cuò)誤捕獲,而恰恰三方庫進(jìn)行了錯(cuò)誤捕獲卻沒有拋出錯(cuò)誤處理信息,此時(shí)使用者很難感知到自己設(shè)計(jì)的回調(diào)函數(shù)是否有錯(cuò)誤

  • 使用者難以感知到三方庫的回調(diào)時(shí)機(jī)和回調(diào)次數(shù),這個(gè)回調(diào)函數(shù)執(zhí)行的權(quán)利控制在三方庫手中

  • 使用者無法更改三方庫提供的回調(diào)參數(shù),回調(diào)參數(shù)可能無法滿足使用者的訴求

  • ...

舉個(gè)簡單的例子:

interface ILib<T> {

  params: T;

  emit(params: T): void;

  on(callback: (params: T) => void): void;

}



// 假設(shè)以下是一個(gè)三方庫,并發(fā)布成了npm 包

export const lib: ILib<string> = {

  params: '',



  emit(params) {

    this.params = params;

  },



  on(callback) {

    try {

      // callback 回調(diào)執(zhí)行權(quán)在 lib 上

      // lib 庫可以決定回調(diào)執(zhí)行多次

      callback(this.params);

      callback(this.params);

      callback(this.params);

      // lib 庫甚至可以決定回調(diào)延遲執(zhí)行

      // 異步執(zhí)行回調(diào)函數(shù)

      setTimeout(() => {

        callback(this.params);

      }, 3000);

    } catch (err) {

      // 假設(shè) lib 庫的捕獲沒有拋出任何異常信息

    }

  },

};



// 開發(fā)者引入 lib 庫開始使用

lib.emit('hello');



lib.on((value) => {

  // 使用者希望 on 里的回調(diào)只執(zhí)行一次

 // 這里的回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)是由三方庫 lib 決定

  // 實(shí)際上打印四次,并且其中一次是異步執(zhí)行

  console.log(value);

});



lib.on((value) => {

  // 下述是異常代碼

  // 但是執(zhí)行下述代碼不會(huì)拋出任何異常信息

  // 開發(fā)者無法感知自己的代碼設(shè)計(jì)錯(cuò)誤

  console.log(value.a.b.c)

});

Promise

Callback 的異步操作形式除了會(huì)造成回調(diào)地獄,還會(huì)造成難以測試的問題。ES6 中的 Promise (基于 Promise A +[19] 規(guī)范的異步編程解決方案)利用有限狀態(tài)機(jī)[20]的原理來解決異步的處理問題,Promise 對象提供了統(tǒng)一的異步編程 API,它的特點(diǎn)如下:

  • Promise 對象的執(zhí)行狀態(tài)不受外界影響。Promise 對象的異步操作有三種狀態(tài): pending(進(jìn)行中)、 fulfilled(已成功)和 rejected(已失?。?nbsp;,只有 Promise 對象本身的異步操作結(jié)果可以決定當(dāng)前的執(zhí)行狀態(tài),任何其他的操作無法改變狀態(tài)的結(jié)果

  • Promise 對象的執(zhí)行狀態(tài)不可變。Promise 的狀態(tài)只有兩種變化可能:從 pending(進(jìn)行中)變?yōu)?nbsp;fulfilled(已成功)或從 pending(進(jìn)行中)變?yōu)?nbsp;rejected(已失敗)

溫馨提示:有限狀態(tài)機(jī)提供了一種優(yōu)雅的解決方式,異步的處理本身可以通過異步狀態(tài)的變化來觸發(fā)相應(yīng)的操作,這會(huì)比回調(diào)函數(shù)在邏輯上的處理更加合理,也可以降低代碼的復(fù)雜度。

Promise 對象的執(zhí)行狀態(tài)不可變示例如下:

const promise = new Promise<number>((resolve, reject) => {

  // 狀態(tài)變更為 fulfilled 并返回結(jié)果 1 后不會(huì)再變更狀態(tài)

  resolve(1);

  // 不會(huì)變更狀態(tài)

  reject(4);

}
);



promise

  .then((result) => {

    // 在 ES 6 中 Promise 的 then 回調(diào)執(zhí)行是異步執(zhí)行(微任務(wù))

    // 在當(dāng)前 then 被調(diào)用的那輪事件循環(huán)(Event Loop)的末尾執(zhí)行

    console.log('result: ', result);

  }
)

  .catch((error) => {

    // 不執(zhí)行

    console.error('error: ', error);

  }
);

假設(shè)要實(shí)現(xiàn)兩個(gè)繼發(fā)的 HTTP 請求,第一個(gè)請求接口返回的數(shù)據(jù)是第二個(gè)請求接口的參數(shù),使用回調(diào)函數(shù)的實(shí)現(xiàn)方式如下所示(這里使用 setTimeout 來指代異步請求):

// 回調(diào)地獄

const doubble = (result: number, callback: (finallResult: number) => void) => {

  // Mock 第一個(gè)異步請求

  setTimeout(() => {

    // Mock 第二個(gè)異步請求(假設(shè)第二個(gè)請求的參數(shù)依賴第一個(gè)請求的返回結(jié)果)

    setTimeout(() => {

      callback(result * 2);

    }, 2000);

  }, 1000);

};



doubble(1000(result) => {

  console.log('result: ', result);

});

溫馨提示:繼發(fā)請求的依賴關(guān)系非常常見,例如人員基本信息管理系統(tǒng)的開發(fā)中,經(jīng)常需要先展示組織樹結(jié)構(gòu),并默認(rèn)加載第一個(gè)組織下的人員列表信息。

如果采用 Promise 的處理方式則可以規(guī)避上述常見的回調(diào)地獄問題:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    // Mock 異步請求

    // 將 resolve 改成 reject 會(huì)被 catch 捕獲

    setTimeout(() => resolve(result), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    // Mock 異步請求

    // 將 resolve 改成 reject 會(huì)被 catch 捕獲

    setTimeout(() => resolve(result * 2), 1000);

  });

};



firstPromise(1000)

  .then((result) => {

    return nextPromise(result);

  })

  .then((result) => {

    // 2s 后打印 2000

    console.log('result: ', result);

  })

  // 任何一個(gè) Promise 到達(dá) rejected 狀態(tài)都能被 catch 捕獲

  .catch((err) => {

    console.error('err: ', err);

  });

Promise 的錯(cuò)誤回調(diào)可以同時(shí)捕獲 firstPromise 和 nextPromise 兩個(gè)函數(shù)的 rejected 狀態(tài)。接下來考慮以下調(diào)用場景:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    // Mock 異步請求

    setTimeout(() => resolve(result), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    // Mock 異步請求

    setTimeout(() => resolve(result * 2), 1000);

  });

};



firstPromise(1000)

  .then((result) => {

    nextPromise(result).then((result) => {

      // 后打印

      console.log('nextPromise result: ', result);

    });

  })

  .then((result) => {

    // 先打印

    // 由于上一個(gè) then 沒有返回值,這里打印 undefined

    console.log('firstPromise result: ', result);

  })

  .catch((err) => {

    console.error('err: ', err);

  });

首先 Promise 可以注冊多個(gè) then(放在一個(gè)執(zhí)行隊(duì)列里),并且這些 then 會(huì)根據(jù)上一次返回值的結(jié)果依次執(zhí)行。除此之外,各個(gè) Promise 的 then 執(zhí)行互不干擾。  我們將示例進(jìn)行簡單的變換:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    // Mock 異步請求

    setTimeout(() => resolve(result), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    // Mock 異步請求

    setTimeout(() => resolve(result * 2), 1000);

  });

};



firstPromise(1000)

  .then((result) => {

    // 返回了 nextPromise 的 then 執(zhí)行后的結(jié)果

    return nextPromise(result).then((result) => {

      return result;

    });

  })

  // 接著 nextPromise 的 then 執(zhí)行的返回結(jié)果繼續(xù)執(zhí)行

  .then((result) => {

    // 2s 后打印 2000

    console.log('nextPromise result: ', result);

  })

  .catch((err) => {

    console.error('err: ', err);

  });



上述例子中的執(zhí)行結(jié)果是因?yàn)?nbsp;then 的執(zhí)行會(huì)返回一個(gè)新的 Promise 對象,并且如果 then 執(zhí)行后返回的仍然是 Promise 對象,那么下一個(gè) then 的鏈?zhǔn)秸{(diào)用會(huì)等待該 Promise 對象的狀態(tài)發(fā)生變化后才會(huì)調(diào)用(能得到這個(gè) Promise 處理的結(jié)果)。接下來重點(diǎn)看下 Promise 的錯(cuò)誤處理:

const promise = new Promise<string>((resolve, reject) => {

  // 下述是異常代碼

  console.log(a.b.c);

  resolve('hello');

}
);



promise

  .then((result) => {

    console.log('result: ', result);

  }
)

  // 去掉 catch 仍然會(huì)拋出錯(cuò)誤,但不會(huì)退出進(jìn)程終止腳本執(zhí)行

  .catch((err) => {

    // 執(zhí)行

    // ReferenceError: a is not defined

    console.error(err);

  }
);



setTimeout(() => {

  // 繼續(xù)執(zhí)行

  console.log('hello world!');

}, 2000
);

從上述示例可以看出 Promise 的錯(cuò)誤不會(huì)影響其他代碼的執(zhí)行,只會(huì)影響 Promise 內(nèi)部的代碼本身,因?yàn)?Promise 會(huì)在內(nèi)部對錯(cuò)誤進(jìn)行異常捕獲,從而保證整體代碼執(zhí)行的穩(wěn)定性。Promise 還提供了其他的一些 API 方便多任務(wù)的執(zhí)行,包括

  • Promise.all:適合多個(gè)異步任務(wù)并發(fā)執(zhí)行但不允許其中任何一個(gè)任務(wù)失敗

  • Promise.race :適合多個(gè)異步任務(wù)搶占式執(zhí)行

  • Promise.allSettled :適合多個(gè)異步任務(wù)并發(fā)執(zhí)行但允許某些任務(wù)失敗

Promise 相對于 Callback 對于異步的處理更加優(yōu)雅,并且能力也更加強(qiáng)大, 但是也存在一些自身的缺點(diǎn):

  • 無法取消 Promise 的執(zhí)行

  • 無法在 Promise 外部通過 try...catch... 的形式進(jìn)行錯(cuò)誤捕獲(Promise 內(nèi)部捕獲了錯(cuò)誤)

  • 狀態(tài)單一,每次決斷只能產(chǎn)生一種狀態(tài)結(jié)果,需要不停的進(jìn)行鏈?zhǔn)秸{(diào)用

溫馨提示:手寫 Promise 是面試官非常喜歡的一道筆試題,本質(zhì)是希望面試者能夠通過底層的設(shè)計(jì)正確了解 Promise 的使用方式,如果你對 Promise 的設(shè)計(jì)原理不熟悉,可以深入了解一下或者手動(dòng)設(shè)計(jì)一個(gè)。

Generator

Promise 解決了 Callback 的回調(diào)地獄問題,但也造成了代碼冗余,如果一些異步任務(wù)不支持 Promise 語法,就需要進(jìn)行一層 Promise 封裝。Generator 將 JavaScript 的異步編程帶入了一個(gè)全新的階段,它使得異步代碼的設(shè)計(jì)和執(zhí)行看起來和同步代碼一致。Generator 使用的簡單示例如下:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 2), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 3), 1000);

  });

};



// 在 Generator 函數(shù)里執(zhí)行的異步代碼看起來和同步代碼一致

functiongen(result: number): Generator<Promise<number>, Promise<number>, number{

  // 異步代碼

  const firstResult = yield firstPromise(result)

  console.log('firstResult: ', firstResult) // 2

 // 異步代碼

  const nextResult = yield nextPromise(firstResult)

  console.log('nextResult: ', nextResult) // 6

  return nextPromise(firstResult)

}



const g = gen(1)



// 手動(dòng)執(zhí)行 Generator 函數(shù)

g.next().value.then((res: number) => {

  // 將 firstPromise 的返回值傳遞給第一個(gè) yield 表單式對應(yīng)的 firstResult

  return g.next(res).value

}).then((res: number) => {

  // 將 nextPromise 的返回值傳遞給第二個(gè) yield 表單式對應(yīng)的 nextResult

  return g.next(res).value

})

通過上述代碼,可以看出 Generator 相對于 Promise 具有以下優(yōu)勢:

  • 豐富了狀態(tài)類型,Generator 通過 next 可以產(chǎn)生不同的狀態(tài)信息,也可以通過 return 結(jié)束函數(shù)的執(zhí)行狀態(tài),相對于 Promise 的 resolve 不可變狀態(tài)更加豐富

  • Generator 函數(shù)內(nèi)部的異步代碼執(zhí)行看起來和同步代碼執(zhí)行一致,非常利于代碼的維護(hù)

  • Generator 函數(shù)內(nèi)部的執(zhí)行邏輯和相應(yīng)的狀態(tài)變化邏輯解耦,降低了代碼的復(fù)雜度

next 可以不停的改變狀態(tài)使得 yield 得以繼續(xù)執(zhí)行的代碼可以變得非常有規(guī)律,例如從上述的手動(dòng)執(zhí)行 Generator 函數(shù)可以看出,完全可以將其封裝成一個(gè)自動(dòng)執(zhí)行的執(zhí)行器,具體如下所示:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 2), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 3), 1000);

  });

};



type Gen =  Generator<Promise<number>, Promise<number>, number>



functiongen(): Gen {

  const firstResult = yield firstPromise(1)

  console.log('firstResult: ', firstResult) // 2

  const nextResult = yield nextPromise(firstResult)

  console.log('nextResult: ', nextResult) // 6

  return nextPromise(firstResult)

}



// Generator 自動(dòng)執(zhí)行器

function co(gen: () => Gen{

  const g = gen()

  function next(data: number{

    const result = g.next(data)

    if(result.done) {

      return result.value

    }

    result.value.then(data => {

      // 通過遞歸的方式處理相同的邏輯

      next(data)

    })

  }

  // 第一次調(diào)用 next 主要用于啟動(dòng) Generator 函數(shù)

  // 內(nèi)部指針會(huì)從函數(shù)頭部開始執(zhí)行,直到遇到第一個(gè) yield 表達(dá)式

  // 因此第一次 next 傳遞的參數(shù)沒有任何含義(這里傳遞只是為了防止 TS 報(bào)錯(cuò))

  next(0)

}



co(gen)



溫馨提示:TJ Holowaychuk [21]設(shè)計(jì)了一個(gè) Generator 自動(dòng)執(zhí)行器 Co[22],使用 Co 的前提是 yield  命令后必須是 Promise 對象或者 Thunk 函數(shù)。Co 還可以支持并發(fā)的異步處理,具體可查看官方的 API 文檔[23]。

需要注意的是 Generator 函數(shù)的返回值是一個(gè) Iterator 遍歷器對象,具體如下所示:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 2), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 3), 1000);

  });

};



type Gen = Generator<Promise<number>>;



functiongen(): Gen {

  yield firstPromise(1);

  yield nextPromise(2);

}



// 注意使用 next 是繼發(fā)執(zhí)行,而這里是并發(fā)執(zhí)行

Promise.all([...gen()]).then((res) => {

  console.log('res: ', res);

});



for (const promise of gen()) {

  promise.then((res) => {

    console.log('res: ', res);

  });

}

Generator 函數(shù)的錯(cuò)誤處理相對復(fù)雜一些,極端情況下需要對執(zhí)行和 Generator 函數(shù)進(jìn)行雙重錯(cuò)誤捕獲,具體如下所示:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    // 需要注意這里的reject 沒有被捕獲

    setTimeout(() => reject(result * 2), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 3), 1000);

  });

};



type Gen = Generator<Promise<number>>;



functiongen(): Gen {

  try {

    yield firstPromise(1);

    yield nextPromise(2);

  } catch (err) {

    console.error('Generator 函數(shù)錯(cuò)誤捕獲: ', err);

  }

}



try {

  const g = gen();

  g.next();

  // 返回 Promise 后還需要通過 Promise.prototype.catch 進(jìn)行錯(cuò)誤捕獲

  g.next();

  // Generator 函數(shù)錯(cuò)誤捕獲

  g.throw('err');

  // 執(zhí)行器錯(cuò)誤捕獲

  g.throw('err');

catch (err) {

  console.error('執(zhí)行錯(cuò)誤捕獲: ', err);

}

在使用 g.throw 的時(shí)候還需要注意以下一些事項(xiàng):

  • 如果 Generator 函數(shù)本身沒有捕獲錯(cuò)誤,那么 Generator 函數(shù)內(nèi)部拋出的錯(cuò)誤可以在執(zhí)行處進(jìn)行錯(cuò)誤捕獲

  • 如果 Generator 函數(shù)內(nèi)部和執(zhí)行處都沒有進(jìn)行錯(cuò)誤捕獲,則終止進(jìn)程并拋出錯(cuò)誤信息

  • 如果沒有執(zhí)行過 g.next,則 g.throw 不會(huì)在 Gererator 函數(shù)中被捕獲(因?yàn)閳?zhí)行指針沒有啟動(dòng) Generator 函數(shù)的執(zhí)行),此時(shí)可以在執(zhí)行處進(jìn)行執(zhí)行錯(cuò)誤捕獲

Async

Async 是 Generator 函數(shù)的語法糖,相對于 Generator 而言 Async 的特性如下:

  • 內(nèi)置執(zhí)行器:Generator 函數(shù)需要設(shè)計(jì)手動(dòng)執(zhí)行器或者通用執(zhí)行器(例如 Co 執(zhí)行器)進(jìn)行執(zhí)行,Async 語法則內(nèi)置了自動(dòng)執(zhí)行器,設(shè)計(jì)代碼時(shí)無須關(guān)心執(zhí)行步驟

  • yield 命令無約束:在 Generator 中使用 Co 執(zhí)行器時(shí) yield 后必須是 Promise 對象或者 Thunk 函數(shù),而 Async 語法中的 await 后可以是 Promise 對象或者原始數(shù)據(jù)類型對象、數(shù)字、字符串、布爾值等(此時(shí)會(huì)對其進(jìn)行 Promise.resolve() 包裝處理)

  • 返回 Promise: async 函數(shù)的返回值是 Promise 對象(返回原始數(shù)據(jù)類型會(huì)被 Promise 進(jìn)行封裝),  因此還可以作為 await   的命令參數(shù),相對于 Generator 返回 Iterator 遍歷器更加簡潔實(shí)用

舉個(gè)簡單的示例:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 2), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 3), 1000);

  });

};



async function co({

  const firstResult = await firstPromise(1);

  // 1s 后打印 2

  console.log('firstResult: ', firstResult);

  // 等待 firstPromise 的狀態(tài)發(fā)生變化后執(zhí)行

  const nextResult = await nextPromise(firstResult);

  // 2s 后打印 6

  console.log('nextResult: ', nextResult);

  return nextResult;

}



co();



co().then((res) => {

  console.log('res: ', res); // 6

});

通過上述示例可以看出,async 函數(shù)的特性如下:

  • 調(diào)用 async 函數(shù)后返回的是一個(gè) Promise 對象,通過 then 回調(diào)可以拿到 async 函數(shù)內(nèi)部 return 語句的返回值

  • 調(diào)用 async 函數(shù)后返回的 Promise 對象必須等待內(nèi)部所有 await 對應(yīng)的 Promise 執(zhí)行完(這使得 async 函數(shù)可能是阻塞式執(zhí)行)后才會(huì)發(fā)生狀態(tài)變化,除非中途遇到了 return 語句

  • await 命令后如果是 Promise 對象,則返回 Promise 對象處理后的結(jié)果,如果是原始數(shù)據(jù)類型,則直接返回原始數(shù)據(jù)類型

上述代碼是阻塞式執(zhí)行,nextPromise 需要等待 firstPromise 執(zhí)行完成后才能繼續(xù)執(zhí)行,如果希望兩者能夠并發(fā)執(zhí)行,則可以進(jìn)行下述設(shè)計(jì):

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 2), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 3), 1000);

  });

};



async function co({

  return await Promise.all([firstPromise(1), nextPromise(1)]);

}



co().then((res) => {

  console.log('res: ', res); // [2,3]

});



除了使用 Promise 自帶的并發(fā)執(zhí)行 API,也可以通過讓所有的 Promise 提前并發(fā)執(zhí)行來處理:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    console.log('firstPromise');

    setTimeout(() => resolve(result * 2), 10000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    console.log('nextPromise');

    setTimeout(() => resolve(result * 3), 1000);

  });

};



async function co({

  // 執(zhí)行 firstPromise

  const first = firstPromise(1);

  // 和 firstPromise 同時(shí)執(zhí)行 nextPromise

  const next = nextPromise(1);

  // 等待 firstPromise 結(jié)果回來

  const firstResult = await first;

  console.log('firstResult: ', firstResult);

  // 等待 nextPromise 結(jié)果回來

  const nextResult = await next;

  console.log('nextResult: ', nextResult);

  return nextResult;

}



co().then((res) => {

  console.log('res: ', res); // 3

});

Async 的錯(cuò)誤處理相對于 Generator 會(huì)更加簡單,具體示例如下所示:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    // Promise 決斷錯(cuò)誤

    setTimeout(() => reject(result * 2), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 3), 1000);

  });

};



async function co({

  const firstResult = await firstPromise(1);

  console.log('firstResult: ', firstResult);

  const nextResult = await nextPromise(1);

  console.log('nextResult: ', nextResult);

  return nextResult;

}



co()

  .then((res) => {

    console.log('res: ', res);

  })

  .catch((err) => {

    console.error('err: ', err); // err: 2

  });

async 函數(shù)內(nèi)部拋出的錯(cuò)誤,會(huì)導(dǎo)致函數(shù)返回的 Promise 對象變?yōu)?nbsp;rejected 狀態(tài),從而可以通過 catch 捕獲,  上述代碼只是一個(gè)粗粒度的容錯(cuò)處理,如果希望 firstPromise 錯(cuò)誤后可以繼續(xù)執(zhí)行 nextPromise,則可以通過 try...catch... 在 async 函數(shù)里進(jìn)行局部錯(cuò)誤捕獲:

const firstPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    // Promise 決斷錯(cuò)誤

    setTimeout(() => reject(result * 2), 1000);

  });

};



const nextPromise = (result: number): Promise<number> => {

  return new Promise((resolve, reject) => {

    setTimeout(() => resolve(result * 3), 1000);

  });

};



async function co({

  try {

    await firstPromise(1);

  } catch (err) {

    console.error('err: ', err); // err: 2

  }



  // nextPromise 繼續(xù)執(zhí)行

  const nextResult = await nextPromise(1);

  return nextResult;

}



co()

  .then((res) => {

    console.log('res: ', res); // res: 3

  })

  .catch((err) => {

    console.error('err: ', err);

  });

溫馨提示:Callback 是 Node.js 中經(jīng)常使用的編程方式,Node.js 中很多原生的 API 都是采用 Callback 的形式進(jìn)行異步設(shè)計(jì),早期的 Node.js 經(jīng)常會(huì)有 Callback 和 Promise 混用的情況,并且在很長一段時(shí)間里都沒有很好的支持 Async 語法。如果你對 Node.js 和它的替代品 Deno 感興趣,可以觀看 Ryan Dahl 在 TS Conf 2019 中的經(jīng)典演講 Deno is a New Way to JavaScript[24]。

31、 Object.defineProperty 有哪幾個(gè)參數(shù)?各自都有什么作用?

32、 Object.defineProperty 和 ES6 的 Proxy 有什么區(qū)別?

閱讀鏈接:基于 Vue 實(shí)現(xiàn)一個(gè) MVVM[25] - 數(shù)據(jù)劫持的實(shí)現(xiàn)。

33、 ES6 中 Symbol、Map、Decorator 的使用場景有哪些?或者你在哪些庫的源碼里見過這些 API 的使用?

34、 為什么要使用 TypeScript ? TypeScript 相對于 JavaScript 的優(yōu)勢是什么?

35、 TypeScript 中 const 和 readonly 的區(qū)別?枚舉和常量枚舉的區(qū)別?接口和類型別名的區(qū)別?

具體分析待補(bǔ),先放個(gè)鏈接

閱讀鏈接:https://www.typescriptlang.org/docs/handbook/interfaces.html#readonly-properties

36、 TypeScript 中 any 類型的作用是什么?

37、 TypeScript 中 any、never、unknown 和 void 有什么區(qū)別?

具體分析待補(bǔ),先放個(gè)鏈接

閱讀鏈接:https://www.typescriptlang.org/docs/handbook/basic-types.html#any

38、 TypeScript 中 interface 可以給 Function / Array / Class(Indexable)做聲明嗎?

具體分析待補(bǔ),先放個(gè)鏈接

閱讀鏈接:

  • Interface 可以給 Function(Object) 做聲明:https://www.typescriptlang.org/docs/handbook/interfaces.html#function-types
  • Interface 可以給 Indexable (Array)做聲明:https://www.typescriptlang.org/docs/handbook/interfaces.html#indexable-types
  • Interface 可以給 Class 做聲明:https://www.typescriptlang.org/docs/handbook/interfaces.html#difference-between-the-static-and-instance-sides-of-classes

39、 TypeScript 中可以使用 String、Number、Boolean、Symbol、Object 等給類型做聲明嗎?

40、 TypeScript 中的 this 和 JavaScript 中的 this 有什么差異?

41、 TypeScript 中使用 Unions 時(shí)有哪些注意事項(xiàng)?

42、 TypeScript 如何設(shè)計(jì) Class 的聲明?

具體分析待補(bǔ),先放個(gè)鏈接

閱讀鏈接:

  • Class 類型聲明:https://www.typescriptlang.org/docs/handbook/classes.html#constructor-functions
  • Interface 可以給 Class 做聲明:https://www.typescriptlang.org/docs/handbook/interfaces.html#difference-between-the-static-and-instance-sides-of-classes

43、 TypeScript 中如何聯(lián)合枚舉類型的 Key?






#### 44、 TypeScript 中 ?.、??、!.、_、** 等符號(hào)的含義?

#### 45、 TypeScript 中預(yù)定義的有條件類型有哪些?

#### 46、 簡單介紹一下 TypeScript 模塊的加載機(jī)制?

#### 47、 簡單聊聊你對 TypeScript 類型兼容性的理解?抗變、雙變、協(xié)變和逆變的簡單理解?

#### 48、 TypeScript 中對象展開會(huì)有什么副作用嗎?

#### 49、 TypeScript 中 interface、type、enum 聲明有作用域的功能嗎?

#### 50、 TypeScript 中同名的 interface 或者同名的 interface 和 class 可以合并嗎?

#### 51、 如何使 TypeScript 項(xiàng)目引入并識(shí)別編譯為 JavaScript 的 npm 庫包?

#### 52、 TypeScript 的 tsconfig.json 中有哪些配置項(xiàng)信息?

#### 53、 TypeScript 中如何設(shè)置模塊導(dǎo)入的路徑別名?





### 框架





#### 54、 React Class 組件有哪些周期函數(shù)?分別有什么作用?

#### 55、 React Class 組件中請求可以在 componentWillMount 中發(fā)起嗎?為什么?







#### 56、 React Class 組件和 React Hook 的區(qū)別有哪些?

#### 57、 React 中高階函數(shù)和自定義 Hook 的優(yōu)缺點(diǎn)?

#### 58、 簡要說明 React Hook 中 useState 和 useEffect 的運(yùn)行原理?

#### 59、 React 如何發(fā)現(xiàn)重渲染、什么原因容易造成重渲染、如何避免重渲染?

#### 60、 React Hook 中 useEffect 有哪些參數(shù),如何檢測數(shù)組依賴項(xiàng)的變化?

#### 61、 React 的 useEffect 是如何監(jiān)聽數(shù)組依賴項(xiàng)的變化的?

#### 62、 React Hook 和閉包有什么關(guān)聯(lián)關(guān)系?

#### 63、 React 中 useState 是如何做數(shù)據(jù)初始化的?

#### 64、 列舉你常用的 React 性能優(yōu)化技巧?

#### 65、 Vue 2.x 模板中的指令是如何解析實(shí)現(xiàn)的?

#### 66、 簡要說明 Vue 2.x 的全鏈路運(yùn)作機(jī)制?

#### 67、 簡單介紹一下 Element UI 的框架設(shè)計(jì)?

#### 68、 如何理解 Vue 是一個(gè)漸進(jìn)式框架?

#### 69、 Vue 里實(shí)現(xiàn)跨組件通信的方式有哪些?

#### 70、 Vue 中響應(yīng)式數(shù)據(jù)是如何做到對某個(gè)對象的深層次屬性的監(jiān)聽的?

#### 71、 MVVM、MVC 和 MVP 的區(qū)別是什么?各自有什么應(yīng)用場景?、

#### 72、 什么是 MVVM 框架?







### 工程





#### 73、Vue CLI 3.x 有哪些功能?Vue CLI 3.x 的插件系統(tǒng)了解?

#### 74、Vue CLI 3.x 中的 Webpack 是如何組裝處理的?

#### 75、Vue 2.x 如何支持 TypeScript 語法?

#### 76、如何配置環(huán)境使得 JavaScript 項(xiàng)目可以支持 TypeScript 語法?

#### 77、如何對 TypeScript 進(jìn)行 Lint 校驗(yàn)?ESLint 和 TSLint 有什么區(qū)別?

#### 78、Node.js 如何支持 TypeScript 語法?

#### 79、TypeScript 如何自動(dòng)生成庫包的聲明文件?

#### 80、Babel 對于 TypeScript 的支持有哪些限制?

#### 81、Webpack 中 Loader 和 Plugin 的區(qū)別是什么?

#### 82、在 Webpack 中是如何做到支持類似于 JSX 語法的 Sourcemap 定位?

#### 83、發(fā)布 Npm 包如何指定引入地址?

#### 84、如何發(fā)布開發(fā)項(xiàng)目的特定文件夾為 Npm 包的根目錄?

#### 85、如何發(fā)布一個(gè)支持 Tree Shaking 機(jī)制的 Npm 包?

#### 86、Npm 包中 peerDependencies 的作用是什么?

#### 87、如何優(yōu)雅的調(diào)試需要發(fā)布的 Npm 包?

#### 88、在設(shè)計(jì)一些庫包時(shí)如何生成版本日志?

#### 89、了解 Git (Submodule)子模塊嗎?簡單介紹一下 Git 子模塊的作用?

#### 90、Git 如何修改已經(jīng)提交的 Commit 信息?

#### 91、Git 如何撤銷 Commit 并保存之前的修改?

#### 92、Git 如何 ignore 被 commit 過的文件?

#### 93、在使用 Git 的時(shí)候如何規(guī)范 Git 的提交說明(Commit 信息)?

#### 94、簡述符合 Angular 規(guī)范的提交說明的結(jié)構(gòu)組成?

#### 95、Commit 信息如何和 Github Issues 關(guān)聯(lián)?

#### 96、Git Hook 在項(xiàng)目中哪些作用?

#### 97、Git Hook 中客戶端和服務(wù)端鉤子各自用于什么作用?

#### 98、Git Hook 中常用的鉤子有哪些?

#### 99、pre-commit 和 commit-msg 鉤子的區(qū)別是什么?各自可用于做什么?

#### 100、husky 以及 ghook 等工具制作 Git Hook 的原理是什么?

#### 101、如何設(shè)計(jì)一個(gè)通用的 Git Hook ?

#### 102、Git Hook 可以采用 Node 腳本進(jìn)行設(shè)計(jì)嗎?如何做到?

#### 103、如何確保別人上傳的代碼沒有 Lint 錯(cuò)誤?如何確保代碼構(gòu)建沒有 Lint 錯(cuò)誤?

#### 104、如何在 Vs Code 中進(jìn)行 Lint 校驗(yàn)提示?如何在 Vs Code 中進(jìn)行 Lint 保存格式化?

#### 105、ESLint 和 Prettier 的區(qū)別是什么?兩者在一起工作時(shí)會(huì)產(chǎn)生問題嗎?

#### 106、如何有效的識(shí)別 ESLint 和 Prettier 可能產(chǎn)生沖突的格式規(guī)則?如何解決此類規(guī)則沖突問題?

#### 107、在通常的腳手架項(xiàng)目中進(jìn)行熱更新(hot module replacement)時(shí)如何做到 ESLint 實(shí)時(shí)打印校驗(yàn)錯(cuò)誤信息?

#### 108、談?wù)勀銓?SourceMap 的了解?

#### 109、如何調(diào)試 Node.js 代碼?如何調(diào)試 Node.js TypeScript 代碼?在瀏覽器中如何調(diào)試 Node.js 代碼?

#### 110、列舉你知道的所有構(gòu)建工具并說說這些工具的優(yōu)缺點(diǎn)?這些構(gòu)建工具在不同的場景下應(yīng)該如何選型?

#### 111、VS Code 配置中的用戶和工作區(qū)有什么區(qū)別?

#### 112、VS Code 的插件可以只對當(dāng)前項(xiàng)目生效嗎?

#### 113、你所知道的測試有哪些測試類型?

#### 114、你所知道的測試框架有哪些?

#### 115、什么是 e2e 測試?有哪些 e2e 的測試框架?

#### 116、假設(shè)現(xiàn)在有一個(gè)插入排序算法,如何對該算法進(jìn)行單元測試?







### 網(wǎng)絡(luò)





#### 117、CDN 服務(wù)如何實(shí)現(xiàn)網(wǎng)絡(luò)加速?

#### 118、WebSocket 使用的是 TCP 還是 UDP 協(xié)議?

#### 119、什么是單工、半雙工和全雙工通信?

#### 120、簡單描述 HTTP 協(xié)議發(fā)送一個(gè)帶域名的 URL 請求的協(xié)議傳輸過程?(DNS、TCP、IP、鏈路)

#### 121、什么是正向代理?什么是反向代理?

#### 122、Cookie 可以在服務(wù)端生成嗎?Cookie 在服務(wù)端生成后的工作流程是什么樣的?

#### 123、Session、Cookie 的區(qū)別和關(guān)聯(lián)?如何進(jìn)行臨時(shí)性和永久性的 Session 存儲(chǔ)?

#### 124、設(shè)置 Cookie 時(shí)候如何防止 XSS 攻擊?

#### 125、簡單描述一下用戶免登陸的實(shí)現(xiàn)過程?可能會(huì)出現(xiàn)哪些安全性問題?一般如何對用戶登錄的密碼進(jìn)行加密?

#### 126、HTTP 中提升傳輸速率的方式有哪些?常用的內(nèi)容編碼方式有哪些?

#### 127、傳輸圖片的過程中如果突然中斷,如何在恢復(fù)后從之前的中斷中恢復(fù)傳輸?

#### 128、什么是代理?什么是網(wǎng)關(guān)?代理和網(wǎng)關(guān)的作用是什么?

#### 129、HTTPS 相比 HTTP 為什么更加安全可靠?

#### 130、什么是對稱密鑰(共享密鑰)加密?什么是非對稱密鑰(公開密鑰)加密?哪個(gè)更加安全?

#### 131、你覺得 HTTP 協(xié)議目前存在哪些缺點(diǎn)?







### 性能





#### 133、在 React 中如何識(shí)別一個(gè)表單項(xiàng)里的表單做到了最小粒度 / 代價(jià)的渲染?

#### 134、在 React 的開發(fā)的過程中你能想到哪些控制渲染成本的方法?







### 插件





#### 135、Vue CLI 3.x 的插件系統(tǒng)是如何設(shè)計(jì)的?

#### 136、Webpack 中的插件機(jī)制是如何設(shè)計(jì)的?







### 系統(tǒng)





#### 137、\r\n(CRLF) 和 \n (LF)的區(qū)別是什么?(Vs Code 的右下角可以切換)

#### 138、/dev/null 的作用是啥?

#### 139、如何在 Mac 的終端中設(shè)置一個(gè)命令的別名?

#### 140、如何在 Windows 中設(shè)置環(huán)境變量?

#### 141、Mac 的文件操作系統(tǒng)默認(rèn)區(qū)分文件路徑的大小寫嗎?

#### 142、編寫 Shell 腳本時(shí)如何設(shè)置文件的絕對路徑?







### 后端





#### 143、Session、Cookie 的區(qū)別和關(guān)聯(lián)?如何進(jìn)行臨時(shí)性和永久性的 Session 存儲(chǔ)?

#### 144、如何部署 Node.js 應(yīng)用?如何處理負(fù)載均衡中 Session 的一致性問題?

#### 145、如何提升 Node.js 代碼的運(yùn)行穩(wěn)定性?

#### 146、GraphQL 與 Restful 的區(qū)別,它有什么優(yōu)點(diǎn)?

#### 147、Vue SSR 的工作原理?Vuex 的數(shù)據(jù)如何同構(gòu)渲染?

#### 148、SSR 技術(shù)和 SPA 技術(shù)的各自的優(yōu)缺點(diǎn)是什么?

#### 149、如何處理 Node.js 渲染 HTML 壓力過大問題?











## 業(yè)務(wù)思考





業(yè)務(wù)思考更多的是結(jié)合基礎(chǔ)知識(shí)的廣度和深度進(jìn)行的具體業(yè)務(wù)實(shí)踐,主要包含以下幾個(gè)方面:







- 工程化:代碼部署、CI / CD 流程設(shè)計(jì)、Jenkins、Gitlab、Docker 等

- 通用性:腳手架、SDK、組件庫等框架設(shè)計(jì)

- 應(yīng)用框架:Hybrid 混合、微前端、BFF、Monorepo

- 可視化:

- 低代碼:通用表單設(shè)計(jì)、通用布局設(shè)計(jì)、通用頁面設(shè)計(jì)、JSON Schema 協(xié)議設(shè)計(jì)等

- 測試:E2E 測試、單元測試、測試覆蓋率、測試報(bào)告等

- 業(yè)務(wù):數(shù)據(jù)、體驗(yàn)、復(fù)雜度、監(jiān)控







### 工程化





#### 150、你所知道的 CI / CD 工具有哪些?在項(xiàng)目中有接觸過類似的流程嗎?

#### 151、如果讓你實(shí)現(xiàn)一個(gè) Web 前端的 CI / CD 工程研發(fā)平臺(tái),你會(huì)如何設(shè)計(jì)?

#### 152、如果我們需要將已有項(xiàng)目中的線上產(chǎn)物資源(例如圖片)轉(zhuǎn)換成本地私有化資源,你有什么解決方案?

#### 153、如何使用 Vue CLI 3.x 定制一個(gè)腳手架?比如內(nèi)部自動(dòng)集成了 i18n、 axios、Element UI、路由守衛(wèi)等?

#### 154、Jenkins 如何配合 Node.js 腳本進(jìn)行 CI / CD 設(shè)計(jì)?







### 通用性





#### 155、如果讓你設(shè)計(jì)一個(gè)通用的項(xiàng)目腳手架,你會(huì)如何設(shè)計(jì)?一個(gè)通用的腳手架一般需要具備哪些能力?

#### 156、如果讓你設(shè)計(jì)一個(gè)通用的工具庫,你會(huì)如何設(shè)計(jì)?一個(gè)通用的工具庫一般需要具備哪些能力?

#### 157、假設(shè)你自己實(shí)現(xiàn)的 React 或 Vue 的組件庫要設(shè)計(jì)演示文檔,你會(huì)如何設(shè)計(jì)?設(shè)計(jì)的文檔需要實(shí)現(xiàn)哪些功能?

#### 158、在設(shè)計(jì)工具庫包的時(shí)候你是如何設(shè)計(jì) API 文檔的?







### 應(yīng)用框架





#### 159、談?wù)?Electron、Nw.js、CEF、Flutter 和原生開發(fā)的理解?

#### 160、談?wù)勛烂娑藨?yīng)用中 HotFix 的理解?

#### 161、你覺得什么樣的場景需要使用微前端框架?







### 業(yè)務(wù)





#### 162、什么是單點(diǎn)登錄?如何做單點(diǎn)登錄?

#### 163、如何做一個(gè)項(xiàng)目的國際化方案?

#### 164、如何做一個(gè)項(xiàng)目的監(jiān)控和埋點(diǎn)方案?

#### 165、如何建設(shè)項(xiàng)目的穩(wěn)定性(監(jiān)控、灰度、錯(cuò)誤降級、回滾...)?

#### 166、一般管理后臺(tái)型的應(yīng)用需要考慮哪些性能方面的優(yōu)化?

#### 167、簡述一些提升項(xiàng)目體驗(yàn)的案例和技術(shù)方案(骨架屏、Loading 處理、緩存、錯(cuò)誤降級、請求重試...)?

#### 168、假設(shè)需要對頁面設(shè)計(jì)一個(gè)水印方案,你會(huì)如何設(shè)計(jì)?











### 低代碼





#### 169、如何設(shè)計(jì)一個(gè)通用的 JSON Schema 協(xié)議使其可以動(dòng)態(tài)渲染一個(gè)通用的聯(lián)動(dòng)表單?

#### 170、一般的低代碼平臺(tái)需要具備哪些能力?











## 筆試實(shí)踐





筆試更多的是考驗(yàn)應(yīng)聘者的邏輯思維能力和代碼書寫風(fēng)格,主要包含以下幾個(gè)方面:







- 正則表達(dá)式

- 算法

- 數(shù)據(jù)結(jié)構(gòu)

- 設(shè)計(jì)模式

- 框架的部分原理實(shí)現(xiàn)

- TypeScript 語法

- 模板解析







### 數(shù)據(jù)結(jié)構(gòu)





#### 171、使用 TypeScript 語法將沒有層級的扁平數(shù)據(jù)轉(zhuǎn)換成樹形結(jié)構(gòu)的數(shù)據(jù)

```javascript

// 扁平數(shù)據(jù)

[{

  name: '文本1',

  parent: null,

  id: 1,

}, {

  name: '文本2',

  id: 2,

  parent: 1

}, {

  name: '文本3',

  parent: 2,

  id: 3,

}]



// 樹狀數(shù)據(jù)

[{

  name: '文本1',

  id: 1,

  children: [{

    name: '文本2',

    id: 2,

    children: [{

      name: '文本3',

      id: 3

    }]

  }]

}]

模板解析

172、實(shí)現(xiàn)一個(gè)簡易的模板引擎

const template = '嗨,{{ info.name.value }}您好,今天是星期 {{ day.value }}';



const data = {

  info: {

    name: {

      value'張三'

    }

  },

  day: {

    value'三'

  }

};



render(template, data); // 嗨,張三您好,今天是星期三

設(shè)計(jì)模式

173、簡單實(shí)現(xiàn)一個(gè)發(fā)布 / 訂閱模式

正則表達(dá)式

174、匹配出字符串中 const a = require('xxx') 中的 xxx

參考資料

[1] 

專有釘釘前端面試指南: https://juejin.cn/post/6986436944913924103

[2] 

面試分享:兩年工作經(jīng)驗(yàn)成功面試阿里 P6 總結(jié): https://juejin.cn/post/6844903928442667015#heading-43

[3] 

三地址碼(Three Address Code, TAC): https://en.wikipedia.org/wiki/Three-address_code

[4] 

靜態(tài)單賦值形式(Static Single Assignment Form, SSA): https://en.wikipedia.org/wiki/Static_single_assignment_form

[5] 

基于 Vue 實(shí)現(xiàn)一個(gè)簡易 MVVM : https://juejin.cn/post/6844904099704471559#heading-10

[6] 

What is the precise definition of programming paradigm?: https://softwareengineering.stackexchange.com/questions/166442/what-is-the-precise-definition-of-programming-paradigm#

[7] 

簡明 JavaScript 函數(shù)式編程——入門篇: https://juejin.cn/post/6844903936378273799

[8] 

函數(shù)式編程指北: https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/

[9] 

Web console: https://developer.mozilla.org/en-US/docs/Tools/Web_Console

[10] 

事件循環(huán)機(jī)制: https://juejin.cn/post/6844903843197616136#heading-3

[11] 

EventEmitter: http://nodejs.cn/api/events.html#events_class_eventemitter

[12] 

Stream: http://nodejs.cn/api/stream.html

[13] 

Promise: https://es6.ruanyifeng.com/#docs/promise

[14] 

Generator: https://es6.ruanyifeng.com/#docs/generator-async

[15] 

Async: https://es6.ruanyifeng.com/#docs/async

[16] 

Q: https://github.com/kriskowal/q

[17] 

Co、: https://github.com/tj/co

[18] 

Bluebird: https://github.com/petkaantonov/bluebird

[19] 

Promise A +: https://promisesaplus.com/

[20] 

有限狀態(tài)機(jī): http://www.ruanyifeng.com/blog/2013/09/finite-state_machine_for_javascript.html

[21] 

TJ Holowaychuk : https://github.com/tj

[22] 

Co: https://github.com/tj/co

[23] 

API 文檔: https://github.com/tj/co#arrays

[24] 

Deno is a New Way to JavaScript: https://www.youtube.com/watch?v=1gIiZfSbEAE

[25] 

基于 Vue 實(shí)現(xiàn)一個(gè) MVVM: https://juejin.cn/post/6844904099704471559#heading-23



內(nèi)推社群


我組建了一個(gè)氛圍特別好的騰訊內(nèi)推社群,如果你對加入騰訊感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時(shí)候隨時(shí)幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


瀏覽 35
點(diǎn)贊
評論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)
評論
圖片
表情
推薦
點(diǎn)贊
評論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 丁香五月婷婷在线| 东京热观看| 日韩无码一卡| 无码高清视频在线观看| 美女网站永久免费观看| 亚洲二区视频| 91人妻无码精品一区二区三区| 日韩A片在线| 黑人精品欧美一区二区蜜桃| 精东影业AV无码精品| 在线a | 国产成人精品a视频一区| 日日夜夜精选视频| 人人澡超碰碰| 亚洲黄片免费在线观看| 99热99精品| 成人无码毛片| 日韩AV一区二区三区| 三洞齐开Av在线免费观看| 无码精品黄色片| 操逼电影免费| 91在线无码精品入口电车| 欧美AAAAAAAA| 日韩一级片网站| 国产做受91电影| 人妻乱码| 国产无码激情| 色婷婷在线播放| 国产在线成人视频| 日本视频在线免费| 精东av| 欧美成人在线视频网站| 特级西西44www无码| 国产AV无码成人精品毛片| 久久99九九| 国产一级婬片A片免费妖精视频| 超碰在线网| 大蕉伊人网| 人人艹在线| 91.www91成人影视在线观看91成人网址9 | 亚洲香蕉| 尤物视频在线播放| 青草青青视频| 国产系列第一页| 午夜资源网| 亚洲AV无码成人| 免费看一级无码成人片| 欧美日逼视频| 国产麻豆免费| 91麻豆免费视频| 亚洲成人视频免费观看| 六月婷婷五月丁香| 男人的天堂视频在线观看| 日本黄色视频大全| 69色综合| 午夜黄色| 无码中文综合成熟精品AV电影| 一区二区三区四区五区| 大香蕉久热| 俺来也俺去也| 久艹99| 国产精品综合| 无码AV电影| 内射毛片| 操B视频在线| 大香蕉午夜视频| av片在线免费观看| 四虎永久在线精品无码| 热热热热色| 日韩一级成人片| 国产精品午夜福利| 伊人三区| 91成人久久| 亚洲色欲色欲www在线成人网| 亚洲第一黄色| 免费高清无码视频在线观看| 日韩午夜电影| 人人色人人爱| 久久老熟女| 中文字幕东京热| 国产精品一级a毛一级a| 久久精品99| 国产天堂av| 亚洲熟妇视频| 超碰在线观看97| 欧美日韩一区二区三区四区五区六区| 色情一级AA片免费观看| 大色欧美| 日韩精品一区二区三区四区| 久久精品免费电影| 黄色片a| 黄色视频网站亚洲| 亚l洲视频在线观看| 在线中文字幕网站| 国产人成视频| 无码国产视频| 日韩无码18| av婷婷五月天| 婷婷色导航| 成年人AV| 一本大道DVD中文字幕| 俺去俺来也www色视频| 欧美va视频| 少妇bbb搡bbbb搡bbbb| 黄片视频免费在线观看| 国产黄色在线看| 欧美中出| 久草在线资源| 久久不卡视频| 人人妻人人澡人人爽久久| 欧美精品亚洲| 婷婷久久五月天| 福利导航视频| 操操影视| 69久久成人精品| 69式荫蒂被添全过程频| 国产黄色视频观看| 可以免费观看的av| 兔子先生和優奈玩游戲脫衣服,運氣報表優奈輸到脫精光 | 免费看无码网站成人A片| 国产在线性爱视频| 精品视频| 天天日天天日天天操| 欧美一二| 伊人婷婷久久| 亚洲成av人无码| 久久久久久久久久久久久久久久久久免费精品分类视频 | 色99视频| 色福利网| 亚洲无码在线视频播放| 欧美一区三区视频z| 黄片小视频在线观看| 就去色色五月丁香婷婷久久久| 久操婷婷| 黃色A片一級二級三級免費久久久 亚洲AV无码第一区二区三区蜜桃 亚洲综合免费观看高清完整版在线 | 大地影视官网第三页入口| 天天撸天天操| 豆花视频一区二区| 成人国产精品秘久久久网站| 国产三级网址| 午夜性爽视频男人的天堂| 在线免费观看视频黄| 天天搞天天曰在线观看| 在线观看亚洲专区| 色色色成人视频| 精品久久久国产| 欧美成人网站视频| 456亚洲影院| 天堂成人AV| 欧美视频精品| 九一无码| 日本一级A片| 国产精品AV在线观看| 亚洲欧美在线综合| 91丨PORNY丨丰满人妻网站 | 日韩无码播放| 蜜臀久久99精品久久久| 国产精品久久久| 日韩精品久久久| 黄色视频网站亚洲| 伊人久色| 操B影院| 亚洲色图15p| 激情五月天视频| 91要爱爱| 在线永久看片免费的视频| 国产人成| 肏屄视频在线观看| 操逼视频观看| 岛国电影av| 免费黄片视频大全| 69天堂| 性满足BBWBBWBBW| 久久久在线视频| 三级网站在线播放| 国产高清久久| 欧美黄色免费| 亚洲无码在线免费观看视频| 羽月希奶水饱胀在线播放| 色玉米地熟妇| 三级亚洲| 国产亚洲无码激情| 大香蕉av一区二区三区在线观看| 麻豆av在线观看| 九九惹伊人| 亚洲日逼| 天天操天天射天天日| 亚洲女人天堂AV| 日本天堂网| 国产日女人| 最新久欠一区二区免费看| 亚洲一区AV| 大伊香蕉久久| 国产一级a毛一级a做免费的视频l 精品国产免费观看久久久_久久天天 | 2018中文字幕第一页| 亚洲一区中文字幕成人在线| 91亚洲一线产区二线产区| 欧洲三级网观看| 久久婷视频| 国产熟妇婬乱A片免费看牛牛 | 五月天无码在线| 在线伊人网| 亚洲AV无码第一区二区三区蜜桃 | 亚洲无码高清视频| 成人亚洲AV日韩AV无码| 亚洲av大全| 少妇搡BBBB搡BBB搡打电话| 中文天堂| 亚洲成人综合网站| 亚洲精品久久久久久久蜜桃| 中文免费高清在线观看视频| 波多野结衣AV在线播放| 亚洲爆乳无码一区二区三区| 亚洲一区亚洲二区| 亚洲无码三级片在线观看| 超碰成人在线观看| 青草青在线视频| 影音先锋成人在线视频| 5252a我爱haose01我愿| 亚洲中文av| 欧美午夜福利电影| 一级性爽AV毛片| 国产又粗又大又爽| 懂色成人视频在线观看| 国产中文字幕在线播放| 亚洲图片在线观看| 免费黄色福利视频| 在线看一区二区三区| 日日干视频| 日本黄色小视频| 亚州高清无码视频| 中文无码不卡| 久久久久久久久久久久久久久久久久免费精品分类视频 | 黃色一级A片一級片| 先锋av资源网| 亚洲成人AAAAA| 中文字幕一级片| 韩国高清无码60.70.80| 久久久久国产视频| 无码免费一区| 黄片无码免费| 中文字幕第11页| 人人妻人人上| 日韩无码一卡| 日本不卡一区二区三区四区| 久热久热| 久久大鸡吧| 国产精品永久| 黄色成人网站在线播放| 国产精品久久久久久久久久久久久| 欧美乱伦内射| 中文字幕在线观看网| 88av在线观看| 8050午夜| 你懂的视频在线| 色婷婷影院| 亚洲高清无码在线免费观看| 12—13女人毛片毛片| 1024大香蕉| 狼友视频免费观看| 吴梦梦一区二区三区| 日日射人妻| 久久久一区二区| 西西人体WW大胆无码| 操一操影院| 国产成人亚洲综合A∨婷婷| 91啦丨露脸丨熟女色啦| 美女大香蕉| 日韩香蕉视频| 91麻豆福利| 樱桃性爱视频| www.五月天婷婷| 操屄视频播放| 婷婷五月天在线电影| 成人黄色在线观看视频| 一级aa视频| 天天欧美| 中文字幕国产综合| 视频一区二| 99欧美精品| 欧性猛交ⅩXXX乱大交| 黄色不卡| 日日摸日日操| 亚洲无码成人在线| 黄色免费大片| 中文字幕免费高清| 欧美城综合在线观看网| 91麻豆天美传媒在线| 婷婷五月天成人社区| 又大又粗AV| 一本之道高清数码大全| xxxxxbbbbb| 六月激情网| 五月天婷婷丁香综合视频| 亚欧洲精品在线视频免费观看| 水蜜桃在线观看视频| 国产成人精品一区二区三区| 99无码视频| 一区二区三区无码视频| 炮友露脸青楼传媒刘颖儿| 欧美综合亚洲图片综合区| 黄色成人视频网站| 一区二区免费在线观看| 成人免费黄色视频网站| 热久久9| 国产三级网| 91工厂露脸熟女| 亚洲无码三级片| 波多野结衣高潮| 1024大香蕉| 色黄视频在线观看| 国产最新地址| 69成人精品国产| 国产小视频在线观看| 久久久久久成人无码| 你懂得视频在线观看| 国内精品久久久久久久久98| 一区二区三区色| 东京热视频网| 老妇性BBWBBWBBWBBW| 日日夜夜天天综合| 2025中文字幕在线| 五月丁香999| 免费激情| 日国无码| 99热官方网站| 综合激情网站| 中文字幕亚洲一区| 人妻无码精品| 亚州AV在线| 亚洲中文字幕日韩精品| 欧美黑人大吊| 91丨人妻丨偷拍| 黄色成人网站在线观看| 亚洲无码一区二区三| 91超碰免费在线| 丁香色色网| 福利二区| 日p视频在线观看| 91视频熟女| 精品热99| 亚洲国产成人91精品| 亚洲AV无码成人精品区| 特一级黄色片| 少妇BBBBBB| 国产有码在线观看| 亚卅无码| 午夜成人视频| 欧美老妇性猛交| av在线资源网站| 偷拍九九热| 国产一级片免费视频| 人妻制服丝袜| 超碰少妇| 乱伦无码高清麻豆视频一区二区 | 大吊妞| 日韩视频二区| 欧美黄色免费观看| 久久大香蕉视频| 91精品少妇高潮一区二区三区不卡| 成人做爰黄A片免费视频网站野外 国产成人午夜精品无码区久久麻豆 | 久久人妻无码中文字幕系列| 欧美三级无码| 春色av| 91操美女视频| h片免费网站| 偷拍第一页| JlZZJLZZJlZZ亚洲女人17| 一本到免费视频| 久久精品小视频| 尻屄电影| 色婷婷18禁| 无码人妻视频| 97性爱视频| 日韩三级片无码| 日韩欧美激情| 国产美女被爽到高潮免费A片软件| 大荫蒂视频另类XX| 视频在线观看一区| 亚洲五月婷婷| 无码免费中文字幕| www.久久久| 婷婷五月天久久| 奇米av在线| 妻子互换被高潮了三次| 精品无码一区二区三区四区久久久软件| 国产免费av网站| 少妇搡BBBB搡BBB搡造水爽| 欧美成人性爱网址| 99久久影院| 无码专区一区二区三区| 亚洲Av秘无码一区二区| 欧美日韩高清无码| 粉嫩小泬BBBB免费看| 国产乱伦对白| 日本女优婷婷青青草原| 日韩五码在线| 天天操夜夜爽| 在线视频你懂| 中日韩欧美一级A片免费| 精品AAA| 亚洲精品视频在线播放| 成人五月天黄色电影| 国产AV无码成人精品区| 日韩性爱在线| 老司机一区二区| 三级片在线观看视频| 大香蕉熟女| 日本无码视频在线观看| 中文字幕在线网站| 日韩av无码中文字幕| 91欧美视频| 黄色a片网站| 日本二区三区| 久色入口| 日本一级片在线观看| 丁香花在线小说免费阅读| 无码日韩视频| 安徽妇搡BBBB搡BBBB,另类老妇| 在线A视频| 激情人妻在线| 日韩人妻码一区二区三区| 午夜福利高清在线观看| 嫩BBB| 中文字幕在线观看完整av| 免费欧美性爱| 91双飞会所双飞在线| 亚洲视频欧美视频| 成人片成人网久久蜜桃臀| 色五月在线观看| 香蕉网站操逼片| 天天肏夜夜肏| 99久久久精品久久久久久| 黄色91| 久久国产香蕉| 成人无码www在线看免费| 国产白丝精品91爽爽久久| 国产黄色录像| 久久熟女嫩草成人片免费| 亚洲国产精品成人综合色五月 | 91麻豆影院| 天天干婷婷五月天| 国产精品一区二区三| 亚洲精品成人网站| 波多野结衣视频网站| 97精品一区二区三区A片| 久久99久久99久久| 亚洲精品熟女| 亚洲国产中文字幕在线播放| 天堂精品| 亚洲黄在线观看| 无码三级AV| 一区二区三区中文字幕| 日韩av无码电影| 国产乱子伦真实精品| 无码久| 99青草在线视频| 伊人福利导航| 蜜芽成人精品久久久视频| 乳揉みま痴汉电车羽月希免费观看| 中文字幕乱码中文字幕电视剧| 五月丁香成人电影| 日本免费一区二区三区| 牛牛精品一区二区| 伊人综合成人网| 国产黄色网页| 日本中文字幕中文翻译歌词| а√在线中文网新版地址在线 | 免费a片视频| av在线无码观看| 国产精品123区| 成人黄色免费观看| 国产伦精品一级A片视频夜夜| 亚洲操逼网| jk在线观看| 亚洲成人AV| 乱轮少妇| 国精产品一品二品国精| 免费的黄片| 熟女探花精选| 亚洲国产激情| www.豆花福利视频| 蜜桃视频欧美| 日韩黄色电影网站| 看黄片网站| 成人三级片网| 欧美屄视频| 黄色AA片| AV自拍偷拍| 91麻豆国产视频| 黄色大片网站| 国产九色| 亚洲成人AAAAA| 国产一级A片免费看| 日韩在线小视频| 超碰九九| 美女扣穴| 岛国无码AV在线观看| 影音先锋人妻限定| 1级毛片| 香蕉视频一区| 色综合天| 免费做爱视频网站| 黄色三级在线观看| 五月天无码在线| 欧美日韩中文在线观看| 亚洲天堂无码| 91久久国产| 亚洲精品久久久久久久久豆丁网| 在桌下含她的花蒂和舌头H视频 | 无码中文字幕在线观看| 亚洲精品娱乐| 蜜臀AV在线播放| 欧美影院亚洲| 天天搞天天色| 成人aaa| 国产成人av在线| 中文字幕一区二区三区日本在线| 另类BBwBBw| 丁香六月婷婷激情| 高颜值呻吟给力| 天堂在线| 水密桃网站| 性爱av天堂| 天堂网av2025| 在线视频观看一区| 成人三级电影网| 久久高清免费视频| 国产精品无码无套在线| 夜夜骑夜夜| 亚洲A在线观看| 无码视频久久| 全国男人的天堂网站| 天天综合网站| 国产毛片一照区| 无码精品ThePorn| 欧美日韩在线一区| 亚洲AV无码精品成人| 国产激情在线| 亚州视频在线观看| 大香蕉伊人手机在线| 日韩精品一区二区三区四在线播放 | 黄色电影AV| 在线观看中文字幕视频| 开心色色五月天| 国产a级毛片| 精精品人妻一区二区三区| 逼特逼视频在线观看| 99久久婷婷国产综合精品漫| 在线观看内射视频| 免费色色| 91人人妻人人澡人人爽人人精品| 一级片麻豆| 黄色一级生活片| 91视频成人版一区二区| 69天堂| 成人777| 大香蕉91| 狠狠操在线| 91免费小视频| 偷拍第一页| 狠狠狠狠干| 亚洲人成人无码.www粉色| 国产又粗又猛又黄又爽无遮挡 | 97男人的天堂| 尤物视频在线播放| 欧美五月婷婷| 亚洲精品中文字幕成人片| 九九综合网| 色天天干| 天天躁天干天干| 中文字幕乱码无码人妻系列蜜桃| 日韩午夜欧美精品一二三区| 一区二区三区av| 奇米91| 一曲二曲三曲在线观看中文字| 久久草大香蕉| 在线你懂得| 中文字幕免费在线播放| 91久久综合| 国产V在线观看| 日韩精品人妻中文字幕第4区| 国产做受91电影| 亚洲www在线观看| 99热3| 日本操b| 中文字幕2018第一页| 中文在线字幕高清电视剧| 影音先锋日韩精品| 国产成人无码永久免费| 高清无码电影| 欧美成人精品AAA| 91精品国产综合久久久久久| 国产成人aV| 超碰中文字幕| 黑人粗暴偷拍一区二区| 久久日韩操| 无码精品人妻一区二区| 肉色超薄丝袜脚交一区二区| 最新中文字幕AV| 人人澡人人摸| 性爱日韩| 欧美色插| 欧美一级大香蕉| 欧美性爱-熊猫成人网| 免费高清无码在线观看| 日本免费一级片| 国产精品九九九| 日本精品电影| 少妇搡BBBB搡BBB搡毛片| 日本亚洲精品秘入口A片| 丁香五月天激情视频| 日韩一级免费在线观看| 日韩一级片免费看| 日韩黄色视频网站| 黄色91| 欧洲精品在线观看| 韩国AV三级| 久久国产偷拍| 天天艹av| 日韩精品视频一区二区三区| 99精品色| 欧美成人三级在线观看| 加勒比无码在线| 人妻japanesewoman| 日韩AV无码专区亚洲AV紧身裤| 操逼黄视频| 人人做人人做人人做,人人做全句下一| 闺蜜av| 久久xx| 国产成人+综合亚洲+天堂| 青草香蕉视频| 色色视频网| 欧美性少妇| 91成人视频18| 最新va在线观看| 久久综合婷婷| 四川少妇搡bbw搡bbbb| 午夜福利三级| 精品福利视频导航| 91大片| 先锋影音一区二区三区| 久久久久久久久黄色| 国产aa片| 人人操人人操人人操| 91久久国产综合久久| 自拍偷拍一区二区三区| 日B免费视频| 俺来也俺去也www色官网| 亚洲国产精品久久久| 成人黄色在线| 国产人妻中文字幕| 免费国产黄色视频| 精品欧美一区二区精品久久| 国产又爽又黄免费视频免费观看 | 欧美成人一区二区| 黄色三极片| 蜜桃性爱视频| 日本AⅤ在线| 青草成人在线视频| 91吴梦梦无码一区二区| 亚洲色偷精品一区二区三区| 欧美三级在线视频| 少妇搡BBBB搡BBB搡打电话| 欧美日韩中文字幕视频| 91丨PORNY丨丰满人妻网站 | 黄色A片一级| 午夜成人福利| 大香蕉久在线| 超碰人人在线观看| 日本成人电影| 日本绿色精品视频| 偷拍视频第一页| 久久亚洲成人| 毛片2| 日韩无码123| 欧美精品秘一区二区三区蜜臀| 91麻豆天美传媒在线| 天堂网在线播放| 成人无码网站| 欧美黄色a片| 丰满人妻一区二区三区免费| 在线一区二区三区四区| 国产精品午夜在线观看| 天码人妻一区二区三区在线看| 大香蕉91| 91艹艹| 亚洲欧美日韩色图| 黄色视频日本| 日韩免费高清无码视频| 北条麻妃网站| 国产精品无码永久免费A片| 五月天三级片| 亚洲无码A片在线观看| 人人操人人摸人人射| 亚洲精品视频免费观看| 亚洲人BBwBBwBBWBBw| 友田真希一级婬片A片| 精品国产三级片| 青草娱乐| 国产手机拍视频推荐2023| 麻豆国产视频| 一起操在线视频| 大鸡吧视频在线观看| 亚洲日韩免费在线观看| 亚洲国产激情视频| 欧美一级在线观看| 日韩欧美小电影| 久久视频免费看| 青青国产在线观看| 女生自慰网站在线观看| 国产成人毛片18女人18精品| 国产精品成人无码专区| 福利一区二区视频网| 成人高清无码在线| 国产人人操| 国产人体视频| 欧美操B| 中文无码高清视频| 在线观看免费无码视频| 人人色人人黄| 中文字幕无码Av在线看| 日韩三级视频在线观看| 亚洲人成无码| 欧美爱爱试看| 久久久久无码| 国产91小视频| 国产黄色小电影| 大香蕉伊人网在线| 欧美精产国品一区二区区别| 亚洲人视频| 国产精品欧美一区二区三区苍井空 | 中文字幕人妻在线中文乱码怎么解决| 精品欧美一区二区三区| 77777精品成人免费A片| 亚洲成人免费| 性欧美欧美巨大69| 久操大香蕉| 超碰人人操人人摸| 内射午夜福利在线免费观看视频| 欧美性猛交XXXX乱大交HD| 怡红院成人网| 中文字幕人妻在线中文乱码怎么解决| 天天色综| 青青草精品在线视频| 高清欧美日韩第一摸| 天天操天天操天天操天天操| 中文字幕有码在线播放| 最新色站| 四色五月婷婷| 中文字幕第98页| 丰满熟妇高潮呻吟无码| 日韩人妻无码专区| 影音先锋在线成人| 午夜精品18码视频国产17c| 日韩无码电| 日韩在线小电影| 日韩久久网| 国产乱伦内射视频| 亚洲天堂中文字幕| 国产丝袜在线| 特级毛片AAAAAA蜜桃| 中文字幕久久人妻无码精品蜜桃 | 2026无码视频| caopor在线| 日本中文字幕在线免费观看| 久久免费精品视频| 大香蕉网伊人在线| 8050午夜一级免费| 欧美一区二区三曲的| 国产对白视频| 欧美成人精品一区二区| 日韩午夜电影| 人人射人人摸| 日韩欧美中文字幕在线观看| 青娱乐免费视频| 妖精视频黄色| 国产主播福利| 自拍偷拍视频网| 天天操嫩逼无套视频| 久久久久久性爱| 玖玖色资源| 日韩a| 偷拍777| 野花Av| 久久久久久久久久久久久久久久久久久久| 国产第56页| 国产黄色电影在线| wwwA片| 夜色福利在线| 亚洲成年人在线| 天堂A片电影网站在线观看| 国产黄色小电影| 日韩精品视频免费在线观看| 懂色成人视频在线观看| 高H视频在线观看| 成年免费视频| 久久精彩| 成人无码一区二区| 西西444WWW无码大胆知乎| A免费观看| 夜夜骚AV一二三区无码| 婷婷丁香五月综合| 男女av网站| 麻豆精品一区二区三区| 亚洲人BBwBBwBBWBBw| 欧美国产日韩在线观看| 国产91在线看| 国产日韩欧美综合精品在线观看| 久久久久久久免费视频| 丁香六月综合激情| 亚洲三级AV| 操操小骚逼| 日韩AV毛片| 亚洲综合久| 无码视频久久| 日韩在线大香蕉| 国产97热人人| 无码人妻av黄色一区二区三区| 2026国产精品视频| 成人一级黄色片| 福利黄色片:片| 中文字幕AV在线播放| 亚洲AV无码成人精品区| 在线视频一区二区三区四区| 国产无码一二三区| 好吊一区二区| 午夜A片| 国产一级a毛一级a毛视频在线网站 | 囯产精品久久久久久久久久久久久久| 久在线| 狠狠操狠狠操狠狠操| 国产女人18毛片水真多成人如厕| 337P大胆粉嫩银噜噜噜| 国产亚洲视频在线观看视频| 人妻人人爱| 福利视频亚洲| 成人四区| 国产91视频在线观看| 精品国产AV无码一区二区三区| 三级国产网站| 另类老妇奶性生BBwBB| 国产精品1| 亚洲小视频在线| 亚洲AV无一区二区三区久久| 欧美日韩亚洲一区二区| 青青青国产在线| 日本三级片免费| 亚洲男人综合| 国产成人无码A片V99| 夜夜狠狠擅视频| 免费做爱网站| 高潮AV在线观看| 日韩欧美人妻| 国产八区| 波多野结衣在线无码视频| 开心色播五月天| 免费无码A片在线观看全| 99视频精品| 四虎精品一区二区三区| 伊人狠狠| 91av免费观看| 日本无码高清| 欧美性爱福利视频| 亚洲.欧美.丝袜.中文.综合| 一级a免一级a做片免费| 大地影视官网第三页入口| 欧美日韩中文视频| 91精品国产综合久久久蜜臀图片 | 欧美黄色大香蕉| 亚洲av影院| 亚洲WWW| 91最新网址| 一级a一级a爱片免费视频| 亚洲小说欧美激情另类A片小说| 国产中文在线视频| 成人91视频| 天天躁夜夜躁av| 91美女在线观看| 91男女| 韩国精品久久久| 亚洲激情性爱| 国产麻豆AⅤMDMD0071| 欧美成人一区二区三区| 精品久久视频| 韩国成人啪啪无码高潮| 亚洲午夜福利电影| 色婷婷视频在线播放| 人妻熟妇乱子伦精品无码专区毛片| 色综合中文字幕| 91无码秘蜜桃一区二区三区-百度 精品人妻一区二区三区在线视频不卡 | 欧美自拍视频在线观看| 91做爱视频| 久久er热| 中文字幕av久久久久久欧洲尺码 | 逼特逼视频在线观看| 黑人人妻黑人ThePorn| 91欧美日韩综合| 色久在线| 麻豆91在线|