前端技術演進(二):前端與協議

姜小抖發表於2019-03-04
這個來自之前做的培訓,刪減了一些業務相關的,參考了很多資料(參考資料列表),謝謝前輩們,麼麼噠 ?

目前前後端的通訊一般都是通過協議來完成的,這裡介紹和前端開發相關的各類協議。

HTTP

HTTP 協議是Hyper Text Transfer Protocol(超文字傳輸協議)的縮寫,是全球資訊網的資料通訊的基礎。設計HTTP最初的目的是為了提供一種釋出和接收HTML頁面的方法,現在基本上什麼型別的檔案都可以傳輸了,比如圖片、CSS、JS、資料包文等。

HTTP一般基於TCP/IP通訊協議來傳遞資料,它有如下特點:

  1. 簡單快速:客戶向伺服器請求服務時,只需傳送請求方法和路徑。請求方法常用的有GET、HEAD、POST。每種方法規定了客戶與伺服器聯絡的型別不同。由於HTTP協議簡單,使得HTTP伺服器的程式規模小,因而通訊速度很快。
  2. 靈活:HTTP允許傳輸任意型別的資料物件。正在傳輸的型別由Content-Type加以標記。
  3. 無連線:無連線的含義是限制每次連線只處理一個請求。伺服器處理完客戶的請求,並收到客戶的應答後,即斷開連線。採用這種方式可以節省傳輸時間。
  4. 無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺少狀態意味著如果後續處理需要前面的資訊,則它必須重傳,這樣可能導致每次連線傳送的資料量增大。另一方面,在伺服器不需要先前資訊時它的應答就較快。
  5. 支援B/S及C/S模式。

HTTP使用統一資源識別符號(Uniform Resource Identifiers, URI)來傳輸資料和建立連線。URL是一種特殊型別的URI,包含了用於查詢某個資源的足夠的資訊。一個完整的URL一般包括以下幾部分:

協議 域名 虛擬目錄 檔名 引數

比如:http://test.google.com:80/test/test.html?query=admin#home

請求訊息Request

通常一個HTTP請求訊息包含如下內容:請求行、請求頭、空行、訊息主體。

image.png | center | 466x165

比如:

 GET /books/?sex=man&name=Professional HTTP/1.1
 Host: www.example.com
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
 Gecko/20050225 Firefox/1.0.1
 Connection: Keep-Alive
複製程式碼
  • 請求行:GET /books/?sex=man&name=Professional HTTP/1.1 用來說明請求型別,要訪問的資源以及所使用的HTTP版本。比較常見的請求型別有GET,POST,PUT,DELETE,OPTIONS等。
  • 請求頭:從第二行起為請求頭部,用來說明伺服器要使用的附加資訊。
  • 空行:請求頭部後面的空行是必須的,即使請求資料為空,也必須有空行。
  • 請求資料:也叫請求主體,可以新增任意的其他資料,這個例子的請求資料為空。

帶有請求資料的POST請求:

 POST / HTTP/1.1
 Host: www.example.com
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
 Gecko/20050225 Firefox/1.0.1
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 40
 Connection: Keep-Alive

 sex=man&name=Professional  複製程式碼

響應訊息Response

一般情況下,伺服器接收並處理客戶端發過來的請求後會返回一個HTTP的響應訊息。通常一個HTTP請求訊息包含如下內容:狀態行、訊息報頭、空行、響應正文。

比如:

HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8

<html>
      <head></head>
      <body>
            <!--body goes here-->
      </body>
</html>複製程式碼
  • 狀態行:由HTTP協議版本號, 狀態碼, 狀態訊息 三部分組成。
  • 訊息報頭:用來說明客戶端要使用的一些附加資訊。
  • 空行:訊息報頭後面的空行是必須的。
  • 響應正文,伺服器返回給客戶端的文字資訊。

狀態碼

狀態碼由三位數字組成,第一個數字定義了響應的類別,共分五種類別:

  • 1xx:指示資訊--表示請求已接收,繼續處理。
  • 2xx:成功--表示請求已被成功接收、理解、接受。
  • 3xx:重定向--要完成請求必須進行更進一步的操作。
  • 4xx:客戶端錯誤--請求有語法錯誤或請求無法實現。
  • 5xx:伺服器端錯誤--伺服器未能實現合法的請求。

常見的狀態碼有如下幾種:

  • 200 OK 客戶端請求成功。
  • 301 Moved Permanently 請求永久重定向。
  • 302 Moved Temporarily 請求臨時重定向。
  • 304 Not Modified 檔案未修改,可以直接使用快取的檔案。
  • 400 Bad Request 由於客戶端請求有語法錯誤,不能被伺服器所理解。
  • 401 Unauthorized 請求未經授權。
  • 403 Forbidden 伺服器收到請求,但是拒絕提供服務。伺服器通常會在響應正文中給出不提供服務的原因。
  • 404 Not Found 請求的資源不存在,例如,輸入了錯誤的URL。
  • 500 Internal Server Error 伺服器發生不可預期的錯誤,導致無法完成客戶端的請求。
  • 503 Service Unavailable 伺服器當前不能夠處理客戶端的請求,在一段時間之後,伺服器可能會恢復正常。

HTTP和TCP/IP協議的關係

image.png | left | 827x1208

HTTP/2

HTTP2即超文字傳輸協議2.0版本,是HTTP協議的下一個版本。因為標準委員會不打算再發布子版本了,所以直接叫HTTP/2,而不叫HTTP/2.0。

HTTP2相對於之前的HTTP協議有以下幾個優點:

  • HTTP 2採用完全二進位制的格式來傳輸資料。同時HTTP 2對訊息頭採用HPACK壓縮傳輸,最大限度地節省了傳輸頻寬。相比於HTTP 1.x 每次請求都會攜帶大量冗餘頭資訊(例如瀏覽器Cookie資訊等),HTTP2具有很大的優勢。
  • HTTP 2使用TCP多路複用的方式來降低網路請求連線建立和關閉的開銷,多個請求可以通過一個TCP連線來併發完成。
  • HTTP2支援傳輸流的優先順序和流量控制機制。HTTP2中每個檔案傳輸流都有自己的傳輸優先順序,並可以通過伺服器來動態改變,伺服器會保證優先順序高的檔案流先傳輸。例如在未來的瀏覽器端渲染中,伺服器端就可以優先傳輸CSS檔案保證頁面的渲染,然後在CSS檔案全部傳輸完成後載入JavaScript指令碼檔案。
  • 支援伺服器端推送。服務端能夠在特定條件下把資源主動推送給客戶端。

HTTP 1.1會讓資源排隊載入,如下圖所示:

image.png | center | 720x319

但當我們開啟了HTTP/2之後,有了TCP多路複用,個數幾乎沒有限制了,如下圖所示:

image.png | center | 720x571

HTTP/2 將 HTTP 協議通訊分解為二進位制編碼幀的交換,這些幀對應著特定資料流中的訊息。所有這些都在一個 TCP 連線內複用。這是 HTTP/2 協議所有其他功能和效能優化的基礎。

image.png | center | 827x645

目前支援HTTP2協議傳輸的瀏覽器依然很少,隨著技術的發展和瀏覽器的更新迭代,HTTP2的時代終會到來,但我們依然不能在短時間內企圖通過它來幫我們進行頁面優化。

HTTPS

HTTPS(超文字傳輸安全協議 Hypertext Transfer Protocol Secure)經由HTTP進行通訊,但利用SSL/TLS來加密資料包。HTTPS的主要思想是在不安全的網路上建立一安全通道。通常,HTTP 直接和 TCP 通訊。當使用 SSL 時,則演變成先和 SSL 通訊,再由 SSL 和 TCP 通訊了。簡言之,所謂 HTTPS,其實就是身披 SSL 協議這層外殼的 HTTP。HTTP的URL由“http://”起始且預設使用埠80,HTTPS的URL由“https://”起始且預設使用埠443。

SSL 是獨立於 HTTP 的協議,所以不光是 HTTP 協議,其他執行在應用層的 SMTP 和 Telnet 等協議均可配合 SSL(Secure Socket Layer) 協議使用。

image.png | center | 342x157

HTTP是不安全的,攻擊者通過監聽和中間人攻擊等手段,可以獲取網站帳戶和敏感資訊等。人們對 HTTPS 有一個普遍的錯誤認識,認為只有處理敏感通訊的網站才需要 HTTPS。 每個未受保護的 HTTP 請求都可能暴露與使用者行為和身份有關的資訊。儘管訪問一次未受保護的網站可能看上去無害,但一些入侵者會檢視彙總的使用者瀏覽活動,以推斷他們的行為和意圖,從而進行去匿名化攻擊,查出匿名使用者的身份。例如,員工可能在閱讀未受保護的醫療文章時不經意地向其僱主洩露敏感的健康資訊。

公鑰和私鑰

加密和解密同用一個金鑰的方式稱為共享金鑰加密(Common key crypto system),也被叫做對稱金鑰加密。

image.png | center | 600x456.23632385120345

以共享金鑰方式加密時必須將金鑰也發給對方。可究竟怎樣才能安全地轉交?在網際網路上轉發金鑰時,如果通訊被監聽那麼金鑰就可會落入攻擊者之手,同時也就失去了加密的意義。

SSL 採用一種叫做公開金鑰加密(Public-key cryptography)的加密處理方式。公開金鑰加密使用一對非對稱的金鑰。一把叫做私有金鑰(private key),另一把叫做公開金鑰(public key)。顧名思義,私有金鑰不能讓其他任何人知道,而公開金鑰則可以隨意釋出,任何人都可以獲得。

使用公開金鑰加密方式,傳送密文的一方使用對方的公開金鑰進行加密處理,對方收到被加密的資訊後,再使用自己的私有金鑰進行解密。利用這種方式,不需要傳送用來解密的私有金鑰,也不必擔心金鑰被攻擊者竊聽而盜走。

image.png | center | 600x464.6080760095012

HTTPS 採用混合加密機制:

image.png | center | 600x538.3863080684596

證照頒發機構

公開金鑰加密方式還是存在一些問題的。那就是無法證明公開金鑰本身就是貨真價實的公開金鑰。

證照頒發機構 (CA) 是一個組織,對公鑰和與公共 DNS 名稱之間的對映進行證實。例如,客戶端如何知道特定公鑰是否為 www.foobar.com 的真實公鑰?按理說,無法知道。CA 證實特定金鑰是特定網站的真實金鑰,它使用自己的私鑰來加密簽名該網站的公鑰。此簽名在計算上是無法偽造的。瀏覽器(和其他客戶端)維護信任錨儲存庫,它包含知名 CA 擁有的公鑰,並且它們使用這些公鑰來加密驗證 CA 的簽名。

SSL握手流程

image.png | center | 827x886

最後使用共享金鑰來進行以後的通訊,詳細流程:

image.png | center | 827x1564

Websocket

在實際的前端應用專案中,除了使用應答模式的HTTP協議進行普通網路資原始檔的請求載入外,有時也需要建立客戶端與服務端之間的實時連線進行通訊,例如網頁實時聊天的應用場景,這就必須涉及瀏覽器端的實時通訊協議了。對於這些對實時性要求較高的應用場景,普通的HTTP協議就並不適用。雖然前端可以通過Ajax定時向服務端輪詢的方式來持續獲取服務端的訊息,但是這種方式效率相對較低。

WebSocket是瀏覽器端和伺服器端建立實時連線的一種通訊協議,可以在伺服器和瀏覽器端建立類似Socket方式的訊息通訊。相對於HTTP1.1協議,WebSocket協議的優勢是方便伺服器和瀏覽器之間的雙向資料實時通訊。

7層網路模型

這裡只是類似Socket方式,WebSocket 是建立在 TCP/IP 協議之上,屬於應用層的協議,而 Socket 是在應用層和傳輸層中的一個抽象層,它是將 TCP/IP 層的複雜操作抽象成幾個簡單的介面來提供給應用層呼叫。簡單回顧一下7層網路模型:

image.png | center | 827x586

簡單來說,我們在傳輸資料時,可以只使用(傳輸層)TCP/IP協議,但是那樣的話,如果沒有應用層,便無法識別資料內容。如果想要使傳輸的資料有意義,則必須使用到應用層協議。應用層協議有很多,比如HTTP、FTP、TELNET等,也可以自己定義應用層協議。WEB使用HTTP協議作應用層協議,以封裝HTTP文字資訊,然後使用TCP/IP做傳輸層協議將它發到網路上。

TCP/IP只是一個協議棧,就像作業系統的執行機制一樣,必須要具體實現,同時還要提供對外的操作介面。這個就像作業系統會提供標準的程式設計介面,比如win32程式設計介面一樣,TCP/IP也要提供可供程式設計師做網路開發所用的介面,這就是Socket程式設計介面。Socket的出現只是使得程式設計師更方便地使用TCP/IP協議棧而已,是對TCP/IP協議的抽象,從而形成了我們知道的一些最基本的函式介面,比如create、listen、connect、accept、send、read和write等等。

image.png | center | 827x1172

Websocket使用

WebSocket 的實現分為握手,資料傳送/讀取,關閉連線。

image.png | center | 827x505

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};      複製程式碼

線上演示:html5demos.com/web-socket/

DDP

DDP ( Distributed Data Protocol,分散式資料協議)是一種新型的客戶端與伺服器端的實時通訊協議,由於相容性的原因,目前使用還不廣泛。

DDP使用JSON的資料格式在客戶端和瀏覽器之間進行資料傳輸通訊,所以對於前端開發者來說使用非常方便。有名的Meteor Web框架的雙向實時資料更新機制底層使用的就是DDP,這種協議模式下客戶端可向伺服器端發起遠端過程呼叫,客戶端也可以訂閱服務端資料,在服務端資料變化時,伺服器會向客戶端發起通知,觸發瀏覽器響應的操作。

//建立服務端
const DDPClient = require('ddp');
const client = new DDPClient({
  host: 'localhost',
  port: 3000
});

//監聽訊息
client.on('message', function(data, flags){
  console.log('[DDP訊息]: ', data);
});

//建立連線
client.connect(function(){
  client.subscribe('post',[],function(){
    console.log('[post訂閱訊息]');
  });
});複製程式碼

協議標準:github.com/meteor/mete…

RESTful

REST (Representational State Transfer,表述性狀態轉化)並不是某一種具體的協議,而是定義了一種網路應用軟體之間的架構關係並提出了一套與之對應的網路之間互動呼叫的規則。與之類似的例如早期的WebSevice,WebSevice現在基本都不用了。

在REST形式的軟體應用服務中,每個資源都有一個與之對應的URI地址,資源本身都是方法呼叫的目標,方法列表對所有資源都是一樣的,而且這些方法都推薦使用HTTP協議的標準方法,例如GET、POST、PUT、DELETE等。如果一個網路應用軟體的設計是按照REST定義的,我們就可以認為它使用的互動呼叫的方法設計遵循RESTful規範。換種方式理解,RESTful 是一種軟體架構之間互動呼叫資料的協議風格規範,它建議以一種通用的方式來定義和管理資料互動呼叫介面。

例如:對於書籍book的記錄管理介面,有增、刪、改、查操作,於是我們定義介面:

  • path/addBook
  • path/deleteBook
  • path/updateBook
  • path/getBook

看上去好像沒有什麼問題。後來,另一個專案也有類似的介面定義,卻可能叫作:

  • path/appendBook
  • path/delBook
  • path/modifyBook
  • path/getBook

接著有一天,專案負責人可能會說,要升級介面來滿足新的需求,於是我們又新增了:

  • path/addBook2
  • path/deleteBook2
  • path/updateBook2
  • path/getBook2

這樣用起來是沒有什麼問題,但是這些隨意的定義會增加資料介面維護難度和專案繼續開發的成本。

這時,我們或許會考慮使用文件或規範,規定一定要使用add來新增,新的介面版本號放前面 path/v2/addBook,開發的人必須嚴格按照文件規範去寫。這樣做很好,但依然不夠完善,原因有以下幾點:

  • 因為專案工作常常排期緊張,你可能沒時間去寫文件,或者後面接手的人不想去看文件。
  • 開發修改功能後很可能來不及或忘記去更新文件。
  • 無論文件寫得多清楚,我們看起來效率總是很低。

這時如果有一個風格更好的通用規範來定義資料互動介面,就不用這麼麻煩了。所以我們完全可以利用RESTful設計的規範特性來解決上面遇到的問題。對於書籍記錄操作介面的命名可以如下操作:

HTTP方法URI描述
POSTpath/v1/book新增書籍資訊
DELETEpath/v1/book刪除書籍資訊
PUTpath/v1/book更新書籍資訊
GETpath/v1/book獲取書籍資訊

使用RESTful規範來重新設計介面後,一切就變得很清晰自然,這樣新的工程師接手專案時,只要他足夠了解RESTful規範,幾乎沒有時間成本。即使他不瞭解RESTful規範,也可以很快地去了解,這就可以避免他去讀那份看似完善其實冗長雜的文件。

這裡涉及到了幾個設計的原則:

  • “資源”表示一種實體,所以應該是名詞,URL不應該有動詞,動詞應該放在HTTP協議中。
  • 按照標準,不應該在URL中包含版本號,應該放在HTTP請求頭資訊的Accept欄位中,不過這樣不夠直觀,所以一般的方式還是把版本號放在URL中,算是一個反模式。

RESTful API 的主要設計原則就是這些,總結來說就是結合HTTP的固有方式來表徵資源的狀態變化描述,而不是通過動詞加名詞的方式來設計。

Github RESTful API:developer.github.com/v3/

GraphQL

GraphQL 對 API 中的資料提供了一套易於理解的完整描述,使得客戶端能夠準確地獲得它需要的資料,而且沒有任何冗餘,也讓 API 更容易地隨著時間推移而演進。

GraphQL 解決的最重要的3個問題分別是:

  • 需要進行多次往返以獲取所需的資料:典型的 REST API 請求多個資源時得載入多個 URL,而 GraphQL 可以通過一次請求就獲取你應用所需的所有資料。
  • 客戶端依賴於服務端:消除了伺服器對資料內容進行硬編碼的需要。我們可以把客戶端與服務端分離開來,單獨進行維護和改進。前後端徹底分離。
  • 糟糕的前端開發體驗:使用 GraphQL,開發人員可以宣告式地來表達其使用者介面的資料需求,不用總是關注資料是如何獲取的。

RESTful APIs vs GraphQL APIs

一個簡單的示例:我們要做一個星球大戰人物資訊展示的UI介面:需要顯示人物的姓名,出生年份,星球名稱以及所有他們參演的電影的名稱。

這個 UI 的 JSON 資料可能類似於:

{
  "data": {
    "person": {
      "name": "Darth Vader",
      "birthYear": "41.9BBY",
      "planet": {
        "name": "Tatooine"
      },
      "films": [
        { "title": "A New Hope" },
        { "title": "The Empire Strikes Back" },
        { "title": "Return of the Jedi" },
        { "title": "Revenge of the Sith" }
      ]
    }
  }
}複製程式碼

如果使用 React.js ,一般會這樣表示檢視:

// The Container Component:
<PersonProfile person={data.person} ></PersonProfile>

// The PersonProfile Component:
Name: {person.name}
Birth Year: {person.birthYear}
Planet: {person.planet.name}
Films: {person.films.map(film => film.title)}複製程式碼

如果使用 RESTful API,我們可能這樣請求資料:

1、獲取人物資訊:

GET - /people/{id}複製程式碼
{
  "name": "Darth Vader",
  "birthYear": "41.9BBY",
  "planetId": 1
  "filmIds": [1, 2, 3, 6],
  ...
}複製程式碼

2、獲取星球資訊:

GET - /planets/1複製程式碼

3、獲取所有電影資訊:

GET - /films/1
GET - /films/2
GET - /films/3
GET - /films/6複製程式碼

演示

我們需要傳送6個請求才能獲取到所有需要的資料,每個獲取資料的方法都是命令式的。每個介面返回的資訊還有很多欄位不是我們所需要的。為了解決這個問題,我們可能會新增加一個介面,比如:

GET - /people/{id}/films複製程式碼

但是這樣就不是純粹的RESTful API了,而且後端要額外的建立這個介面,用來滿足前端的資料要求,如果增減欄位或物件,後端還要新增介面或者重新編碼。

如果使用GraphQL,我們可以這樣來查詢:

GET or POST - /graphql?query={...}複製程式碼

比如引數使用:

{
  person(personID: 4) {
    name,
    birthYear,
    homeworld {
      name
    },
    filmConnection {
      films {
        title
      }
    }
  }
}複製程式碼

演示

一個請求就完成了所有資料的獲取。

GraphQL的靈活性也會帶來一些問題,比如增加複雜度,資源耗盡攻擊,N+1查詢等。FB針對N+1給出了 dataloader 的方案。

Github GraphQL API:developer.github.com/v4/

線上除錯:developer.github.com/v4/explorer…

與Native互動

Hybrid App是在Native App應用的基礎上結合了Web App應用所形成的模式,一般稱之為混合App。從技術開發上來看,相比於傳統的桌面瀏覽器端的Web App,它具有以下幾方面的特徵:

  • 可用的系統網路資源更少。由於移動裝置CPU、記憶體、網路卡、網路連線多方面的限制,HybridApp的前端頁面可用的系統資源遠遠小於桌面瀏覽器。就網路連線來說,大部分移動裝置的使用者使用的仍是3G、4G的網路,頻寬和流量均有限制,和桌面瀏覽器的頻寬接入相比還是有著本質上的區別。
  • 支援更新的瀏覽器特性。目前智慧裝置瀏覽器種類相對較少,且隨著硬體裝置的快速更新,主流的瀏覽器以WebKit核心居多,支援較新的瀏覽器特性。不像桌面瀏覽器那樣需要考慮較低版本Internet Explorer的相容性問題。
  • 可實現離線應用。Hybrid的一個優勢是可以通過新的瀏覽器特性或Native的檔案讀取機制進行檔案級的檔案快取和離線更新。這是桌面瀏覽器,上較難做到的。這些離線機制常常可以用來彌補Hybrid App網路系統資源不足的缺點,讓瀏覽器指令碼更快從本地快取中載入。
  • 較多的機型考慮。由於目前移動裝置平臺不統一,而且不同裝置機型系統的瀏覽器實現仍有一定的區別,因此Hybrid App應用需要考慮不同裝置機型的相容性問題。
  • 支援與Native互動。Hybrid App的另一個特點是結合了移動端Native特性,可以在前端頁面中呼叫客戶端Native的能力,例如攝像頭、定位、感測器、本地檔案訪問等。

Web呼叫Native

在HTML5中呼叫Native程式一般有幾種較通用的方法:

一、通過URI請求。

Native應用可在移動端系統中註冊一個Scheme協議的URI,這個URI可在系統的任意地方授權訪問來調起一段原生方法或一個原生的介面。同樣,Native 的WebView控制元件中的JavaScript指令碼的請求也可以匹配呼叫這一通用的Scheme協議。例如我們通過對 window.location.href 賦值或使用iframe的方式傳送一個URI的請求,這個請求可以被Native應用的系統捕獲並調起Native應用註冊匹配的這個Scheme協議內容。

比如微信的scheme為(weixin://)。

image.png | center | 596x625

二、通過addJavascriptInterface(Android)或JavaScriptCore(iOS)注入方法到頁面中呼叫。

Android,原生Webview需要先註冊可供前端呼叫的JS函式:

// Android容器允許JS指令碼,必須要
webSettings.setJavaScriptEnabled(true);
// Android容器設定僑連物件
mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");

// Android4.2版本及以上,本地方法要加上註解@JavascriptInterface,否則會找不到方法。
private Object getJSBridge(){  
    Object insertObj = new Object(){  
        @JavascriptInterface
        public String foo(){  
            return "foo";  
        }  

        @JavascriptInterface
        public String foo2(final String param){  
            return "foo2:" + param;  
        }  

    };  
    return insertObj;  
}複製程式碼

然後H5中即可呼叫原生中註冊的函式:

// 呼叫方法一
window.JSBridge.foo(); // 返回:'foo'
// 呼叫方法二
window.JSBridge.foo2('test'); // 返回:'foo2:test'複製程式碼

iOS,需要引入JavaScriptCore庫:

#import <JavaScriptCore/JavaScriptCore.h>複製程式碼

然後原生需要註冊API:

//webview載入完畢後設定一些js介面
-(void)webViewDidFinishLoad:(UIWebView *)webView{
    [self hideProgress];
    [self setJSInterface];
}

-(void)setJSInterface{

    JSContext *context =[_wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

    // 註冊名為foo的api方法
    context[@"foo"] = ^() {

        //獲取引數
        NSArray *args = [JSContext currentArguments];
        NSString *title = [NSString stringWithFormat:@"%@",[args objectAtIndex:0]];
        //做一些自己的邏輯
        //返回一個值  'foo:'+title
        return [NSString stringWithFormat:@"foo:%@", title];
    };
    
}複製程式碼

之後前端就可以呼叫了:

// 呼叫方法,用top是確保呼叫到最頂級,因為iframe要用top才能拿到頂級
window.top.foo('test'); // 返回:'foo:test'複製程式碼

三、改寫瀏覽器原有物件。

通過修改原來瀏覽器的window某些方法,然後攔截固定規則的引數,然後分發給Java對應的方法去處理。這裡常用的是以下四個方法:

  • alert,可以被webview的onJsAlert監聽
  • confirm,可以被webview的onJsConfirm監聽
  • console.log,可以被webview的onConsoleMessage監聽
  • prompt,可以被webview的onJsPrompt監聽

Native呼叫Web

需要先使用JavaScript 在HTML5頁面全域性中宣告相對應的方法。

然後Native向HTML5發起呼叫,Android平臺一般通過loadUrl,iOS通常通過stringByEvaluatingJavaScriptFromString實現。

Android調HTML5:

// 非同步執行JS程式碼,並獲取返回值    
mWebView.evaluateJavascript("javascript: 方法名('引數,需要轉為字串')", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            // 這裡的value即為對應JS方法的返回值
        }
});複製程式碼

iOS調HTML5:

// 可以取得JS函式執行的返回值
// 方法必須是Html頁面繫結在最頂層的window上物件的
// 如window.top.foo
[webView stringByEvaluatingJavaScriptFromString:@"方法名(引數);"];複製程式碼

JSBridge

image.png | center | 690x167

JSBridge是HTML5與Native通訊的橋樑,其作用是實現HTML5與Native間的雙向通訊。JSBridge綜合了上面的技術,更多的是一種形式、一種思想,各家的實現方式也略有差異。比如微信JSSDK,就是基於WeixinJSBridge,微信瀏覽器中的頁面,通過WeixinJSBridge呼叫微信提供的一些原生功能。


相關文章