javascript

前端的網路請求方式分析

一、前端進行網路請求的關注點大多數情況下,在前端發起一個網路請求我們只需關注下面幾點:二、前端進行網路請求的方...

>

一、前端進行網路請求的關注點

大多數情況下,在前端發起一個網路請求我們只需關注下面幾點:

url
cookie

二、前端進行網路請求的方式

  • form 表單、 ifream 、重新整理頁面
  • Ajax - 非同步網路請求的開山鼻祖
  • jQuery - 一個時代
  • fetch - Ajax 的替代者
  • axios、request 等眾多開源庫

三、關於網路請求的疑問

Ajax
Ajax
jQuery
fetch
fetch

帶著以上這些問題、關注點我們對幾種網路請求進行一次全面的分析。

四、Ajax的出現解決了什麼問題

Ajax 出現之前, web 程式是這樣工作的:

img

這種互動的的缺陷是顯而易見的,任何和伺服器的互動都需要重新整理頁面,使用者體驗非常差, Ajax 的出現解決了這個問題。 Ajax 全稱 Asynchronous JavaScript + XML (非同步 JavaScriptXML

使用 Ajax ,網頁應用能夠快速地將增量更新呈現在用戶界面上,而不需要過載(重新整理)整個頁面。

Ajax 本身不是一種新技術,而是用來描述一種使用現有技術集合實現的一個技術方案,瀏覽器的 XMLHttpRequest 是實現 Ajax 最重要的物件( IE6 以下使用 ActiveXObject )。

儘管 XAjax 中代表 XML , 但由於 JSON 的許多優勢,比如更加輕量以及作為 Javascript 的一部分,目前 JSON 的使用比 XML 更加普遍。

五、原生Ajax的用法

這裏主要分析 XMLHttpRequest 物件,下面是它的一段基礎使用:

var xhr = new XMLHttpRequest();
        xhr.open('post','www.xxx.com',true)
        // 接收返回值
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4 ){
                if(xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                    console.log(xhr.responseText);
                }
            }
        }
        // 處理請求引數
        postData = {"name1":"value1","name2":"value2"};
        postData = (function(value){
        var dataString = "";
        for(var key in value){
             dataString += key+"="+value[key]+"&";
        };
          return dataString;
        }(postData));
        // 設定請求頭
        xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
        // 異常處理
        xhr.onerror = function() {
           console.log('Network request failed')
        }
        // 跨域攜帶cookie
        xhr.withCredentials = true;
        // 發出請求
        xhr.send(postData);

下面分別對 XMLHttpRequest 物件常用的的函式、屬性、事件進行分析。

img

函式

open

用於初始化一個請求,用法:

xhr.open(method, url, async);
  • method :請求方式,如 get、post
  • url :請求的 url
  • async :是否為非同步請求

send

用於傳送 HTTP 請求,即呼叫該方法後 HTTP 請求才會被真正發出,用法:

xhr.send(param)
  • param :http請求的引數,可以為 string、Blob 等型別。

abort

用於終止一個 ajax 請求,呼叫此方法後 readyState 將被設定為 0 ,用法:

xhr.abort()

setRequestHeader

用於設定 HTTP 請求頭,此方法必須在 open() 方法和 send() 之間呼叫,用法:

xhr.setRequestHeader(header, value);

getResponseHeader

用於獲取 http 返回頭,如果在返回頭中有多個一樣的名稱,那麼返回的值就會是用逗號和空格將值分隔的字串,用法:

var header = xhr.getResponseHeader(name);

屬性

readyState

用來標識當前 XMLHttpRequest 物件所處的狀態, XMLHttpRequest 物件總是位於下列狀態中的一個:

狀態 描述
0 UNSENT 代理被建立,但尚未呼叫 open() 方法。
1 OPENED open() 方法已經被呼叫。
2 HEADERS_RECEIVED send() 方法已經被呼叫,並且頭部和狀態已經可獲得。
3 LOADING 下載中; responseText 屬性已經包含部分資料。
4 DONE 下載操作已完成。

status

表示 http 請求的狀態, 初始值為 0 。如果伺服器沒有顯式地指定狀態碼, 那麼 status 將被設定為預設值, 即 200

responseType

表示響應的資料型別,並允許我們手動設定,如果為空,預設為 text 型別,可以有下面的取值:

描述
"" responseType 設為空字串與設定為 "text" 相同, 是預設型別 (實際上是 DOMString )。
"arraybuffer" response 是一個包含二進制資料的 JavaScript ArrayBuffer
"blob" response 是一個包含二進制資料的 Blob 物件 。
"document" response 是一個 HTML DocumentXML XMLDocument ,這取決於接收到的資料的 MIME 型別。
"json" response 是一個 JavaScript 物件。這個物件是通過將接收到的資料型別視為 JSON 解析得到的。
"text" response 是包含在 DOMString 物件中的文字。

response

返回響應的正文,返回的型別由上面的 responseType 決定。

withCredentials

ajax 請求預設會攜帶同源請求的 cookie ,而跨域請求則不會攜帶 cookie ,設定 xhrwithCredentials 的屬性為 true 將允許攜帶跨域 cookie

事件回撥

onreadystatechange

xhr.onreadystatechange = callback;

readyState 屬性發生變化時,callback會被觸發。

onloadstart

xhr.onloadstart = callback;

ajax 請求傳送之前( readyState==1 後, readyState==2 前), callback 會被觸發。

onprogress

xhr.onprogress = function(event){
  console.log(event.loaded / event.total);
}

回撥函式可以獲取資源總大小 total ,已經載入的資源大小 loaded ,用這兩個值可以計算載入進度。

onload

xhr.onload = callback;

當一個資源及其依賴資源已完成載入時,將觸發 callback ,通常我們會在 onload 事件中處理返回值。

異常處理

onerror

xhr.onerror = callback;

ajax 資源載入失敗時會觸發 callback

ontimeout

xhr.ontimeout = callback;

當進度由於預定時間到期而終止時,會觸發 callback ,超時時間可使用 timeout 屬性進行設定。

六、jQuery對Ajax的封裝

在很長一段時間裏,人們使用 jQuery 提供的 ajax 封裝進行網路請求,包括 $.ajax、$.get、$.post 等,這幾個方法放到現在,我依然覺得很實用。

$.ajax({
    dataType: 'json', // 設定返回值型別
    contentType: 'application/json', // 設定引數型別
    headers: {'Content-Type','application/json'},// 設定請求頭
    xhrFields: { withCredentials: true }, // 跨域攜帶cookie
    data: JSON.stringify({a: [{b:1, a:1}]}), // 傳遞引數
    error:function(xhr,status){  // 錯誤處理
       console.log(xhr,status);
    },
    success: function (data,status) {  // 獲取結果
       console.log(data,status);
    }
})

$.ajax 只接收一個引數,這個引數接收一系列配置,其自己封裝了一個 jqXHR 物件,有興趣可以閱讀一下 jQuary-ajax 原始碼

img

常用配置:

url

當前頁地址。傳送請求的地址。

type

型別: String 請求方式 ( "POST""GET" ), 預設為 "GET" 。注意:其它 HTTP 請求方法,如 PUTDELETE 也可以使用,但僅部分瀏覽器支援。

timeout

型別: Number 設定請求超時時間(毫秒)。此設定將覆蓋全域性設定。

success

型別: Function 請求成功後的回撥函式。

jsonp

在一個 jsonp 請求中重寫回調函式的名字。這個值用來替代在 "callback=?" 這種 GETPOST 請求中 URL 引數裡的 "callback" 部分。

error型別: Function 。請求失敗時呼叫此函式。

注意:原始碼裡對錯誤的判定:

isSuccess = status >= 200 && status < 300 || status === 304;

返回值除了這幾個狀態碼都會進 error 回撥。

dataType

"xml": 返回 XML 文件,可用 jQuery 處理。
"html": 返回純文字 HTML 資訊;包含的 script 標籤會在插入 dom 時執行。
"script": 返回純文字 JavaScript 程式碼。不會自動快取結果。除非設定了 "cache" 引數。注意:在遠端請求時(不在同一個域下),所有 POST 請求都將轉為 GET 請求。(因為將使用 DOM 的 script標籤來載入)
"json": 返回 JSON 資料 。
"jsonp": JSONP 格式。使用 JSONP 形式呼叫函式時,如 "myurl?callback=?" jQuery 將自動替換 ? 為正確的函式名,以執行回撥函式。
"text": 返回純文字字串

data

型別: String 使用 JSON.stringify 轉碼

complete

型別: Function 請求完成後回撥函式 (請求成功或失敗之後均呼叫)。

async

型別: Boolean 預設值: true 。預設設定下,所有請求均為非同步請求。如果需要傳送同步請求,請將此選項設定為 false

contentType

型別: String 預設值: "application/x-www-form-urlencoded" 。傳送資訊至伺服器時內容編碼型別。

鍵值對這樣組織在一般的情況下是沒有什麼問題的,這裏說的一般是,不帶巢狀型別 JSON ,也就是 簡單的 JSON ,形如這樣:

{
    a: 1,
    b: 2,
    c: 3
}

但是在一些複雜的情況下就有問題了。 例如在 Ajax 中你要傳一個複雜的 json 對像,也就說是物件嵌陣列,陣列中包括物件,你這樣傳: application/x-www-form-urlencoded 這種形式是沒有辦法將複雜的 JSON 組織成鍵值對形式。

{
  data: {
    a: [{
      x: 2
    }]
  }
}

可以用如下方式傳遞複雜的 json 物件

$.ajax({
    dataType: 'json',
    contentType: 'application/json',
    data: JSON.stringify({a: [{b:1, a:1}]})
})

七、jQuery的替代者

近年來前端 MV* 的發展壯大,人們越來越少的使用 jQuery ,我們不可能單獨爲了使用 jQueryAjax api 來單獨引入他,無可避免的,我們需要尋找新的技術方案。

尤雨溪在他的文件中推薦大家用 axios 進行網路請求。 axios 基於 Promise 對原生的 XHR進行了非常全面的封裝,使用方式也非常的優雅。另外, axios 同樣提供了在 node 環境下的支援,可謂是網路請求的首選方案。

未來必定還會出現更優秀的封裝,他們有非常周全的考慮以及詳細的文件,這裏我們不多做考究,我們把關注的重點放在更底層的API fetch

Fetch API 是一個用用於訪問和操縱HTTP管道的強大的原生 API。

這種功能以前是使用 XMLHttpRequest實現的。Fetch提供了一個更好的替代方法,可以很容易地被其他技術使用,例如 Service Workers。Fetch還提供了單個邏輯位置來定義其他HTTP相關概念,例如CORS和HTTP的擴充套件。

可見 fetch 是作為 XMLHttpRequest 的替代品出現的。

使用 fetch ,你不需要再額外載入一個外部資源。但它還沒有被瀏覽器完全支援,所以你仍然需要一個 polyfill

八、fetch的使用

一個基本的 fetch請求:

const options = {
    method: "POST", // 請求引數
    headers: { "Content-Type": "application/json"}, // 設定請求頭
    body: JSON.stringify({name:'123'}), // 請求引數
    credentials: "same-origin", // cookie設定
    mode: "cors", // 跨域
}
fetch('http://www.xxx.com')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson); // 響應資料
  })
  .catch(function(err){
    console.log(err); // 異常處理
  })

Fetch API 提供了一個全域性的 fetch() 方法,以及幾個輔助物件來發起一個網路請求。

img

  • fetch()

fetch() 方法用於發起獲取資源的請求。它返回一個 promise ,這個 promise 會在請求響應後被 resolve ,並傳回 Response 物件。

  • Headers

可以通過 Headers() 建構函式來建立一個你自己的 headers 物件,相當於 response/request 的頭資訊,可以使你查詢到這些頭資訊,或者針對不同的結果做不同的操作。

var myHeaders = new Headers();
myHeaders.append("Content-Type", "text/plain");
  • Request

通過 Request() 建構函式可以建立一個 Request 物件,這個物件可以作為 fetch 函式的第二個引數。

  • Response

fetch() 處理完 promises 之後返回一個 Response 例項,也可以手動建立一個 Response 例項。

九、fetch polyfill原始碼分析

由於 fetch 是一個非常底層的 API ,所以我們無法進一步的探究它的底層,但是我們可以藉助它的 polyfill 探究它的基本原理,並找出其中的坑點。

程式碼結構

img

由程式碼可見, polyfill 主要對 Fetch API提供的四大物件進行了封裝:

fetch 封裝

img

程式碼非常清晰:

  • 構造一個 Promise 物件並返回
  • 建立一個 Request 物件
  • 建立一個 XMLHttpRequest 物件
  • 取出 Request 物件中的請求 url ,請求方發, open 一個 xhr 請求,並將 Request 物件中儲存的 headers 取出賦給xhr
  • xhr onload 後取出 responsestatusheadersbody 封裝 Response 物件,呼叫 resolve

異常處理

img

可以發現,呼叫 reject 有三種可能:

  • 1.請求超時
  • 2.請求失敗

注意:當和伺服器建立簡介,並收到伺服器的異常狀態碼如 404、500 等並不能觸發 onerror。當網路故障時或請求被阻止時,纔會標記為 reject ,如跨域、 url 不存在,網路異常等會觸發 onerror

所以使用fetch當接收到異常狀態碼都是會進入then而不是catch。這些錯誤請求往往要手動處理。

  • 3.手動終止

可以在 request 引數中傳入 signal 物件,並對 signal 物件新增 abort 事件監聽,當 xhr.readyState 變為 4 (響應內容解析完成)後將signal物件的abort事件監聽移除掉。

這表示,在一個 fetch 請求結束之前可以呼叫 signal.abort 將其終止。在瀏覽器中可以使用 AbortController() 建構函式建立一個控制器,然後使用 AbortController.signal 屬性

這是一個實驗中的功能,此功能某些瀏覽器尚在開發中

Headers封裝

img

在header物件中維護了一個 map 物件,建構函式中可以傳入 Header 物件、陣列、普通物件型別的 header ,並將所有的值維護到 map 中。

之前在 fetch 函式中看到呼叫了 headerforEach 方法,下面是它的實現:

img

可見 header 的遍歷即其內部 map 的遍歷。

另外 Header 還提供了 append、delete、get、set 等方法,都是對其內部的 map 物件進行操作。

Request物件

img

Request 物件接收的兩個引數即 fetch 函式接收的兩個引數,第一個引數可以直接傳遞 url,也可以傳遞一個構造好的 request 物件。第二個引數即控制不同配置的 option 物件。

可以傳入 credentials、headers、method、mode、signal、referrer 等屬性。

這裏注意:

  • 傳入的 headers 被當作 Headers 建構函式的引數來構造header物件。

cookie處理

fetch函式中還有如下的程式碼:

if (request.credentials === 'include') {
      xhr.withCredentials = true
    } else if (request.credentials === 'omit') {
      xhr.withCredentials = false
    }

預設的 credentials 型別為 same-origin ,即可攜帶同源請求的coodkie。

然後我發現這裏polyfill的實現和 MDN-使用Fetch 以及很多資料是不一致的:

mdn: 預設情況下,fetch 不會從服務端傳送或接收任何 cookies

於是我分別實驗了下使用 polyfill 和使用原生 fetch 攜帶cookie的情況,發現在不設定 credentials 的情況下居然都是預設攜帶同源 cookie 的,這和文件的說明說不一致的,查閱了許多資料後都是說 fetch 預設不會攜帶cookie,下面是使用原生 fetch 在瀏覽器進行請求的情況:

img

然後我發現在 MDN-Fetch-Request 已經指出新版瀏覽器 credentials 預設值已更改為 same-origin ,舊版依然是 omit

確實 MDN-使用Fetch 這裏的文件更新的有些不及時,誤人子弟了...

Response物件

Response 物件是 fetch 呼叫成功後的返回值:

回顧下 f etch 中對 Response`的操作:

xhr.onload = function () {
      var options = {
        status: xhr.status,
        statusText: xhr.statusText,
        headers: parseHeaders(xhr.getAllResponseHeaders() || '')
      }
      options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
      var body = 'response' in xhr ? xhr.response : xhr.responseText
      resolve(new Response(body, options))
    }

Response 建構函式:

img

可見在建構函式中主要對 options 中的 status、statusText、headers、url 等分別做了處理並掛載到 Response 物件上。

建構函式裏面並沒有對 responseText 的明確處理,最後交給了 _initBody 函式處理,而 Response 並沒有主動宣告 _initBody 屬性,程式碼最後使用 Response 呼叫了 Body 函式,實際上 _initBody 函式是通過 Body 函式掛載到 Response 身上的,先來看看 _initBody 函式:

img

可見, _initBody 函式根據 xhr.response 的型別( Blob、FormData、String... ),為不同的引數進行賦值,這些引數在 Body 方法中得到不同的應用,下面具體看看 Body 函式還做了哪些其他的操作:

img

Body 函式中還為 Response 物件掛載了四個函式, text、json、blob、formData ,這些函式中的操作就是將_initBody中得到的不同型別的返回值返回。

這也說明了,在 fetch 執行完畢後,不能直接在 response 中獲取到返回值而必須呼叫 text()、json() 等函式才能獲取到返回值。

這裏還有一點需要說明:幾個函式中都有類似下面的邏輯:

var rejected = consumed(this)
    if (rejected) {
      return rejected
    }

consumed函式:

function consumed(body) {
  if (body.bodyUsed) {
    return Promise.reject(new TypeError('Already read'))
  }
  body.bodyUsed = true
}

每次呼叫 text()、json() 等函式後會將 bodyUsed 變數變為 true ,用來標識返回值已經讀取過了,下一次再讀取直接丟擲 TypeError('Already read') 。這也遵循了原生 fetch 的原則:

因為Responses物件被設定爲了 stream 的方式,所以它們只能被讀取一次

十、fetch的坑點

VUE 的文件中對 fetch 有下面的描述:

使用 fetch 還有很多別的注意事項,這也是為什麼大家現階段還是更喜歡 axios 多一些。當然這個事情在未來可能會發生改變。

由於 fetch 是一個非常底層的 API ,它並沒有被進行很多封裝,還有許多問題需要處理:

JavaScript
cookie
jsonp

十一、對fetch的封裝

請求引數處理

支援傳入不同的引數型別:

function stringify(url, data) {
  var dataString = url.indexOf('?') == -1 ? '?' : '&';
  for (var key in data) {
    dataString += key + '=' + data[key] + '&';
  };
  return dataString;
}

if (request.formData) {
  request.body = request.data;
} else if (/^get$/i.test(request.method)) {
  request.url = `${request.url}${stringify(request.url, request.data)}`;
} else if (request.form) {
  request.headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
  request.body = stringify(request.data);
} else {
  request.headers.set('Content-Type', 'application/json;charset=UTF-8');
  request.body = JSON.stringify(request.data);
}

cookie攜帶

fetch 在新版瀏覽器已經開始預設攜帶同源 cookie ,但在老版瀏覽器中不會預設攜帶,我們需要對他進行統一設定:

request.credentials =  'same-origin'; // 同源攜帶
  request.credentials =  'include'; // 可跨域攜帶

異常處理

當接收到一個代表錯誤的 HTTP 狀態碼時,從 fetch()返回的 Promise 不會被標記為 reject, 即使該 HTTP 響應的狀態碼是 404 或 500。相反,它會將 Promise 狀態標記為 resolve (但是會將 resolve 的返回值的 ok 屬性設定為 false ),僅當網路故障時或請求被阻止時,纔會標記為 reject。

因此我們要對 fetch 的異常進行統一處理

.then(response => {
  if (response.ok) {
    return Promise.resolve(response);
  }else{
    const error = new Error(`請求失敗! 狀態碼: ${response.status}, 失敗資訊: ${response.statusText}`);
    error.response = response;
    return Promise.reject(error);
  }
});

返回值處理

對不同的返回值型別呼叫不同的函式接收,這裏必須提前判斷好型別,不能多次呼叫獲取返回值的方法:

.then(response => {
  let contentType = response.headers.get('content-type');
  if (contentType.includes('application/json')) {
    return response.json();
  } else {
    return response.text();
  }
});

jsonp

fetch 本身沒有提供對 jsonp 的支援, jsonp 本身也不屬於一種非常好的解決跨域的方式,推薦使用 cors 或者 nginx 解決跨域,具體請看下面的章節。

fetch封裝好了,可以愉快的使用了。

嗯,axios真好用...

十二、跨域總結

談到網路請求,就不得不提跨域。

瀏覽器的同源策略限制了從同一個源載入的文件或指令碼如何與來自另一個源的資源進行互動。這是一個用於隔離潛在惡意檔案的重要安全機制。通常不允許不同源間的讀操作。

跨域條件:協議,域名,埠,有一個不同就算跨域。

下面是解決跨域的幾種方式:

nginx

使用 nginx 反向代理實現跨域

cors

CORS 是一個 W3C 標準,全稱是"跨域資源共享" (Cross-origin resource sharing) 。它允許瀏覽器向跨源伺服器,發出 XMLHttpRequest 請求。

服務端設定 Access-Control-Allow-Origin 就可以開啟 CORS 。 該屬性表示哪些域名可以訪問資源,如果設定萬用字元則表示所有網站都可以訪問資源。

app.all('*', function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
    next();
});

jsonp

script 標籤的 src 屬性中的連結可以訪問跨域的 js 指令碼,利用這個特性,服務端不再返回 JSON 格式的資料,而是返回一段呼叫某個函式的 js 程式碼,在 src 中進行了呼叫,這樣實現了跨域。

jqueryjsonp 的支援:

$.ajax({
            type : "get",
            url : "http://xxxx"
            dataType: "jsonp",
            jsonp:"callback", 
            jsonpCallback: "doo",
            success : function(data) {
                console.log(data);
            }
        });

fetch、axios 等並沒有直接提供對 jsonp 的支援,如果需要使用這種方式,我們可以嘗試進行手動封裝:

(function (window,document) {
    "use strict";
    var jsonp = function (url,data,callback) {

        // 1.將傳入的data資料轉化為url字串形式
        // {id:1,name:'jack'} => id=1&name=jack
        var dataString = url.indexof('?') == -1? '?': '&';
        for(var key in data){
            dataString += key + '=' + data[key] + '&';
        };

        // 2 處理url中的回撥函式
        // cbFuncName回撥函式的名字 :my_json_cb_名字的字首 + 隨機數(把小數點去掉)
        var cbFuncName = 'my_json_cb_' + Math.random().toString().replace('.','');
        dataString += 'callback=' + cbFuncName;

        // 3.建立一個script標籤並插入到頁面中
        var scriptEle = document.createElement('script');
        scriptEle.src = url + dataString;

        // 4.掛載回撥函式
        window[cbFuncName] = function (data) {
            callback(data);
            // 處理完回撥函式的資料之後,刪除jsonp的script標籤
            document.body.removeChild(scriptEle);
        }

        document.body.appendChild(scriptEle);
    }

    window.$jsonp = jsonp;

})(window,document)

postMessage跨域

postMessage() 方法允許來自不同源的指令碼採用非同步方式進行有限的通訊,可以實現跨文字檔、多視窗、跨域訊息傳遞。

//捕獲iframe
var domain = 'http://scriptandstyle.com';
var iframe = document.getElementById('myIFrame').contentWindow;

//傳送訊息
setInterval(function(){
    var message = 'Hello!  The time is: ' + (new Date().getTime());
    console.log('blog.local:  sending message:  ' + message);
        //send the message and target URI
    iframe.postMessage(message,domain); 
},6000);
//響應事件
window.addEventListener('message',function(event) {
    if(event.origin !== 'http://davidwalsh.name') return;
    console.log('message received:  ' + event.data,event);
    event.source.postMessage('holla back youngin!',event.origin);
},false);

postMessage 跨域適用於以下場景:同瀏覽器多視窗間跨域通訊、 iframe 間跨域通訊。

WebSocket

WebSocket 是一種雙向通訊協議,在建立連線之後, WebSocketserverclient 都能主動向對方傳送或接收資料而不受同源策略的限制。

function WebSocketTest(){
            if ("WebSocket" in window){
               alert("您的瀏覽器支援 WebSocket!");
               // 開啟一個 web socket
               var ws = new WebSocket("ws://localhost:3000/abcd");
               ws.onopen = function(){
                  // Web Socket 已連線上,使用 send() 方法傳送資料
                  ws.send("傳送資料");
                  alert("資料傳送中...");
               };
               ws.onmessage = function (evt) { 
                  var received_msg = evt.data;
                  alert("資料已接收...");
               };
               ws.onclose = function(){ 
                  // 關閉 websocket
                  alert("連線已關閉..."); 
               };
            } else{
               // 瀏覽器不支援 WebSocket
               alert("您的瀏覽器不支援 WebSocket!");
            }
         }
Facebook Profile photo
Written by Nat
This is the author box. A short description about the author of this article. Could be their website link, what they like to read about and so on. Profile