天天看點

前端面試總結四

1.Canvas和SVG的差別

Canvas和SVG是html5支援的兩種可視化技術。基于這兩種技術,誕生了很多可視化工具。

Echarts是基于Canvas技術的可視化工具,底層封裝了原生的JavaScript的繪圖 API。我們很容易聯想到另一個同樣很優秀的web前端可視化庫D3,D3是也最流行的可視化庫之一,它被很多其他的表格插件所使用。D3底層基于SVG技術,與Canvas完全不一樣,SVG的本質是一個XML 文檔。

這兩種方式在功能上是等同的,任何一種都可以用另一種來模拟。它們都是有效的圖形工具,可用來快速建立在網頁中顯示的輕型圖形;它們都使用 JavaScript 和 HTML;它們都遵守網際網路聯合會 (W3C) 标準。

Canvas和SVG都允許在浏覽器中建立圖形,但是它們在根本上是不同的。它們很不相同,他們各有強項和弱點。

Canvas 通過JavaScript來繪制2D圖形。Canvas 是逐像素進行渲染的。在 canvas 中,一旦圖形被繪制完成,它就不會繼續得到浏覽器的關注。如果其位置發生變化,那麼整個場景也需要重新繪制,包括任何或許已被圖形覆寫的對象。

SVG 是一種使用 XML 描述 2D 圖形的語言。SVG 基于 XML,這意味着 SVG DOM 中的每個元素都是可用的。您可以為某個元素附加 JavaScript 事件處理器。在 SVG 中,每個被繪制的圖形均被視為對象。如果 SVG 對象的屬性發生變化,那麼浏覽器能夠自動重制圖形。

差別:

Canvas 是基于像素的即時模式圖形系統最适合較小的表面或較大數量的對象,Canvas不支援滑鼠鍵盤等事件。

SVG 是基于形狀的保留模式圖形系統,更加适合較大的表面或較小數量的對象。Canvas和SVG在修改方式上還存在着不同。繪制Canvas對象後,不能使用腳本和 CSS 對它進行修改。因為 SVG 對象是文檔對象模型的一部分,是以可以随時使用腳本和 CSS 修改它們。

Canvas:

1)依賴分辨率

2)不支援事件處理器

3)弱的文本渲染能力

4)能夠以 .png 或 .jpg 格式儲存結果圖像

5)最适合圖像密集型的遊戲,其中的許多對象會被頻繁重繪

SVG:

1)不依賴分辨率

2)支援事件處理器

3)最适合帶有大型渲染區域的應用程式(比如谷歌地圖)

4)複雜度高會減慢渲染速度(任何過度使用 DOM 的應用都不快)

5)不适合遊戲應用

參考:Canvas和SVG的差別

2.HTML語義化(H5新特性)

語義化是指根據内容的結構化(内容語義化),選擇合适的标簽(代碼語義化),便于開發者閱讀和寫出更優雅的代碼的同時,讓浏覽器的爬蟲和機器很好的解析。

為什麼要語義化?

  • 有利于SEO,有助于爬蟲抓取更多的有效資訊,爬蟲是依賴于标簽來确定上下文和各個關鍵字的權重。
  • 語義化的HTML在沒有CSS的情況下也能呈現較好的内容結構與代碼結構。
  • 友善其他裝置的解析
  • 便于團隊開發和維護

<section></section>

定義文檔中的主體部分的節、段。

<article></article>

一個特殊的section标簽,比section有更明确的語義。定義來自外部的一個獨立的、完整的内容塊,例如什麼論壇的文章,部落格的文本。

<aside></aside>

用來裝載頁面中非正文的内容,獨立于其他子產品。例如廣告、成組的連結、側邊欄。

<header></header>

定義文檔、頁面的頁眉。通常是一些引導和導航資訊,不局限于整個頁面頭部,也可以用在内容裡。

<footer></footer>

定義了文檔、頁面的頁腳,和header類似。

<nav></nav>

定義了一個連結組組成的導航部分,其中的連結可以連結到其他網頁或者目前頁面的其他部分。

<hgroup></hgroup>

用于對網頁或區段(section)的标題元素(h1~h6)進行組合。

<figure></figure>

用于對元素進行組合。

<figcaption></figcaption>

為figure元素加标題。一般放在figure第一個子元素或者最後一個。

<details></details>

定義元素的細節,使用者可以點選檢視或者隐藏。

<summary></summary>

和details連用,用來包含details的标題。

<canvas></canvas>

用來進行canvas繪圖。

<video></video>

定義視訊。

<audio></audio>

定義音頻。

<embed></embed>

定義嵌入網頁的内容。比如插件。

<source></source>

該标簽為媒介元素(比如video、audio)定義媒介元素。

<datalist id='dl'></datalist>

定義可選資料的清單,與input配合使用()可制作輸入值的下拉清單。

<mark></mark>

在視覺上向使用者展現出那些想要突出的文字。比如搜尋結果中向使用者高亮顯示搜尋關鍵詞。

<meter [min/max/low/high/optimum/value]></meter>

度量衡,用紅黃綠表示出一個數值所在範圍。

<output></output>

定義不同類型的輸出,樣式與span無異。

<progress></progress>

進度條,運作中的進度。

<time></time>

定義日期或者時間。

<keygen></keygen>

定義加密内容。

<command></command>

定義指令行為。

參考:前端面試基礎-html篇之H5新特性

3.常見的浏覽器核心

  • Trident( MSHTML ):IE MaxThon TT The World 360 搜狗浏覽器
  • Geckos:Netscape6及以上版本 FireFox Mozilla Suite/SeaMonkey
  • Presto:Opera7及以上(Opera核心原為:Presto,現為:Blink)
  • Webkit:Safari Chrome

4.SVG 路徑 -

<path>

<path>

元素用于定義一個路徑。

下面的指令可用于路徑資料:

  • M = moveto
  • L = lineto
  • H = horizontal lineto
  • V = vertical lineto
  • C = curveto
  • S = smooth curveto
  • Q = quadratic Bézier curve
  • T = smooth quadratic Bézier curveto
  • A = elliptical Arc
  • Z = closepath

注意:以上所有指令均允許小寫字母。大寫表示絕對定位,小寫表示相對定位。

上面的例子定義了一條路徑,它開始于位置150 0,到達位置75 200,然後從那裡開始到225 200,最後在150 0關閉路徑。

前端面試總結四
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
    <path d="M150 0 L75 200 L225 200 Z" />
</svg>
           

下面的例子建立了一個二次方貝塞爾曲線,A 和 C 分别是起點和終點,B 是控制點:

前端面試總結四
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
  stroke-width="3" fill="none" />
  <path id="lineBC" d="M 250 50 l 150 300" stroke="red"
  stroke-width="3" fill="none" />
  <path d="M 175 200 l 150 0" stroke="green" stroke-width="3"
  fill="none" />
  <path d="M 100 350 q 150 -300 300 0" stroke="blue"
  stroke-width="5" fill="none" />
  <!-- Mark relevant points -->
  <g stroke="black" stroke-width="3" fill="black">
    <circle id="pointA" cx="100" cy="350" r="3" />
    <circle id="pointB" cx="250" cy="50" r="3" />
    <circle id="pointC" cx="400" cy="350" r="3" />
  </g>
  <!-- Label the points -->
  <g font-size="30" font="sans-serif" fill="black" stroke="none"
  text-anchor="middle">
    <text x="100" y="350" dx="-30">A</text>
    <text x="250" y="50" dy="-10">B</text>
    <text x="400" y="350" dx="30">C</text>
  </g>
</svg>
           

參考:SVG

<path>

5.HTML檔案裡開頭的!Doctype有什麼作用?

DOCTYPE是什麼?

DOCTYPE是document type的簡寫,它并不是 HTML 标簽,也沒有結束标簽,它是一種标記語言的文檔類型聲明,即告訴浏覽器目前 HTML 是用什麼版本編寫的。DOCTYPE的聲明必須是 HTML 文檔的第一行,位于html标簽之前。大多數Web文檔的頂部都有doctype聲明,它是在建立一個文檔時,由Web創作軟體草率處理的衆多細節之一。很少人會去注意 doctype ,但在遵循标準的任何Web文檔中,它都是一項必需的元素。doctype會影響代碼驗證,并決定了浏覽器最終如何顯示你的 Web文檔。

DOCTYPE可聲明三種 DTD 類型:嚴格、過渡以及架構集的 HTML 文檔。

超文本嚴格文檔類型定義:HTML Strict DTD

如果需要幹淨的标記,免于表現層的混亂,請使用此類型。請與層疊樣式表配合使用。該 DTD 包含所有 HTML 元素和屬性,但不包括展示性的和棄用的元素(比如 font)。不允許架構集(Framesets)

代碼為:

DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//en" "http://www.w3. org/TR/html4/strict.dtd"

超文本過渡文檔類型定義:HTML Transitional DTD

可包含 W3C 所期望移入樣式表的呈現屬性和元素。如果您的讀者使用了不支援層疊樣式表(CSS)的浏覽器以至于您不得不使用 HTML 的呈現特性時,請使用此類型。該 DTD 包含所有 HTML 元素和屬性,包括展示性的和棄用的元素(比如 font)。不允許架構集(Framesets)。

代碼為:

DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"

超文本架構集文檔類型定義:Frameset DTD

此類型定義應當被用于帶有架構的文檔。除 frameset 元素取代了 body 元素之外,Frameset DTD 等同于 Transitional DTD。該 DTD 等同于 HTML 4.01 Transitional,但允許架構集内容。

代碼為:

DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"

DOCTYPE的作用是什麼?

DOCTYPE聲明中指出閱讀程式應該用什麼規則來解釋文檔中的标記。在Web文檔的情況下,閱讀程式通常是浏覽器或者校驗器這樣的一個程式,規則是W3C所釋出的一個文檔類型定義 DTD 中包含的規則。制作一個符合标準的網頁,DOCTYPE聲明是不可缺少的,它在Web設計中用來說明你用的XHTML或者HTML是什麼版本,如果不做DOCTYPE聲明或聲明不正确的情況下,将有可能導緻你的辨別與CSS失效,進而令你網頁的布局變亂,造成網頁在浏覽器中不能正常的顯示。我們還可以通過W3C提供的驗證工具來檢查頁面的内容是否符合在DOCTYPE中聲明的标準。

DTD是什麼

DTD是文檔類型定義(Document Type Definition)是一套為了進行程式間的資料交換而建立的關于标記符的文法規則。

它使用一系列合法的元素來定義文檔的結構。是SGML的一部分,可被成行地聲明于 XML 文檔中,也可作為一個外部引用。

通過它,獨立的團體可一緻地使用某個标準的文檔類型定義來交換資料。而您的應用程式也可使用某個标準的文檔類型定義來驗證從外部接收到的資料。

參考:https://www.jianshu.com/p/ce48b13a4e1e

6.BOM與DOM解釋與分析

JavaScript分為 ECMAScript,DOM,BOM。

BOM(Browser Object Model)是指浏覽器對象模型,它使 JavaScript 有能力與浏覽器進行“對話”。

DOM (Document Object Model)是指文檔對象模型,通過它,可以通路HTML文檔的所有元素。

BOM:

BOM包含windows(視窗)、navigator(浏覽器)、screen(浏覽器螢幕)、history(通路曆史)、location(位址)等。

(1)windows:

頁面一旦加載,就會建立windows對象,無需自行建立。通過該對象可以擷取文本框的寬高(windows.innerWidth)、浏覽器視窗寬高(windows.outerWidth)、打開關閉新視窗(windows.open("/")、windows.close())等屬性

(2)Navigator:

提供浏覽器相關資訊,包括浏覽器名稱、版本号、作業系統等等。

(3)screen:

提供使用者螢幕相關資訊。如高寬等(screen.width)。

(4)history:

記錄通路曆史。傳回上次通路位址:history.back。傳回上上次history.back(2),以此類推。

(5)location:

浏覽器中的位址欄,可以完成重新整理目前頁面與放回首頁、顯示端口号、伺服器等資訊。

(6)alert彈出框、confirm确認框、prompt輸入框

(7)計時器:

前端面試總結四

DOM:

DOM就是把html裡的各種資料當做對象處理的一種思路。

(1)通過document.getElementById擷取了id=d1的div标簽對應的元素節點

然後通過attributes 擷取了該節點對應的屬性節點

接着通過childNodes擷取了内容節點

(2)擷取節點:

前端面試總結四

(3)擷取節點的屬性:

前端面試總結四

(4)DOM的事件:

前端面試總結四

參考:BOM與DOM解釋與分析

7.CSS外邊距折疊(Margin Collapse)

前端面試總結四

什麼是外邊距疊加:

W3C對于外邊距疊加的定義:

In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin.

即,在CSS中,兩個或多個毗鄰的普通流中的盒子(可能是父子元素,也可能是兄弟元素)在垂直方向上的外邊距會發生疊加,這種形成的外邊距稱之為外邊距疊加。

  • 毗鄰

    毗鄰說明了他們的位置關系,沒有被padding、border、clear和line box分隔開。

  • 兩個或多個

    兩個或多個盒子是指元素之間的互相影響,單個元素不會存在外邊距疊加的情況。

  • 垂直方向

    隻有垂直方向的外邊距會發生外邊距疊加。水準方向的外邊距不存在疊加的情況。

  • 普通流(in flow)

    啥為普通流?W3C隻對out of flow作了定義:

    An element is called out of flow if it is floated, absolutely positioned, or is the root element.An element is called in-flow if it is not out-of-flow.

    從定義中我們可以知道隻要不是float、absolutely positioned和root element時就是in flow。

什麼時候會發生外邊距疊加:

外邊距疊加存在兩種情況:一是父子外邊距疊加;二是兄弟外邊距疊加。

  • 都屬于普通流的塊級盒子且參與到相同的塊級格式上下文中
  • 沒有被padding、border、clear和line box分隔開
  • 都屬于垂直毗鄰盒子邊緣:

    (1)盒子的top margin和它第一個普通流子元素的top margin

    (2)盒子的bottom margin和它下一個普通流兄弟的top margin

    (3)盒子的bottom margin和它父元素的bottom margin

    (4)盒子的top margin和bottom margin,且沒有建立一個新的塊級格式上下文,且有被計算為0的min-height,被計算為0或auto的height,且沒有普通流子元素

如何避免外邊距疊加:

隻要破壞上面講到的四個條件中的任何一個即可:毗鄰、兩個或多個、普通流和垂直方向。

  • 浮動元素不會與任何元素發生疊加,也包括它的子元素
  • 建立了BFC的元素不會和它的子元素發生外邊距疊加
  • 絕對定位元素和其他任何元素之間不發生外邊距疊加,也包括它的子元素
  • inline-block元素和其他任何元素之間不發生外邊距疊加,也包括它的子元素
  • 普通流中的塊級元素的margin-bottom永遠和它相鄰的下一個塊級元素的margin-top疊加,除非相鄰的兄弟元素clear
  • 普通流中的塊級元素(沒有border-top、沒有padding-top)的margin-top和它的第一個普通流中的子元素(沒有clear)發生margin-top疊加
  • 普通流中的塊級元素(height為auto、min-height為0、沒有border-bottom、沒有padding-bottom)和它的最後一個普通流中的子元素(沒有自身發生margin疊加或clear)發生margin-bottom疊加
  • 如果一個元素的min-height為0、沒有border、沒有padding、高度為0或者auto、不包含子元素,那麼它自身的外邊距會發生疊加

參考:深入了解CSS外邊距折疊(Margin Collapse)

8.僞類和僞元素

僞類:

CSS3給出的定義是:

The pseudo-class concept is introduced to permit selection based on information that lies outside of the document tree or that cannot be expressed using the other simple selectors.

僞類存在的意義是為了通過選擇器,格式化DOM樹以外的資訊以及不能被正常CSS選擇器擷取到的資訊。

僞類的功能有兩種:

(1)格式化DOM樹以外的資訊。

比如:

<a>

标簽的:link、:visited 等。這些資訊不存在于DOM樹中。

(2)不能被正常CSS選擇器擷取到的資訊。

比如:要擷取第一個子元素,我們無法用正常的CSS選擇器擷取,但可以通過 :first-child 來擷取到。

僞元素:

CSS3給出的定義如下:

Pseudo-elements create abstractions about the document tree beyond those specified by the document language. For instance, document languages do not offer mechanisms to access the first letter or first line of an element’s content. Pseudo-elements allow authors to refer to this otherwise inaccessible information. Pseudo-elements may also provide authors a way to refer to content that does not exist in the source document (e.g., the ::before and ::after pseudo-elements give access to generated content).

僞元素可以建立一些文檔語言無法建立的虛拟元素。比如:文檔語言沒有一種機制可以描述元素内容的第一個字母或第一行,但僞元素可以做到(::first-letter、::first-line)。同時,僞元素還可以建立源文檔不存在的内容,比如使用 ::before 或 ::after。

僞類和僞元素的差別(CSS3下的差別):

僞類其實是彌補了CSS選擇器的不足,用來更友善地擷取資訊。

<ul>
    <li>11111</li>
    <li>22222</li>
</ul>   
           
li:first-child {
    color: red;   
}
// 選擇器不能直接選取第一個子元素
// 僞類彌補了選擇器的不足
           

而僞元素本質上是建立了一個虛拟容器(元素),我們可以在其中添加内容或樣式。

<p>
    <span class="first-letter">H</span>ello, World
</p>
           
.first-letter {
  color: red;
}
           

上面的代碼其實就是:

p::first-letter {
  color: red;
}
           

參考:我終于了解了僞類和僞元素

9.跨域資源共享CORS

CORS是一個W3C标準,全稱是"跨域資源共享"(Cross-origin resource sharing)。

它允許浏覽器向跨源伺服器,發出XMLHttpRequest請求,進而克服了AJAX隻能同源使用的限制。

CORS需要浏覽器和伺服器同時支援。目前,所有浏覽器都支援該功能,IE浏覽器不能低于IE10。

整個CORS通信過程,都是浏覽器自動完成,不需要使用者參與。對于開發者來說,CORS通信與同源的AJAX通信沒有差别,代碼完全一樣。浏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。

是以,實作CORS通信的關鍵是伺服器。隻要伺服器實作了CORS接口,就可以跨源通信。

兩種請求:

浏覽器将CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

隻要同時滿足以下兩大條件,就屬于簡單請求。

(1)請求方法是以下三種方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的頭資訊不超出以下幾種字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:隻限于三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同時滿足上面兩個條件,就屬于非簡單請求。

浏覽器對這兩種請求的處理,是不一樣的。

簡單請求:

對于簡單請求,浏覽器直接發出CORS請求。具體來說,就是在頭資訊之中,增加一個Origin字段。

下面是一個例子,浏覽器發現這次跨源AJAX請求是簡單請求,就自動在頭資訊之中,添加一個Origin字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
           

上面的頭資訊中,Origin字段用來說明,本次請求來自哪個源(協定 + 域名 + 端口)。伺服器根據這個值,決定是否同意這次請求。

如果Origin指定的源,不在許可範圍内,伺服器會傳回一個正常的HTTP回應。浏覽器發現,這個回應的頭資訊沒有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯了,進而抛出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。注意,這種錯誤無法通過狀态碼識别,因為HTTP回應的狀态碼有可能是200。

如果Origin指定的域名在許可範圍内,伺服器傳回的響應,會多出幾個頭資訊字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
           

上面的頭資訊之中,有三個與CORS請求相關的字段,都以Access-Control-開頭。

(1)Access-Control-Allow-Origin

該字段是必須的。它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求。

(2)Access-Control-Allow-Credentials

該字段可選。它的值是一個布爾值,表示是否允許發送Cookie。預設情況下,Cookie不包括在CORS請求之中。設為true,即表示伺服器明确許可,Cookie可以包含在請求中,一起發給伺服器。這個值也隻能設為true,如果伺服器不要浏覽器發送Cookie,删除該字段即可。

(3)Access-Control-Expose-Headers

該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法隻能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers裡面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以傳回FooBar字段的值。

withCredentials 屬性:

上面說到,CORS請求預設不發送Cookie和HTTP認證資訊。如果要把Cookie發到伺服器,一方面要伺服器同意,指定Access-Control-Allow-Credentials字段。

另一方面,開發者必須在AJAX請求中打開withCredentials屬性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
           

否則,即使伺服器同意發送Cookie,浏覽器也不會發送。或者,伺服器要求設定Cookie,浏覽器也不會處理。

但是,如果省略withCredentials設定,有的浏覽器還是會一起發送Cookie。這時,可以顯式關閉withCredentials。

需要注意的是,如果要發送Cookie,Access-Control-Allow-Origin就不能設為星号,必須指定明确的、與請求網頁一緻的域名。同時,Cookie依然遵循同源政策,隻有用伺服器域名設定的Cookie才會上傳,其他域名的Cookie并不會上傳,且(跨源)原網頁代碼中的document.cookie也無法讀取伺服器域名下的Cookie。

非簡單請求:

預檢請求:

非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。

非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。

浏覽器先詢問伺服器,目前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭資訊字段。隻有得到肯定答複,浏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
           

上面代碼中,HTTP請求的方法是PUT,并且發送一個自定義頭資訊X-Custom-Header。

浏覽器發現,這是一個非簡單請求,就自動發出一個"預檢"請求,要求伺服器确認可以這樣請求。下面是這個"預檢"請求的HTTP頭資訊。

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
           

"預檢"請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭資訊裡面,關鍵字段是Origin,表示請求來自哪個源。

除了Origin字段,"預檢"請求的頭資訊包括兩個特殊字段。

(1)Access-Control-Request-Method

該字段是必須的,用來列出浏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。

(2)Access-Control-Request-Headers

該字段是一個逗号分隔的字元串,指定浏覽器CORS請求會額外發送的頭資訊字段,上例是X-Custom-Header。

預檢請求的回應:

伺服器收到"預檢"請求以後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以後,确認允許跨源請求,就可以做出回應。

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
           

上面的HTTP回應中,關鍵的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以請求資料。該字段也可以設為星号,表示同意任意跨源請求。

如果浏覽器否定了"預檢"請求,會傳回一個正常的HTTP回應,但是沒有任何CORS相關的頭資訊字段。這時,浏覽器就會認定,伺服器不同意預檢請求,是以觸發一個錯誤,被XMLHttpRequest對象的onerror回調函數捕獲。控制台會列印出如下的報錯資訊。

XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
           

伺服器回應的其他CORS相關字段如下。

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
           

(1)Access-Control-Allow-Methods

該字段必需,它的值是逗号分隔的一個字元串,表明伺服器支援的所有跨域請求的方法。注意,傳回的是所有支援的方法,而不單是浏覽器請求的那個方法。這是為了避免多次"預檢"請求。

(2)Access-Control-Allow-Headers

如果浏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗号分隔的字元串,表明伺服器支援的所有頭資訊字段,不限于浏覽器在"預檢"中請求的字段。

(3)Access-Control-Allow-Credentials

該字段與簡單請求時的含義相同。

(4)Access-Control-Max-Age

該字段可選,用來指定本次預檢請求的有效期,機關為秒。上面結果中,有效期是20天(1728000秒),即允許緩存該條回應1728000秒(即20天),在此期間,不用發出另一條預檢請求。

浏覽器的正常請求和回應:

一旦伺服器通過了"預檢"請求,以後每次浏覽器正常的CORS請求,就都跟簡單請求一樣,會有一個Origin頭資訊字段。伺服器的回應,也都會有一個Access-Control-Allow-Origin頭資訊字段。

下面是"預檢"請求之後,浏覽器的正常CORS請求。

PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
           

上面頭資訊的Origin字段是浏覽器自動添加的。

下面是伺服器正常的回應。

Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
           

上面頭資訊中,Access-Control-Allow-Origin字段是每次回應都必定包含的。

與JSONP比較:

CORS與JSONP的使用目的相同,但是比JSONP更強大。

JSONP隻支援GET請求,CORS支援所有類型的HTTP請求。JSONP的優勢在于支援老式浏覽器,以及可以向不支援CORS的網站請求資料。

參考:跨域資源共享CORS詳解

10.Ajax及其底層實作

什麼是Ajax?

Ajax 即“Asynchronous Javascript And XML”(異步 JavaScript 和 XML),是指一種建立互動式網頁應用的網頁開發技術,是異步重新整理技術,用來在目前頁面内響應不同的請求内容。

  • Ajax = 異步 JavaScript 和 XML。
  • Ajax 是一種用于建立快速動态網頁的技術。
  • 通過在背景與伺服器進行少量資料交換,Ajax 可以使網頁實作異步更新。這意味着可以在不重新加載整個網頁的情況下,對網頁的某部分進行更新

為什麼需要Ajax

 需求:

  有的時候我們需要将本次的響應結構和前面的響應結果内容在同一個頁面中展示給使用者。

解決:

  1、原始:在背景伺服器端将多次響應的内容重新拼接成一個jsp頁面,響應,但是這樣會造成很多響應内容被重複響應,資源浪費。

  2、使用Ajax技術,實作響應局部内容(異步重新整理技術)。

Ajax底層實作:

Ajax通路原理:

  Ajax的原理簡單來說通過XmlHttpRequest對象來向伺服器發異步請求,從伺服器獲得資料,然後用javascript來操作DOM而更新頁面。

Ajax的基本使用流程:

  • 建立Ajax引擎對象(XmlHttpRequest、ActiveObject)
  • 複寫onreadystatement函數(狀态碼、響應狀态碼的使用,操作Dom對象)
  • 發送請求(get/post,參數傳遞)

Ajax的狀态碼(readyState)和響應狀态碼(status):

 Ajax狀态碼:

  運作Ajax所經曆過的幾種狀态,無論通路是否成功都将響應的步驟,可以了解成為Ajax運作步驟。如:正在發送,正在響應等,由 Ajax對象與伺服器互動時所得 ajax.readyState:0,1,2,3,4

  • 0 -(未初始化)還沒有調用send()
  • 1 - (載入)已調用send()方法,正在發送請求
  • 2 - (載入完成)send()方法執行完成
  • 3 - (互動)正在解析響應内容
  • 4 - (完成)響應内容解析完成,可以在用戶端調用上面的狀态,其中“0”狀态是在定義後自動具有的狀态碼值,而對于成功通路的狀态(得到資訊)我們大多數采用“4”進行判斷。

這就是我們在使用Ajax時為什麼采用下面的方式判斷所獲得的資訊是否正确的原因。

Ajax響應狀态碼:

  無論Ajax通路是否成功,由http協定根據所送出的資訊,伺服器所傳回的http頭資訊代碼,該資訊使用“ajax.status”所獲得(常見的是200成功,404頁面找不到)

  總的來說:status展現的是伺服器對請求的回報,而readystate表明用戶端與客戶的互動狀态過程。

Ajax的異步和同步:

  同步的意思是當js代碼加載到目前Ajax的時候會把頁面裡所有的代碼停止加載,頁面出現假死狀态(等待Ajax執行完),當這個Ajax執行完畢後才會繼續運作其他代碼頁面假死狀态解除。

  異步則這個Ajax代碼運作中的時候其他代碼一樣可以運作。

   jquery的async:false/true,這個屬性預設是true:異步,false:同步。一般使用預設的異步。

Ajax的請求:

 get:

  get是最常見的請求類型,最常用于向伺服器查詢某些資訊,它适用于當url完全指定請求資源,當請求對伺服器沒有任何副作用以及當伺服器的響應是可緩存的情況下。

使用get方式發送請求時,資料被追加到open()方法中url的末尾

  

ajax.open(type,url,async)

type:請求方式,url:請求位址,async:異步/同步

  

ajax.open("get","AjaxServlet?username=zhangsan&password=333")

 post:

  使用頻率僅次于get的是post請求,通常用于伺服器發送應該被儲存資料。“post"方法常用于html表單。它在請求主體中包含額外資料且這些資料常存儲到伺服器上的資料庫中。相同url的重複post請求從伺服器得到的響應可能不同,同時不應該緩存使用這個方法的請求

  post請求應該把資料作為請求的主體送出,而GET請求傳統上不是這樣。post請求的主體可以包含非常多的資料,而且格式不限。在open()方法第一個參數的位置傳入"post”,就可以初始化一個post請求

  

ajax.open("post","AjaxServlet");

  注意post的方式傳資料必須設定請求頭,很多人寫底層Ajax會漏掉這行代碼

  

ajax.setRequestHeader("content-type","application/x-www-form-urlencoded");

  接下來要以适當的格式建立一個字元串,并使用send()方法發送

  

ajax.send("username=zhangsan&password=333");

Ajax的底層實作代碼:

測試頁面 ajax.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ajax底層實作</title>
<script type="text/javascript" src="js/jquery-3.3.1.min.js" ></script>
<script type="text/javascript" src="js/ajax.js" ></script>
</head>
<body>
<button id='ajaxGet'>ajax技術 get請求</button>
<button id='ajaxPost'>ajax技術 post請求</button>
<hr/>
<span id='span'></span>
</body>
</html>
           

AjaxServlet:

package com.demo.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 功能:ajax底層實作
 * 時間:2018-12-9
 * @author
 *
 */
@WebServlet("/AjaxServlet")
public class AjaxServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//設定請求編碼
		request.setCharacterEncoding("utf-8");
		//設定響應編碼格式
		response.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		//擷取請求資訊
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		//處理請求資訊
		//響應請求資訊
		response.getWriter().write("username:"+username+",password:"+password);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}
           

ajax.js:

$(function() {
	//get請求方式
	$('#ajaxGet').on("click",function() {
		console.log('get');
		//擷取ajax引擎對象
		var ajax;
		if (window.XMLHttpRequest) {
			ajax = new XMLHttpRequest();
		} else {//ie比較特殊
			ajax = new ActiveObject("Msxm12.XMLHTTP");
		}
		//複寫onreadyStatement函數 當ajax狀态值發生改變時 函數就會被觸發事件onreadystatechange 
		ajax.onreadystatechange = function() {
			//判斷ajax狀态值
			if (ajax.readyState == 4) {
				//判斷ajax狀态碼
				if (ajax.status == 200) {
					//擷取響應内容(響應内容的格式)
					var result = ajax.responseText;
					//擷取元素對象、處理響應内容(js操作dom)
					console.log(result);
					$('#span').text(result);
				}
			}
		}
		//發送get請求
		ajax.open("get","AjaxServlet?username=zhangsan&password=333");
		ajax.send(null);
	});
	//post的請求方式
	$('#ajaxPost').on("click",function() {
		console.log('post');
		//擷取ajax引擎對象
		var ajax;
		if (window.XMLHttpRequest) {
			ajax = new XMLHttpRequest();
		} else {//ie比較特殊
			ajax = new ActiveObject("Msxm12.XMLHTTP");
		}
		//複寫onreadyStatement函數
		ajax.onreadystatechange = function() {
			//判斷ajax狀态值
			if (ajax.readyState == 4) {
				//判斷ajax狀态碼
				if (ajax.status == 200) {
					//擷取響應内容(響應内容格式)
					var result = ajax.responseText;
					//擷取元素對象、處理響應内容(js操作dom)
					console.log(result);
					$('#span').text(result);
				}
			}
		}
		//發送post請求
		ajax.open("post","AjaxServlet");
		//設定請求頭
		ajax.setRequestHeader("content-type","application/x-www-form-urlencoded");
		//傳遞參數
		ajax.send("username=zhangsan&password=333");
	});
})
           

封裝自己的Ajax:

測試頁面 myAjax.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ajax底層實作</title>
<script type="text/javascript" src="js/jquery-3.3.1.min.js" ></script>
<script type="text/javascript" src="js/myAjax.js" ></script>
<script type="text/javascript" src="js/myAjaxu.js" ></script>
</head>
<body>
<button id='ajaxGet'>ajax技術 get請求</button>
<button id='ajaxPost'>ajax技術 post請求</button>
<hr/>
<span id='span'></span>
</body>
</html>
           

封裝好的myAjax.js:

/**
 * method:請求方法,值為get/post
 * url:請求位址
 * data:請求資料,沒有值需要傳入null,有值則傳入字元串資料,格式為"a=1&b=2"
 * deal200:接受一個帶有一個形參的js函數對象,形參接收的實參是ajax引擎對象
 * deal404:接受一個帶有一個形參的js函數對象(後面還有500等等)
 * 
 */
function myAjax(method, url, data, deal200, deal404, async) {
	console.log("調用success");
	//擷取ajax引擎對象
	var ajax = getAjax();
	//複寫onreadyStatement函數
	ajax.onreadystatechange = function() {
		//判斷ajax狀态值
		if (ajax.readyState == 4) {
			//判斷ajax狀态碼
			if (ajax.status == 200) {
				if (deal200) {
					deal200(ajax);
				}
			} else if (ajax.status == 404) {
				if (deal404) {
					deal404(ajax);
				}
			}
		}
	}
	//發送請求
	if("get" == method) {
		ajax.open("get",url+(data == null ? "" : "?")+data,async);
		ajax.send(null);
	} else if ("post" == method) {
		//發送請求
		ajax.open("post",url,async);
		//設定請求頭
		ajax.setRequestHeader("content-type","application/x-www-form-urlencoded");
		//參數傳遞
		ajax.send(data);
	}
};

//擷取ajax引擎對象函數
function getAjax() {
	var ajax;
	if (window.XMLHttpRequest) {
		ajax = new XMLHttpRequest();
	} else {//ie比較特殊
		ajax = new ActiveObject("Msxm12.XMLHTTP");
	}
	return ajax;
};
           

測試js myAjax.js:

$(function() {
	//get請求方式
	$('#ajaxGet').on("click",function() {
		//擷取請求資料(我這沒連接配接資料庫,自己給了兩個請求資料)
		var data = "username=zhangsan&password=333";
		myAjax("get","AjaxServlet",data,function(ajax) {
			//擷取響應資訊
			var result = ajax.responseText;
			//處理響應資訊
			$('#span').text(result);
		});
	});
	//post請求方式
	$('#ajaxPost').on("click",function() {
		//擷取請求資料(我這沒連接配接資料庫,自己給了兩個請求資料)
		var data = "username=zhangsan&password=333";
		myAjax("post","AjaxServlet",data,function(ajax){
			//擷取響應資訊
			var result = ajax.responseText;
			//處理響應資訊
			$('#span').text(result);
		});
	});
})
           

參考:什麼是Ajax及其底層實作

11.axios

axios 是一個基于Promise 用于浏覽器和 nodejs 的 HTTP 用戶端,它本身具有以下特征:

  • 從浏覽器中建立 XMLHttpRequest
  • 從 node.js 發出 http 請求
  • 支援 Promise API
  • 攔截請求和響應
  • 轉換請求和響應資料
  • 取消請求
  • 自動轉換JSON資料
  • 用戶端支援防止 CSRF/XSRF

使用:

axios并沒有install 方法,是以是不能使用vue.use()方法的。

解決方法有很多種:

  • 結合 vue-axios使用
  • axios 改寫為 Vue 的原型屬性
  • 結合 Vuex的action

vue-axios使用:

首先在主入口檔案main.js中引用

import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios,axios);
           

之後就可以使用了,在元件檔案中的methods裡去使用了

getdata(){
            this.axios.get('/vue-demo/api/getdata').then((response)=>{
                console.log(response.data)
            }).catch((response)=>{
                console.log(response)
            })
        }
           

axios 改寫為 Vue 的原型屬性:

首先在主入口檔案main.js中引用,之後挂在vue的原型鍊上

import axios from 'axios'
Vue.prototype.$ajax= axios
           

在元件中使用

this.$ajax.get('api/getNewsList').then((response)=>{
        this.newsList=response.data.data;
      }).catch((response)=>{
        console.log(response);
      })
           

Vuex的action:

在vuex的倉庫檔案store.js中引用,使用action添加方法

import Vue from 'Vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
const store = new Vuex.Store({
  // 定義狀态
  state: {
    user: {
      name: 'xiaoming'
    }
  },
  actions: {
    // 封裝一個 ajax 方法
    login (context) {
      axios({
        method: 'post',
        url: '/user',
        data: context.state.user
      })
    }
  }
})
export default store
           

在元件中發送請求的時候,需要使用 this.$store.dispatch

methods: {
  submitForm () {
    this.$store.dispatch('login')
  }
}
           

攔截器:

// 添加請求攔截器
axios.interceptors.request.use(function (config) {
    // 在發送請求之前做些什麼
    return config;
  }, function (error) {
    // 對請求錯誤做些什麼
    return Promise.reject(error);
  });

// 添加響應攔截器
axios.interceptors.response.use(function (response) {
    // 對響應資料做點什麼
    return response;
  }, function (error) {
    // 對響應錯誤做點什麼
    return Promise.reject(error);
  });
           

如果你想在稍後移除攔截器,可以這樣:

var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
           

與ajax差別

Ajax:

Ajax 即“Asynchronous Javascript And XML”(異步 JavaScript 和 XML),是指一種建立互動式網頁應用的網頁開發技術。

  • Ajax = 異步 JavaScript 和 XML(标準通用标記語言的子集)。
  • Ajax 是一種用于建立快速動态網頁的技術。
  • Ajax 是一種在無需重新加載整個網頁的情況下,能夠更新部分網頁的技術。
  • 通過在背景與伺服器進行少量資料交換,Ajax 可以使網頁實作異步更新。這意味着可以在不重新加載整個網頁的情況下,對網頁的某部分進行更新。
  • 傳統的網頁(不使用 Ajax)如果需要更新内容,必須重載整個網頁頁面。

參考:Vue基礎入門(二)axios簡介

12.微信小程式與H5的差別

(1)運作環境不同

傳統的HTML5的運作環境是浏覽器,包括webview,而微信小程式的運作環境并非完整的浏覽器,是微信開發團隊基于浏覽器核心完全重構的一個内置解析器,針對小程式專門做了優化,配合自己定義的開發語言标準,提升了小程式的性能。

(2)開發成本不同

隻在微信中運作,是以不用再去顧慮浏覽器相容性,不用擔心生産環境中出現不可預料的奇妙BUG

(3)擷取系統級權限不同

系統級權限都可以和微信小程式無縫銜接

(4)應用在生産環境的運作流暢度

長久以來,當HTML5應用面對複雜的業務邏輯或者豐富的頁面互動時,它的體驗總是不盡人意,需要不斷的對項目優化來提升使用者體驗。但是由于微信小程式運作環境獨立

13.小程式的雙向綁定和vue哪裡不一樣

小程式直接this.data的屬性是不可以同步到視圖的,必須調用:

this.setData({
          noBind:true
})
           

14.新版小程式登入授權

主動登入:

由于APP中有些頁面預設需要登入的,如[個人中心]頁面,需要登入擷取到使用者資訊,才能繼續操作。這樣的頁面就需要在每次進入頁面(onShow)時判斷是否授權了。

profile頁面

onShow () {
    login(() => {
        do something...
    })
}
           

關于登入授權相關的邏輯都可以封裝在handleLogin.js中

handleLogin.js

// 開始login
function login (callback) {
  wx.showLoading()
  wx.login({
    success (res) {
      if (res.code) {
        // 登入成功,擷取使用者資訊
        getUserInfo(res.code, callback)
      } else {
        // 否則彈窗顯示,showToast需要封裝
        showToast()
      }
    },
    fail () {
      showToast()
    }
  })
}

// 擷取使用者資訊
function getUserInfo (code, callback) {
  wx.getUserInfo({
    // 擷取成功,全局存儲使用者資訊,開發者伺服器登入
    success (res) {
      // 全局存儲使用者資訊
      store.commit('storeUpdateWxUser', res.userInfo)
      postLogin(code, res.iv, res.encryptedData, callback)
    },
    // 擷取失敗,彈窗提示一鍵登入
    fail () {
      wx.hideLoading()
      // 擷取使用者資訊失敗,清楚全局存儲的登入狀态,彈窗提示一鍵登入
      // 使用token管理登入态的,清楚存儲全局的token
      // 使用cookie管理登入态的,可以清楚全局登入狀态管理的變量
      store.commit('storeUpdateToken', '')
      // 擷取不到使用者資訊,說明使用者沒有授權或者取消授權。彈窗提示一鍵登入,後續會講
      showLoginModal()
    }
  })
}

// 開發者服務端登入
function postLogin (code, iv, encryptedData, callback) {
  let params = {
    code: code,
    iv: iv,
    encryptedData: encryptedData
  }
  request(apiUrl.postLogin, params, 'post').then((res) => {
    if (res.code == 1) {
      wx.hideLoading()
      // 登入成功,
      // 使用token管理登入态的,存儲全局token,用于當做登入态判斷,
      // 使用cookie管理登入态的,可以存任意變量當做已登入狀态
      store.commit('storeUpdateToken', res.data.token)
      callback && callback()
    } else {
      showToast()
    }
  }).catch((err) => {
    showToast()
  })
}

// 顯示toast彈窗
function showToast (content = '登入失敗,請稍後再試') {
  wx.showToast({
    title: content,
    icon: 'none'
  })
}
           

到此為止,登入就算完成了。不管使用token還是cookie都可以,都能有正常的登入态了,可以執行後續操作。

整個流程是 wx.login => wx.getUserInfo => 開發者伺服器登入postLogin。

調用接口:

某些頁面預設不需要登入,但某些使用者操作事件是需要登入狀态的,是以一者可以判斷全局存儲的登入狀态管理的變量,如果為false,那麼直接可以彈窗提示需要一鍵登入。二者如果全局狀态為true,則調用接口看接口傳回的code是否是未登入狀态(此情況一般來說是登入态過期),未登入的話也彈窗提示需要一鍵登入。

某頁面(需登入的使用者操作)

getPlayer () {
    // 判斷全局是否有登入狀态,如果沒有直接彈窗提示一鍵登入
    isLogin(() => {
        let params = {
            token: this.token
        }
        request(apiUrl.getPlayer, params).then((res) => {
            // TODO: 删除列印
            if (res.code === 1) {
                store.commit('storeUpdateUser', res.data.player_info)
            } else {
                // 擷取失敗了,如果是code是未登入,則去登入,然後執行回調函數this.getPlayer
                // 如果code不是未登入,直接彈窗報錯誤資訊
                handleError(res, this.getPlayer)
            }
        }).catch((err) => {
            handleError(err)
        })
    })
}
           

handleLogin.js

// 判斷是否登入
function isLogin (callback) {
  let token = store.state.token
  if (token) {
    // 如果有全局存儲的登入态,暫時認為他是登入狀态
    callback && callback()
  } else {
    // 如果沒有登入态,彈窗提示一鍵登入
    showLoginModal()
  }
}

// 接口調用失敗處理,
function handleError (res, callback) {
  // 規定-3041和-3042分别代表未登入和登入态失效
  if (res.code == -3041 || res.code == -3042) {
    // 彈窗提示一鍵登入
    showLoginModal()
  } else if (res.msg) {
    // 彈窗顯示錯誤資訊
    showToast(res.msg)
  }
}
           

到此為止,需要登入的使用者操作就可以處理了。如果全局登入狀态變量為true,先去調用接口,根據傳回的資訊是否是未登入再處理。

彈窗提示:

由于微信小程式授權的接口wx.getUserInfo和wx.authorize中scope 為 “scope.userInfo” ,新版中調用這兩個API是不會主動觸發彈出授權視窗的。需要使用

<button open-type="getUserInfo"></button>

方法。

上面代碼中多處出現的showLoginModal是用于顯示一鍵登入的。如下:

handleLogin.js

// 顯示一鍵登入的彈窗
function showLoginModal () {
  wx.showModal({
    title: '提示',
    content: '你還未登入,登入後可獲得完整體驗 ',
    confirmText: '一鍵登入',
    success (res) {
      // 點選一鍵登入,去授權頁面
      if (res.confirm) {
        wx.navigateTo({
          url: '授權登入頁面位址',
        })
      }
    }
  })
}
           

關于授權登入,我們做了一個專門的頁面處理,此處的button為

<button type="primary" v-if="canIUse" open-type="getUserInfo" @getuserinfo="getUserInfo">微信登入</button>

。如下:

getUserInfo (e) {
    if (e.target.userInfo) {
        // 點選Button彈窗授權,如果授權了,執行login
        // 因為Login流程中有wx.getUserInfo,此時就可以擷取到了
        login(() => {
            // 登入成功後,傳回
            wx.navigateBack()
        })
    }
}
           

參考:新版小程式登入授權

15.ES5和ES6的繼承

/* es5 建立一個Person 構造函數 */
function Person (name,age) {
    this.name = name
    this.age = age
}
/* 定義原型鍊上的方法sayholle */
/* 為什麼要将方法定義在原型上,定義在原型上的方法,所有的執行個體對象都共享 
 不會出現沒實列一個對象都重新建立一個這個方法 */
Person.prototype.sayholle = function () {
    console.log(this.name+' hello'+ this.age)
}

let person1 = new Person('czcz','23')
person1.sayholle()  //  czcz holle23

/* es6 使用class建立一個對象 */
class Personclass {
    /* 實列對象時預設調用的方法 */
    constructor (name,age) {
        this.name = name
        this.age = age
    }
    /* 定義一個方法,相對構造上述原型鍊上的方法 */

    sayholle () {
        console.log(this.name+' hello'+ this.age)
    }
}
let person2 = new Personclass('czcz','26')
person2.sayholle()  //  czcz holle23
           

es5中繼承的方式:

(1)原型鍊繼承

/* es5原型鍊繼承 */
function Person (name,age) {
    this.name = name
    this.age = age
}
Person.prototype.sayhello = function () {
    console.log(this.name+' hello'+ this.age)
}

function Child (sex) {
    this.sex = sex;
}
Child.prototype = new Person();
Child.prototype.hh = 'ddd'
let p = new Child('man')
console.log(p) // 
console.log(new Person());
let p2 = new Child('man')
p2.__proto__.age = '36'
/* 給p2原型上的age指派,則導緻p上的age也改變,父類構造函數上的屬性被所有子類共享 */
console.log(p) // 36
/* 缺點,child 新增的屬性隻能在new person 以後,建立實列時無法向
    父類的構造函數傳送參數,因為直接是指定了原型,所有也不能實作多繼承
    父類構造函數上的屬性被所有子類共享
*/
           

(2)構造函數繼承

/* es5構造函數繼承 */
function Person (name,age) {
    this.name = name
    this.age = age
}
Person.prototype.sayhello = function () {
    console.log(this.name+' hello'+ this.age)
}
function Child (sex,name,age) {
    this.sex = sex
    Person.call(this,name,age)
}

let p = new Child('man','czklove','13')
console.log(p);
/* 
    可以是先多繼承,隻要執行多個call
    建立實列時能像父類構造函數船體參數
    不會出現父類屬性,所有子類構造函數共享
    缺點,
    不能繼承父類原型鍊上的方法,如上面不能掉用sayhello方法
    子類構造函數的實列,原型鍊上并不存在父類構造函數,
    因為不能繼承父類原型鍊上的函數,所有要繼承函數隻能定義在父類構造函數上,
    不能達到函數複用
 */
           

(3)組合繼承,融合了上面兩種方式

/* es5組合繼承 */
function Person (name,age) {
    this.name = name
    this.age = age
}
Person.prototype.sayhello = function () {
    console.log(this.name+' hello'+ this.age)
}
function Child (sex,name,age) {
    this.sex = sex
    Person.call(this,name,age)
}
Child.prototype = new Person();
/* 重新設定一下constructor 不設定也沒有影響,嚴謹的角度上來說還是設定一下*/
/* 不設定的話,__proto__ 上時沒有 constructor */
/* 正常來講constructor是指向自身的 */
Child.prototype.constructor = Child;
let p = new Child('man','czklove','13')
let p1 = new Child('man','czklove1','16')
p.sayhello(); // czklove holle13
console.log(p);


child {sex: "man", name: "czklove", age: "13"}

age: "13"
name: "czklove"
sex: "man"
__proto__: person

age: undefined
constructor: ƒ Child(sex,name,age)
name: undefined
__proto__: Object



    /*
    組合繼承,既能達到對父類屬性的繼承,也能繼承父類原型上的方法
    父類屬性繼承也不會在所有子類的實列上共享
    唯一缺點,子類原型上有父類構造函數的屬性,也就是多了一份屬性
    */


console.log(p.__proto__ === Child.prototype) //true
           

(4)優化版的組合繼承(寄生組合繼承)

/* es5寄生組合繼承 */
function Person (name,age) {
    this.name = name
    this.age = age
}
Person.prototype.sayhello = function () {
    console.log(this.name+' hello'+ this.age)
}
function Child (sex,name,age) {
    this.sex = sex
    Person.call(this,name,age)
}
Child.prototype = Object.create(Person.prototype);
Child.prototype.constructor = Child
let p = new Child('man','czklove','13')
p.sayhello(); // czklove holle13
console.log(p);
/*  child {sex: "man", name: "czklove", age: "13"}
    age: "13"
    name: "czklove"
    sex: "man"
    __proto__: person
    constructor: ƒ child(sex,name,age)
    __proto__:
    sayholle: ƒ ()
    constructor: ƒ person(name,age)
    __proto__: Object */
           

原理:

首先對于屬性方面,一般是在子構造函數裡面調用父構造函數,通過call改變執行上下文,Father.call(this, name); 完成屬性繼承。

其次對原型上的方法繼承,根據原型的向上查找規則,首先建立一個對象(該對象的__proto__指向父構造函數的原型),然後将該對象賦給子構造函數的原型

前端面試總結四

es6 class的繼承:

/* esl class */
class Person {
    constructor (name,age) {
        this.name = name
        this.age = age
    }
    sayhello () {
        console.log(this.name+ ' hello '+this.age)
    }
}

class Child extends Person {
    constructor (name,age,sex) {
        /*  執行父類的構造函數 
            子類必須在構造函數中掉用super
            */
        super(name,age)
        /* 使用this一定要在super 之後 */
        this.sex = sex
    }
}

let p = new Child('czklove','23','man')
console.log(p)
/*  child {name: "czklove", age: "23", sex: "man"}
    age: "23"
    name: "czklove"
    sex: "man"
    __proto__: person
    constructor: class child
    __proto__:
    constructor: class person
    syaholle: ƒ syaholle()
    __proto__: Object */
           

原理:

ES6封裝了class,extends關鍵字來實作繼承。

子類通過extends繼承父類,然後通過在子類中構造函數 super(name); 完成屬性繼承。注意這句代碼需要在構造函數中的第一行。

差別于ES5,這裡不再需要修改子類的原型的構造函數指向。

前端面試總結四

ES5的繼承實質上是先建立子類的執行個體對象,然後再将父類的方法添加到this上(Parent.apply(this)),然後再把原型鍊繼承。

ES6的繼承機制完全不同,實質上是先建立父類的執行個體對象this(是以必須先調用父類的super()方法,才可使用this關鍵字,否則報錯。),然後再用子類的構造函數修改this實作繼承。

參考:es5繼承和es6類和繼承

16.ES6中的class類

傳統的javascript中隻有對象,沒有類的概念。它是基于原型的面向對象語言。原型對象特點就是将自身的屬性共享給新對象。

如果要生成一個對象執行個體,需要先定義一個構造函數,然後通過new操作符來完成。構造函數示例:

//函數名和執行個體化構造名相同且大寫(非強制,但這麼寫有助于區分構造函數和普通函數)
function Person(name,age) {
    this.name = name;
    this.age=age;
}
Person.prototype.say = function(){
    return "我的名字叫" + this.name+"今年"+this.age+"歲了";
}
var obj=new Person("laotie",88);//通過構造函數建立對象,必須使用new 運算符
console.log(obj.say());//我的名字叫laotie今年88歲了
           

構造函數生成執行個體的執行過程:

(1)當使用了構造函數,并且new 構造函數(),背景會隐式執行new Object()建立對象;

(2)将構造函數的作用域給新對象,(即new Object()建立出的對象),而函數體内的this就代表new Object()出來的對象。

(3)執行構造函數的代碼。

(4)傳回新對象(背景直接傳回);

ES6引入了Class(類)這個概念,通過class關鍵字可以定義類。該關鍵字的出現使得其在對象寫法上更加清晰,更像是一種面向對象的語言。如果将之前的代碼改為ES6的寫法就會是這個樣子:

class Person{//定義了一個名字為Person的類
    constructor(name,age){//constructor是一個構造方法,用來接收參數
        this.name = name;//this代表的是執行個體對象
        this.age=age;
    }
    say(){//這是一個類的方法,注意千萬不要加上function
        return "我的名字叫" + this.name+"今年"+this.age+"歲了";
    }
}
var obj=new Person("laotie",88);
console.log(obj.say());//我的名字叫laotie今年88歲了
           

注意項:

(1)在類中聲明方法的時候,千萬不要給該方法加上function關鍵字

(2)方法之間不要用逗号分隔,否則會報錯

由下面代碼可以看出類實質上就是一個函數。類自身指向的就是構造函數。是以可以認為ES6中的類其實就是構造函數的另外一種寫法!

console.log(typeof Person);//function
console.log(Person===Person.prototype.constructor);//true
           

以下代碼說明構造函數的prototype屬性,在ES6的類中依然存在着。

console.log(Person.prototype);//輸出的結果是一個對象

實際上類的所有方法都定義在類的prototype屬性上。代碼證明下:

Person.prototype.say=function(){//定義與類中相同名字的方法。成功實作了覆寫!
    return "我是來證明的,你叫" + this.name+"今年"+this.age+"歲了";
}
var obj=new Person("laotie",88);
console.log(obj.say());//我是來證明的,你叫laotie今年88歲了
           

當然也可以通過prototype屬性對類添加方法。如下:

Person.prototype.addFn=function(){
    return "我是通過prototype新增加的方法,名字叫addFn";
}
var obj=new Person("laotie",88);
console.log(obj.addFn());//我是通過prototype新增加的方法,名字叫addFn
           

還可以通過Object.assign方法來為對象動态增加方法

Object.assign(Person.prototype,{
    getName:function(){
        return this.name;
    },
    getAge:function(){
        return this.age;
    }
})
var obj=new Person("laotie",88);
console.log(obj.getName());//laotie
console.log(obj.getAge());//88
           

constructor方法是類的構造函數的預設方法,通過new指令生成對象執行個體時,自動調用該方法。

class Box{
    constructor(){
        console.log("啦啦啦,今天天氣好晴朗");//當執行個體化對象時該行代碼會執行。
    }
}
var obj=new Box();
           

constructor方法如果沒有顯式定義,會隐式生成一個constructor方法。是以即使你沒有添加構造函數,構造函數也是存在的。constructor方法預設傳回執行個體對象this,但是也可以指定constructor方法傳回一個全新的對象,讓傳回的執行個體對象不是該類的執行個體。

class Desk{
    constructor(){
        this.xixi="我是一隻小小小小鳥!哦";
    }
}
class Box{
    constructor(){
       return new Desk();// 這裡沒有用this哦,直接傳回一個全新的對象
    }
}
var obj=new Box();
console.log(obj.xixi);//我是一隻小小小小鳥!哦
           

constructor中定義的屬性可以稱為執行個體屬性(即定義在this對象上),constructor外聲明的屬性都是定義在原型上的,可以稱為原型屬性(即定義在class上)。hasOwnProperty()函數用于判斷屬性是否是執行個體屬性。其結果是一個布爾值, true說明是執行個體屬性,false說明不是執行個體屬性。in操作符會在通過對象能夠通路給定屬性時傳回true,無論該屬性存在于執行個體中還是原型中。

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
var box=new Box(12,88);
console.log(box.hasOwnProperty("num1"));//true
console.log(box.hasOwnProperty("num2"));//true
console.log(box.hasOwnProperty("sum"));//false
console.log("num1" in box);//true
console.log("num2" in box);//true
console.log("sum" in box);//true
console.log("say" in box);//false
           

類的所有執行個體共享一個原型對象,它們的原型都是Person.prototype,是以proto屬性是相等的

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
//box1與box2都是Box的執行個體。它們的__proto__都指向Box的prototype
var box1=new Box(12,88);
var box2=new Box(40,60);
console.log(box1.__proto__===box2.__proto__);//true
           

由此,也可以通過proto來為類增加方法。使用執行個體的proto屬性改寫原型,會改變Class的原始定義,影響到所有執行個體,是以不推薦使用!

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
var box1=new Box(12,88);
var box2=new Box(40,60);
box1.__proto__.sub=function(){
    return this.num2-this.num1;
}
console.log(box1.sub());//76
console.log(box2.sub());//20
           

class不存在變量提升,是以需要先定義再使用。因為ES6不會把類的聲明提升到代碼頭部,但是ES5就不一樣,ES5存在變量提升,可以先使用,然後再定義。

//ES5可以先使用再定義,存在變量提升
new A();
function A(){

}
//ES6不能先使用再定義,不存在變量提升 會報錯
new B();//B is not defined
class B{

}
           

參考:es6中class類的全方面了解(一)

17.Object.create()

Object.create(proto, [propertiesObject])

//方法建立一個新對象,使用現有的對象來提供新建立的對象的proto。

  • proto : 必須。表示建立對象的原型對象,即該參數會被指派到目标對象(即新對象,或說是最後傳回的對象)的原型上。該參數可以是null, 對象, 函數的prototype屬性 (建立空的對象時需傳null , 否則會抛出TypeError異常)。
  • propertiesObject : 可選。 添加到新建立對象的可枚舉屬性(即其自身的屬性,而不是原型鍊上的枚舉屬性)對象的屬性描述符以及相應的屬性名稱。這些屬性對應Object.defineProperties()的第二個參數。

案例說明:

(1)建立對象的方式不同

new Object() 通過構造函數來建立對象, 添加的屬性是在自身執行個體下。

Object.create() es6建立對象的另一種方式,可以了解為繼承一個對象, 添加的屬性是在原型下。

// new Object() 方式建立
var a = {  rep : 'apple' }
var b = new Object(a)
console.log(b) // {rep: "apple"}
console.log(b.__proto__) // {}
console.log(b.rep) // {rep: "apple"}

// Object.create() 方式建立
var a = { rep: 'apple' }
var b = Object.create(a)
console.log(b)  // {}
console.log(b.__proto__) // {rep: "apple"}
console.log(b.rep) // {rep: "apple"}
           

Object.create()方法建立的對象時,屬性是在原型下面的,也可以直接通路 b.rep // {rep: “apple”} ,

此時這個值不是吧b自身的,是它通過原型鍊proto來通路到b的值。

(2)建立對象屬性的性質不同

// 建立一個以另一個空對象為原型,且擁有一個屬性p的對象
o = Object.create({}, { p: { value: 42 } })

// 省略了的屬性特性預設為false,是以屬性p是不可寫,不可枚舉,不可配置的:
o.p = 24
o.p
//42

o.q = 12
for (var prop in o) {
   console.log(prop)
}
//"q"

delete o.p
//false
           

Object.create() 用第二個參數來建立非空對象的屬性描述符預設是為false的,而構造函數或字面量方法建立的對象屬性的描述符預設為true。看下圖解析:

前端面試總結四

(3)建立空對象時不同

前端面試總結四

當用構造函數或對象字面量方法建立空對象時,對象時有原型屬性的,即有_proto_;

當用Object.create()方法建立空對象時,對象是沒有原型屬性的。

(4)proto 屬性

JavaScript 的對象繼承是通過原型鍊實作的。ES6 提供了更多原型對象的操作方法。

__proto__屬性(前後各兩個下劃線),用來讀取或設定目前對象的prototype對象。目前隻有浏覽器環境必須部署有這個屬性,其他運作環境不一定要部署,是以不建議使用這個屬性,而是使用下面這些來 Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)、Object.create()(生成操作)代替。

Object.create()

描述:該方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__;

格式:Object.create(proto[, propertiesObject])

用法:如果用傳統的方法要給一個對象的原型上添加屬性和方法,是通過 propt 實作的

var proto = {
    y: 20,
    z: 40,
    showNum(){}
};
var o = Object.create(proto);
           
前端面試總結四

如果是不用Object,create()方法,我們是如何給對象原型添加屬性和方法的?

------ 通過構造函數或者類,例如:

//建立一個構造函數或者類
var People = function(){}
People.prototype.y = 20
People.prototype.showNum = function() {}
//通過構造函數建立執行個體
var p = new People();
console.log(p.__proto__ === People.prototype) // true
           
前端面試總結四

Object.setPrototypeOf

描述:該方法的作用與 proto 相同,用來設定一個對象的 prototype 對象,傳回參數對象本身。它是 ES6 正式推薦的設定原型對象的方法。

格式:Object.setPrototypeOf(object, prototype)

用法:

var proto = {
    y: 20,
    z: 40
};
var o = { x: 10 };
Object.setPrototypeOf(o, proto);
           
前端面試總結四

輸出結果中看出,添加的方法是在原型上的。就類似于

Object.getPrototypeOf()

描述:用于讀取一個對象的原型對象;

格式:Object.getPrototypeOf(obj);

用法:

Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
           

參考:https://www.jianshu.com/p/28d85bebe599

18.flex:1

flex 是 flex-grow、flex-shrink、flex-basis的縮寫。故其取值可以考慮以下情況:

flex 的預設值是以上三個屬性值的組合。假設以上三個屬性同樣取預設值,則 flex 的預設值是 0 1 auto。同理,如下是等同的:

.item {flex: 2333 3222 234px;}
.item {
    flex-grow: 2333;
    flex-shrink: 3222;
    flex-basis: 234px;
}
           

當 flex 取值為 none,則計算值為 0 0 auto,如下是等同的:

.item {flex: none;}
.item {
    flex-grow: 0;
    flex-shrink: 0;
    flex-basis: auto;
}
           

當 flex 取值為 auto,則計算值為 1 1 auto,如下是等同的:

.item {flex: auto;}
.item {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: auto;
}
           

當 flex 取值為一個非負數字,則該數字為 flex-grow 值,flex-shrink 取 1,flex-basis 取 0%,如下是等同的:

.item {flex: 1;}
.item {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: 0%;
}
           

當 flex 取值為一個長度或百分比,則視為 flex-basis 值,flex-grow 取 1,flex-shrink 取 1,有如下等同情況(注意 0% 是一個百分比而不是一個非負數字):

.item-1 {flex: 0%;}
.item-1 {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: 0%;
}
.item-2 {flex: 24px;}
.item-1 {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: 24px;
}
           

當 flex 取值為兩個非負數字,則分别視為 flex-grow 和 flex-shrink 的值,flex-basis 取 0%,如下是等同的:

.item {flex: 2 3;}
.item {
    flex-grow: 2;
    flex-shrink: 3;
    flex-basis: 0%;
}
           

當 flex 取值為一個非負數字和一個長度或百分比,則分别視為 flex-grow 和 flex-basis 的值,flex-shrink 取 1,如下是等同的:

.item {flex: 2333 3222px;}
.item {
    flex-grow: 2333;
    flex-shrink: 1;
    flex-basis: 3222px;
}
           

lex-basis 規定的是子元素的基準值。是以是否溢出的計算與此屬性息息相關。flex-basis 規定的範圍取決于 box-sizing。這裡主要讨論以下 flex-basis 的取值情況:

  • auto:首先檢索該子元素的主尺寸,如果主尺寸不為 auto,則使用值采取主尺寸之值;如果也是 auto,則使用值為 content。
  • content:指根據該子元素的内容自動布局。有的使用者代理沒有實作取 content 值,等效的替代方案是 flex-basis 和主尺寸都取 auto。
  • 百分比:根據其包含塊(即伸縮父容器)的主尺寸計算。如果包含塊的主尺寸未定義(即父容器的主尺寸取決于子元素),則計算結果和設為 auto 一樣。

舉一個不同的值之間的差別:

<div class="parent">
    <div class="item-1"></div>
    <div class="item-2"></div>
    <div class="item-3"></div>
</div>
 
<style type="text/css">
    .parent {
        display: flex;
        width: 600px;
    }
    .parent > div {
        height: 100px;
    }
    .item-1 {
        width: 140px;
        flex: 2 1 0%;
        background: blue;
    }
    .item-2 {
        width: 100px;
        flex: 2 1 auto;
        background: darkblue;
    }
    .item-3 {
        flex: 1 1 200px;
        background: lightblue;
    }
</style>
           

主軸上父容器總尺寸為 600px

子元素的總基準值是:0% + auto + 200px = 300px,其中

  • 0% 即 0 寬度
  • auto 對應取主尺寸即 100px

故剩餘空間為 600px - 300px = 300px

伸縮放大系數之和為: 2 + 2 + 1 = 5

剩餘空間配置設定如下:

  • item-1 和 item-2 各配置設定 2/5,各得 120px
  • item-3 配置設定 1/5,得 60px

各項目最終寬度為:

  • item-1 = 0% + 120px = 120px
  • item-2 = auto + 120px = 220px
  • item-3 = 200px + 60px = 260px

當 item-1 基準值取 0% 的時候,是把該項目視為零尺寸的,故即便聲明其尺寸為 140px,也并沒有什麼用,形同虛設

而 item-2 基準值取 auto 的時候,根據規則基準值使用值是主尺寸值即 100px,故這 100px 不會納入剩餘空間

參考:flex:1;詳解

19.TCP和UDP

TCP與UDP差別總結:

(1)TCP面向連接配接(如打電話要先撥号建立連接配接);UDP是無連接配接的,即發送資料之前不需要建立連接配接

(2)TCP提供可靠的服務。也就是說,通過TCP連接配接傳送的資料,無差錯,不丢失,不重複,且按序到達;UDP盡最大努力傳遞,即不保證可靠傳遞

Tcp通過校驗和,重傳控制,序号辨別,滑動視窗、确認應答實作可靠傳輸。如丢包時的重發控制,還可以對次序亂掉的分包進行順序控制。

(3)UDP具有較好的實時性,工作效率比TCP高,适用于對高速傳輸和實時性有較高的通信或廣播通信。

(4)每一條TCP連接配接隻能是點到點的;UDP支援一對一,一對多,多對一和多對多的互動通信

(5)TCP對系統資源要求較多,UDP對系統資源要求較少。

為什麼UDP有時比TCP更有優勢?

UDP以其簡單、傳輸快的優勢,在越來越多場景下取代了TCP,如實時遊戲。

(1)網速的提升給UDP的穩定性提供可靠網絡保障,丢包率很低,如果使用應用層重傳,能夠確定傳輸的可靠性。

(2)TCP為了實作網絡通信的可靠性,使用了複雜的擁塞控制算法,建立了繁瑣的握手過程,由于TCP内置的系統協定棧中,極難對其進行改進。

采用TCP,一旦發生丢包,TCP會将後續的包緩存起來,等前面的包重傳并接收到後再繼續發送,延時會越來越大,基于UDP對實時性要求較為嚴格的情況下,采用自定義重傳機制,能夠把丢包産生的延遲降到最低,盡量減少網絡問題對遊戲性造成影響。

為什麼TCP不适用于實時傳輸?

TCP影響實時性不是因為握手消耗時間。握手一開始建立完就沒事了

一般來說,機關時間内傳輸的資料流量比較平滑。 TCP依賴滑動視窗進行流量控制,滑動視窗大小是自适應的,影響滑動視窗主要有兩個因素,一是網絡延時,二是傳輸速率,滑動視窗的大小與延時成正比,與傳輸速率也成正比。在給定的網絡環境下,延時可以認為是固定的,是以滑動視窗僅與傳輸速率有關,當傳輸實時資料時,因為資料流通量比較固定,是以這時TCP上的滑動視窗會處于一個不大不小的固定值,這個值大小恰好保證目前生産的資料實時傳輸到對方,當出現網絡丢包時,按TCP協定(快速恢複),滑動視窗将減少到原來的一半,是以速率立刻減半,此時發送速率将小于資料生産速率,一些資料将滞留在發送端,然後滑動視窗将不斷增大,直到積累的資料全部發送完畢。上述過程即為典型的TCP流量抖動過程,對于實時傳輸影響很大,可能形成較大的突發時延,從使用者感觀角度來說,就是有時比較流暢,但有時卡(“抖一下”,并且比較嚴重),是以實時傳輸通常不使用TCP。

應用場景:

比如普通的會議視訊圖像,當然首選UDP,畢竟丢幾包無所謂。

如果傳輸檔案等,不能丢包,用TCP

udp如何實作可靠性傳輸

UDP它不屬于連接配接型協定,因而具有資源消耗小,處理速度快的優點,是以通常音頻、視訊和普通資料在傳送時使用UDP較多,因為它們即使偶爾丢失一兩個資料包,也不會對接收結果産生太大影響。

傳輸層無法保證資料的可靠傳輸,隻能通過應用層來實作了。實作的方式可以參照tcp可靠性傳輸的方式,隻是實作不在傳輸層,實作轉移到了應用層。

實作确認機制、重傳機制、視窗确認機制。

目前有如下開源程式利用udp實作了可靠的資料傳輸。分别為RUDP、RTP、UDT。

基于UDP的資料傳輸協定(UDP-based Data Transfer Protocol,簡稱UDT)是一種網際網路資料傳輸協定。UDT的主要目的是支援高速廣域網上的海量資料傳輸,而網際網路上的标準資料傳輸協定TCP在高帶寬長距離網絡上性能很差。 顧名思義,UDT建于UDP之上,并引入新的擁塞控制和資料可靠性控制機制。UDT是面向連接配接的雙向的應用層協定。

參考:TCP和UDP的差別和優缺點

20.HTTP的長連接配接和短連接配接

HTTP的長連接配接和短連接配接本質上是TCP長連接配接和短連接配接。HTTP屬于應用層協定.

短連接配接:

浏覽器和伺服器每進行一次HTTP操作,就建立一次連接配接,但任務結束就中斷連接配接。

長連接配接:

當一個網頁打開完成後,用戶端和伺服器之間用于傳輸HTTP資料的 TCP連接配接不會關閉,如果用戶端再次通路這個伺服器上的網頁,會繼續使用這一條已經建立的連接配接。Keep-Alive不會永久保持連接配接,它有一個保持時間,可以在不同的伺服器軟體(如Apache)中設定這個時間。實作長連接配接要用戶端和服務端都支援長連接配接。

TCP短連接配接:

client向server發起連接配接請求,server接到請求,然後雙方建立連接配接。client向server發送消息,server回應client,然後一次讀寫就完成了,這時候雙方任何一個都可以發起close操作,不過一般都是client先發起 close操作.短連接配接一般隻會在client/server間傳遞一次讀寫操作

TCP長連接配接:

client向server發起連接配接,server接受client連接配接,雙方建立連接配接。Client與server完成一次讀寫之後,它們之間的連接配接并不會主動關閉,後續的讀寫操作會繼續使用這個連接配接。

繼續閱讀