摘要:實例生成以后,可以用方法指定狀態和狀態的回調函數。語法該方法是的別名,用于指定發生錯誤時的回調函數。
一 前言
本文主要對ES6的Promise進行一些入門級的介紹。要想學習一個知識點,肯定是從三個方面出發,what、why、how。下面就跟著我一步步學習吧~
二 什么是Promise首先是what。那么什么是Promise呢?
以下是MDN對Promise的定義
The Promise object is used for asynchronous computations. A Promise represents a single asynchronous operation that hasn"t completed yet, but is expected in the future.譯文:Promise對象用于異步操作,它表示一個尚未完成且預計在未來完成的異步操作。
那么什么是異步操作?在學習promise之前需要把這個概念搞明白,下面將抽離一章專門介紹。
2.1 同步與異步我們知道,JavaScript的執行環境是「單線程」。
所謂單線程,是指JS引擎中負責解釋和執行JavaScript代碼的線程只有一個,也就是一次只能完成一項任務,這個任務執行完后才能執行下一個,它會「阻塞」其他任務。這個任務可稱為主線程。
但實際上還有其他線程,如事件觸發線程、ajax請求線程等。
這也就引發了同步和異步的問題。
2.1.1 同步同步模式,即上述所說的單線程模式,一次只能執行一個任務,函數調用后需等到函數執行結束,返回執行的結果,才能進行下一個任務。如果這個任務執行的時間較長,就會導致「線程阻塞」。
/* 例2.1 */ var x = true; while(x); console.log("don"t carry out"); //不會執行
上面的例子即同步模式,其中的while是一個死循環,它會阻塞進程,因此第三句console不會執行。
同步模式比較簡單,也較容易編寫。但問題也顯而易見,如果請求的時間較長,而阻塞了后面代碼的執行,體驗是很不好的。因此對于一些耗時的操作,異步模式則是更好的選擇。
下面就來看看異步模式。
異步模式,即與同步模式相反,可以一起執行多個任務,函數調用后不會立即返回執行的結果,如果任務A需要等待,可先執行任務B,等到任務A結果返回后再繼續回調。
最常見的異步模式就數定時器了,我們來看看以下的例子。
/* 例2.2 */ setTimeout(function() { console.log("taskA, asynchronous"); }, 0); console.log("taskB, synchronize"); //while(true); -------ouput------- taskB, synchronize taskA, asynchronous
我們可以看到,定時器延時的時間明明為0,但taskA還是晚于taskB執行。這是為什么呢?由于定時器是異步的,異步任務會在當前腳本的所有同步任務執行完才會執行。如果同步代碼中含有死循環,即將上例的注釋去掉,那么這個異步任務就不會執行,因為同步任務阻塞了進程。
2.1.3 回調函數提起異步,就不得不談談回調函數了。上例中,setTimeout里的function便是回調函數。可以簡單理解為:(執行完)回(來)調(用)的函數。
以下是WikiPedia對于callback的定義。
In computer programming, a callback is a piece of executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at some convenient time.
可以看出,回調函數是一段可執行的代碼段,它以「參數」的形式傳遞給其他代碼,在其合適的時間執行這段(回調函數)的代碼。
WikiPedia同時提到
The invocation may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback.
也就是說,回調函數不僅可以用于異步調用,一般同步的場景也可以用回調。在同步調用下,回調函數一般是最后執行的。而異步調用下,可能一段時間后執行或不執行(未達到執行的條件)。
/* 例2.3 */ /******************同步回調******************/ var fun1 = function(callback) { //do something console.log("before callback"); (callback && typeof(callback) === "function") && callback(); console.log("after callback"); } var fun2 = function(param) { //do something var start = new Date(); while((new Date() - start) < 3000) { //delay 3s } console.log("I"m callback"); } fun1(fun2); -------output-------- before callback //after 3s I’m callback after callback
由于是同步回調,會阻塞后面的代碼,如果fun2是個死循環,后面的代碼就不執行了。
上一小節中setTimeout就是常見的異步回調,另外常見的異步回調即ajax請求。
/* 例2.4 */ /******************異步回調******************/ function request(url, param, successFun, errorFun) { $.ajax({ type: "GET", url: url, param: param, async: true, //默認為true,即異步請求;false為同步請求 success: successFun, error: errorFun }); } request("test.html", "", function(data) { //請求成功后的回調函數,通常是對請求回來的數據進行處理 console.log("請求成功啦, 這是返回的數據:", data); },function(error) { console.log("sorry, 請求失敗了, 這是失敗信息:", error); });2.2 為什么使用Promise
說完了以上基本概念,我們就可以繼續學習Promise了。
上面提到,Promise對象是用于異步操作的。既然我們可以使用異步回調來進行異步操作,為什么還要引入一個Promise新概念,還要花時間學習它呢?不要著急,下面就來談談Promise的過人之處。
我們先看看下面的demo,利用Promise改寫例2.4的異步回調。
/* 例2.5 */ function sendRequest(url, param) { return new Promise(function (resolve, reject) { request(url, param, resolve, reject); }); } sendRequest("test.html", "").then(function(data) { //異步操作成功后的回調 console.log("請求成功啦, 這是返回的數據:", data); }, function(error) { //異步操作失敗后的回調 console.log("sorry, 請求失敗了, 這是失敗信息:", error); });
這么一看,并沒有什么區別,還比上面的異步回調復雜,得先新建Promise再定義其回調。其實,Promise的真正強大之處在于它的多重鏈式調用,可以避免層層嵌套回調。如果我們在第一次ajax請求后,還要用它返回的結果再次請求呢?
/* 例2.6 */ request("test1.html", "", function(data1) { console.log("第一次請求成功, 這是返回的數據:", data1); request("test2.html", data1, function (data2) { console.log("第二次請求成功, 這是返回的數據:", data2); request("test3.html", data2, function (data3) { console.log("第三次請求成功, 這是返回的數據:", data3); //request... 繼續請求 }, function(error3) { console.log("第三次請求失敗, 這是失敗信息:", error3); }); }, function(error2) { console.log("第二次請求失敗, 這是失敗信息:", error2); }); }, function(error1) { console.log("第一次請求失敗, 這是失敗信息:", error1); });
以上出現了多層回調嵌套,有種暈頭轉向的感覺。這也就是我們常說的厄運回調金字塔(Pyramid of Doom),編程體驗十分不好。而使用Promise,我們就可以利用then進行「鏈式回調」,將異步操作以同步操作的流程表示出來。
/* 例2.7 */ sendRequest("test1.html", "").then(function(data1) { console.log("第一次請求成功, 這是返回的數據:", data1); return sendRequest("test2.html", data1); }).then(function(data2) { console.log("第二次請求成功, 這是返回的數據:", data2); return sendRequest("test3.html", data2); }).then(function(data3) { console.log("第三次請求成功, 這是返回的數據:", data3); }).catch(function(error) { //用catch捕捉前面的錯誤 console.log("sorry, 請求失敗了, 這是失敗信息:", error); });
是不是明顯清晰很多?孰優孰略也無需多說了吧~下面就讓我們真正進入Promise的學習。
三 Promise的基本用法 3.1 基本用法上一小節我們認識了promise長什么樣,但對它用到的resolve、reject、then、catch想必還不理解。下面我們一步步學習。
Promise對象代表一個未完成、但預計將來會完成的操作。
它有以下三種狀態:
pending:初始值,不是fulfilled,也不是rejected
fulfilled:代表操作成功
rejected:代表操作失敗
Promise有兩種狀態改變的方式,既可以從pending轉變為fulfilled,也可以從pending轉變為rejected。一旦狀態改變,就「凝固」了,會一直保持這個狀態,不會再發生變化。當狀態發生變化,promise.then綁定的函數就會被調用。
注意:Promise一旦新建就會「立即執行」,無法取消。這也是它的缺點之一。
下面就通過例子進一步講解。
/* 例3.1 */ //構建Promise var promise = new Promise(function (resolve, reject) { if (/* 異步操作成功 */) { resolve(data); } else { /* 異步操作失敗 */ reject(error); } });
類似構建對象,我們使用new來構建一個Promise。Promise接受一個「函數」作為參數,該函數的兩個參數分別是resolve和reject。這兩個函數就是就是「回調函數」,由JavaScript引擎提供。
resolve函數的作用:在異步操作成功時調用,并將異步操作的結果,作為參數傳遞出去;
reject函數的作用:在異步操作失敗時調用,并將異步操作報出的錯誤,作為參數傳遞出去。
Promise實例生成以后,可以用then方法指定resolved狀態和reject狀態的回調函數。
/* 接例3.1 */ promise.then(onFulfilled, onRejected); promise.then(function(data) { // do something when success }, function(error) { // do something when failure });
then方法會返回一個Promise。它有兩個參數,分別為Promise從pending變為fulfilled和rejected時的回調函數(第二個參數非必選)。這兩個函數都接受Promise對象傳出的值作為參數。
簡單來說,then就是定義resolve和reject函數的,其resolve參數相當于:
function resolveFun(data) { //data為promise傳出的值 }
而新建Promise中的"resolve(data)",則相當于執行resolveFun函數。
Promise新建后就會立即執行。而then方法中指定的回調函數,將在當前腳本所有同步任務執行完才會執行。如下例:
/* 例3.2 */ var promise = new Promise(function(resolve, reject) { console.log("before resolved"); resolve(); console.log("after resolved"); }); promise.then(function() { console.log("resolved"); }); console.log("outer"); -------output------- before resolved after resolved outer resolved
由于resolve指定的是異步操作成功后的回調函數,它需要等所有同步代碼執行后才會執行,因此最后打印"resolved",這個和例2.2是一樣的道理。
3.2 基本API .then()語法:Promise.prototype.then(onFulfilled, onRejected)
對promise添加onFulfilled和onRejected回調,并返回的是一個新的Promise實例(不是原來那個Promise實例),且返回值將作為參數傳入這個新Promise的resolve函數。
因此,我們可以使用鏈式寫法,如上文的例2.7。由于前一個回調函數,返回的還是一個Promise對象(即有異步操作),這時后一個回調函數,就會等待該Promise對象的狀態發生變化,才會被調用。
.catch()語法:Promise.prototype.catch(onRejected)
該方法是.then(undefined, onRejected)的別名,用于指定發生錯誤時的回調函數。
/* 例3.3 */ promise.then(function(data) { console.log("success"); }).catch(function(error) { console.log("error", error); }); /*******等同于*******/ promise.then(function(data) { console.log("success"); }).then(undefined, function(error) { console.log("error", error); });
/* 例3.4 */ var promise = new Promise(function (resolve, reject) { throw new Error("test"); }); /*******等同于*******/ var promise = new Promise(function (resolve, reject) { reject(new Error("test")); }); //用catch捕獲 promise.catch(function (error) { console.log(error); }); -------output------- Error: test
從上例可以看出,reject方法的作用,等同于拋錯。
promise對象的錯誤,會一直向后傳遞,直到被捕獲。即錯誤總會被下一個catch所捕獲。then方法指定的回調函數,若拋出錯誤,也會被下一個catch捕獲。catch中也能拋錯,則需要后面的catch來捕獲。
/* 例3.5 */ sendRequest("test.html").then(function(data1) { //do something }).then(function (data2) { //do something }).catch(function (error) { //處理前面三個Promise產生的錯誤 });
上文提到過,promise狀態一旦改變就會凝固,不會再改變。因此promise一旦fulfilled了,再拋錯,也不會變為rejected,就不會被catch了。
/* 例3.6 */ var promise = new Promise(function(resolve, reject) { resolve(); throw "error"; }); promise.catch(function(e) { console.log(e); //This is never called });
如果沒有使用catch方法指定處理錯誤的回調函數,Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應(Chrome會拋錯),這是Promise的另一個缺點。
/* 例3.7 */ var promise = new Promise(function (resolve, reject) { resolve(x); }); promise.then(function (data) { console.log(data); });
如圖所示,只有Chrome會拋錯,且promise狀態變為rejected,Firefox和Safari中錯誤不會被捕獲,也不會傳遞到外層代碼,最后沒有任何輸出,promise狀態也變為rejected。
.all()語法:Promise.all(iterable)
該方法用于將多個Promise實例,包裝成一個新的Promise實例。
var p = Promise.all([p1, p2, p3]);
Promise.all方法接受一個數組(或具有Iterator接口)作參數,數組中的對象(p1、p2、p3)均為promise實例(如果不是一個promise,該項會被用Promise.resolve轉換為一個promise)。它的狀態由這三個promise實例決定。
當p1, p2, p3狀態都變為fulfilled,p的狀態才會變為fulfilled,并將三個promise返回的結果,按參數的順序(而不是 resolved的順序)存入數組,傳給p的回調函數,如例3.8。
當p1, p2, p3其中之一狀態變為rejected,p的狀態也會變為rejected,并把第一個被reject的promise的返回值,傳給p的回調函數,如例3.9。
/* 例3.8 */ var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 3000, "first"); }); var p2 = new Promise(function (resolve, reject) { resolve("second"); }); var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "third"); }); Promise.all([p1, p2, p3]).then(function(values) { console.log(values); }); -------output------- //約 3s 后 ["first", "second", "third"]
/* 例3.9 */ var p1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, "one"); }); var p2 = new Promise((resolve, reject) => { setTimeout(reject, 2000, "two"); }); var p3 = new Promise((resolve, reject) => { reject("three"); }); Promise.all([p1, p2, p3]).then(function (value) { console.log("resolve", value); }, function (error) { console.log("reject", error); // => reject three }); -------output------- reject three
這多個 promise 是同時開始、并行執行的,而不是順序執行。從下面例子可以看出。如果一個個執行,那至少需要 1+32+64+128
/* 例3.10 */ function timerPromisefy(delay) { return new Promise(function (resolve) { setTimeout(function () { resolve(delay); }, delay); }); } var startDate = Date.now(); Promise.all([ timerPromisefy(1), timerPromisefy(32), timerPromisefy(64), timerPromisefy(128) ]).then(function (values) { console.log(Date.now() - startDate + "ms"); console.log(values); }); -------output------- 133ms //不一定,但大于128ms [1,32,64,128].race()
語法:Promise.race(iterable)
該方法同樣是將多個Promise實例,包裝成一個新的Promise實例。
var p = Promise.race([p1, p2, p3]);
Promise.race方法同樣接受一個數組(或具有Iterator接口)作參數。當p1, p2, p3中有一個實例的狀態發生改變(變為fulfilled或rejected),p的狀態就跟著改變。并把第一個改變狀態的promise的返回值,傳給p的回調函數。
/* 例3.11 */ var p1 = new Promise(function(resolve, reject) { setTimeout(reject, 500, "one"); }); var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "two"); }); Promise.race([p1, p2]).then(function(value) { console.log("resolve", value); }, function(error) { //not called console.log("reject", error); }); -------output------- resolve two var p3 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, "three"); }); var p4 = new Promise(function(resolve, reject) { setTimeout(reject, 100, "four"); }); Promise.race([p3, p4]).then(function(value) { //not called console.log("resolve", value); }, function(error) { console.log("reject", error); }); -------output------- reject four
在第一個promise對象變為resolve后,并不會取消其他promise對象的執行,如下例
/* 例3.12 */ var fastPromise = new Promise(function (resolve) { setTimeout(function () { console.log("fastPromise"); resolve("resolve fastPromise"); }, 100); }); var slowPromise = new Promise(function (resolve) { setTimeout(function () { console.log("slowPromise"); resolve("resolve slowPromise"); }, 1000); }); // 第一個promise變為resolve后程序停止 Promise.race([fastPromise, slowPromise]).then(function (value) { console.log(value); // => resolve fastPromise }); -------output------- fastPromise resolve fastPromise slowPromise //仍會執行.resolve()
語法:
Promise.resolve(value); Promise.resolve(promise); Promise.resolve(thenable);
它可以看做new Promise()的快捷方式。
Promise.resolve("Success"); /*******等同于*******/ new Promise(function (resolve) { resolve("Success"); });
這段代碼會讓這個Promise對象立即進入resolved狀態,并將結果success傳遞給then指定的onFulfilled回調函數。由于Promise.resolve()也是返回Promise對象,因此可以用.then()處理其返回值。
/* 例3.13 */ Promise.resolve("success").then(function (value) { console.log(value); }); -------output------- Success
/* 例3.14 */ //Resolving an array Promise.resolve([1,2,3]).then(function(value) { console.log(value[0]); // => 1 }); //Resolving a Promise var p1 = Promise.resolve("this is p1"); var p2 = Promise.resolve(p1); p2.then(function (value) { console.log(value); // => this is p1 });
Promise.resolve()的另一個作用就是將thenable對象(即帶有then方法的對象)轉換為promise對象。
/* 例3.15 */ var p1 = Promise.resolve({ then: function (resolve, reject) { resolve("this is an thenable object!"); } }); console.log(p1 instanceof Promise); // => true p1.then(function(value) { console.log(value); // => this is an thenable object! }, function(e) { //not called });
再看下面兩個例子,無論是在什么時候拋異常,只要promise狀態變成resolved或rejected,狀態不會再改變,這和新建promise是一樣的。
/* 例3.16 */ //在回調函數前拋異常 var p1 = { then: function(resolve) { throw new Error("error"); resolve("Resolved"); } }; var p2 = Promise.resolve(p1); p2.then(function(value) { //not called }, function(error) { console.log(error); // => Error: error }); //在回調函數后拋異常 var p3 = { then: function(resolve) { resolve("Resolved"); throw new Error("error"); } }; var p4 = Promise.resolve(p3); p4.then(function(value) { console.log(value); // => Resolved }, function(error) { //not called });.reject()
語法:Promise.reject(reason)
這個方法和上述的Promise.resolve()類似,它也是new Promise()的快捷方式。
Promise.reject(new Error("error")); /*******等同于*******/ new Promise(function (resolve, reject) { reject(new Error("error")); });
這段代碼會讓這個Promise對象立即進入rejected狀態,并將錯誤對象傳遞給then指定的onRejected回調函數。
四 Promise常見問題經過上一章的學習,相信大家已經學會使用Promise。
總結一下創建promise的流程:
使用new Promise(fn)或者它的快捷方式Promise.resolve()、Promise.reject(),返回一個promise對象
在fn中指定異步的處理
處理結果正常,調用resolve
處理結果錯誤,調用reject
如果使用ES6的箭頭函數,將會使寫法更加簡單清晰。
這一章節,將會用例子的形式,以說明promise使用過程中的注意點及容易犯的錯誤。
情景1:reject 和 catch 的區別
promise.then(onFulfilled, onRejected)
在onFulfilled中發生異常的話,在onRejected中是捕獲不到這個異常的。
promise.then(onFulfilled).catch(onRejected)
.then中產生的異常能在.catch中捕獲
一般情況,還是建議使用第二種,因為能捕獲之前的所有異常。當然了,第二種的.catch()也可以使用.then()表示,它們本質上是沒有區別的,.catch === .then(null, onRejected)
情景2:如果在then中拋錯,而沒有對錯誤進行處理(即catch),那么會一直保持reject狀態,直到catch了錯誤
/* 例4.1 */ function taskA() { console.log(x); console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejected(error) { console.log("Catch Error: A or B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask); -------output------- Catch Error: A or B,ReferenceError: x is not defined Final Task
根據例4.1的輸出結果及流程圖,可以看出,A拋錯時,會按照 taskA → onRejected → finalTask這個流程來處理。A拋錯后,若沒有對它進行處理,如例3.7,狀態就會維持rejected,taskB不會執行,直到catch了錯誤。
/* 例4.2 */ function taskA() { console.log(x); console.log("Task A"); } function taskB() { console.log("Task B"); } function onRejectedA(error) { console.log("Catch Error: A", error); } function onRejectedB(error) { console.log("Catch Error: B", error); } function finalTask() { console.log("Final Task"); } var promise = Promise.resolve(); promise .then(taskA) .catch(onRejectedA) .then(taskB) .catch(onRejectedB) .then(finalTask); -------output------- Catch Error: A ReferenceError: x is not defined Task B Final Task
將例4.2與4.1對比,在taskA后多了對A的處理,因此,A拋錯時,會按照A會按照 taskA → onRejectedA → taskB → finalTask這個流程來處理,此時taskB是正常執行的。
情景3:每次調用then都會返回一個新創建的promise對象,而then內部只是返回的數據
/* 例4.3 */ //方法1:對同一個promise對象同時調用 then 方法 var p1 = new Promise(function (resolve) { resolve(100); }); p1.then(function (value) { return value * 2; }); p1.then(function (value) { return value * 2; }); p1.then(function (value) { console.log("finally: " + value); }); -------output------- finally: 100 //方法2:對 then 進行 promise chain 方式進行調用 var p2 = new Promise(function (resolve) { resolve(100); }); p2.then(function (value) { return value * 2; }).then(function (value) { return value * 2; }).then(function (value) { console.log("finally: " + value); }); -------output------- finally: 400
第一種方法中,then的調用幾乎是同時開始執行的,且傳給每個then的value都是100,這種方法應當避免。方法二才是正確的鏈式調用。
因此容易出現下面的錯誤寫法:
/* 例4.4 */ function badAsyncCall(data) { var promise = Promise.resolve(data); promise.then(function(value) { //do something return value + 1; }); return promise; } badAsyncCall(10).then(function(value) { console.log(value); //想要得到11,實際輸出10 }); -------output------- 10
正確的寫法應該是:
/* 改寫例4.4 */ function goodAsyncCall(data) { var promise = Promise.resolve(data); return promise.then(function(value) { //do something return value + 1; }); } goodAsyncCall(10).then(function(value) { console.log(value); }); -------output------- 11
情景4:在異步回調中拋錯,不會被catch到
// Errors thrown inside asynchronous functions will act like uncaught errors var promise = new Promise(function(resolve, reject) { setTimeout(function() { throw "Uncaught Exception!"; }, 1000); }); promise.catch(function(e) { console.log(e); //This is never called });
情景5: promise狀態變為resove或reject,就凝固了,不會再改變
console.log(1); new Promise(function (resolve, reject){ reject(); setTimeout(function (){ resolve(); //not called }, 0); }).then(function(){ console.log(2); }, function(){ console.log(3); }); console.log(4); -------output------- 1 4 3五 結語
關于promise就先介紹到這邊了,比較基礎,有不足的地方歡迎指出,有更好的也歡迎補充~
參考資料:
https://developer.mozilla.org...
http://liubin.org/promises-book/
http://es6.ruanyifeng.com/#do...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80545.html
摘要:是單線程程序,所有代碼都是單線程執行。導致的網絡請求都是異步執行,異步執行可以通過回調函數實現秒鐘才能打印,回調函數處理異步執行的但是推出一種新的方法對象用于表示一個異步操作的最終狀態完成或失敗,以及其返回的值。 javascript是單線程程序,所有代碼都是單線程執行。導致javascript的網絡請求都是異步執行,異步執行可以通過回調函數實現: setTimeout(callbac...
摘要:題外今天嘗試了一下從文件經過再到文件的整個過程,這也是這種靜態博客生成過程中的一環。這過程中,用到的中的系統,寫的過程中恰好也經歷了從到再到的轉變。可以看到上面的函數已經非常順序化了,當有個異步函數回調時,只需要順序寫就可以啦。 題外:今天嘗試了一下從Markdown文件經過ejs再到html文件的整個過程,這也是Hexo這種靜態博客生成過程中的一環。這過程中,用到的Node中的fs系...
摘要:首先介紹是一個庫,他提供了一組用來操縱的默認也就是無的,也可以配置為有有點類似于,但是官方團隊進行維護的,前景更好。使用,相當于同時具有和的能力,應用場景會非常多。 首先介紹Puppeteer Puppeteer是一個node庫,他提供了一組用來操縱Chrome的API(默認headless也就是無UI的chrome,也可以配置為有UI) 有點類似于PhantomJS,但Puppet...
摘要:誕生之初,是單線程的。當接收到服務端的響應之后,便通過回調函數執行之后的操作。沖鋒基于事件驅動。擁有攔截請求消息推送靜默更新地理圍欄等服務。控制時處于兩種狀態之一終止以節省內存監聽獲取和消息事件。支持的所有事件五銷毀瀏覽器決定是否銷毀。 這次體驗一種新的博客風格,我們長話短說,針針見血。 showImg(https://segmentfault.com/img/remote/14600...
閱讀 1893·2021-11-22 15:25
閱讀 1250·2021-11-19 09:40
閱讀 1857·2021-09-27 13:57
閱讀 986·2021-09-22 15:10
閱讀 972·2021-08-16 11:01
閱讀 2971·2021-07-23 17:51
閱讀 765·2019-08-30 15:55
閱讀 818·2019-08-30 13:58