From d53bae971e455859229bcb3246e29bcbc85179d2 Mon Sep 17 00:00:00 2001 From: MDN Date: Tue, 8 Mar 2022 00:12:08 +0000 Subject: [CRON] sync translated content --- files/zh-cn/_redirects.txt | 15 +- files/zh-cn/_wikihistory.json | 128 ++--- .../learn/javascript/asynchronous/index.html | 524 +++++++++++++++++ .../javascript/asynchronous/introducing/index.html | 163 ++++++ .../javascript/asynchronous/promises/index.html | 380 +++++++++++++ .../index.html | 618 +++++++++++++++++++++ .../api/document/visibilitychange_event/index.html | 54 ++ .../javascript/asynchronous/async_await/index.html | 380 ------------- .../choosing_the_right_approach/index.html | 524 ----------------- .../javascript/asynchronous/concepts/index.html | 163 ------ .../asynchronous/timeouts_and_intervals/index.html | 618 --------------------- .../web/api/document/onvisibilitychange/index.html | 53 -- .../onresourcetimingbufferfull/index.html | 64 --- .../resourcetimingbufferfull_event/index.html | 65 +++ .../web/api/window/appinstalled_event/index.html | 49 ++ .../zh-cn/web/api/window/onappinstalled/index.html | 48 -- 16 files changed, 1928 insertions(+), 1918 deletions(-) create mode 100644 files/zh-cn/conflicting/learn/javascript/asynchronous/index.html create mode 100644 files/zh-cn/conflicting/learn/javascript/asynchronous/introducing/index.html create mode 100644 files/zh-cn/conflicting/learn/javascript/asynchronous/promises/index.html create mode 100644 files/zh-cn/conflicting/learn/javascript/asynchronous_ae5a561b0ec11fc53c167201aa8af5df/index.html create mode 100644 files/zh-cn/conflicting/web/api/document/visibilitychange_event/index.html delete mode 100644 files/zh-cn/learn/javascript/asynchronous/async_await/index.html delete mode 100644 files/zh-cn/learn/javascript/asynchronous/choosing_the_right_approach/index.html delete mode 100644 files/zh-cn/learn/javascript/asynchronous/concepts/index.html delete mode 100644 files/zh-cn/learn/javascript/asynchronous/timeouts_and_intervals/index.html delete mode 100644 files/zh-cn/web/api/document/onvisibilitychange/index.html delete mode 100644 files/zh-cn/web/api/performance/onresourcetimingbufferfull/index.html create mode 100644 files/zh-cn/web/api/performance/resourcetimingbufferfull_event/index.html create mode 100644 files/zh-cn/web/api/window/appinstalled_event/index.html delete mode 100644 files/zh-cn/web/api/window/onappinstalled/index.html (limited to 'files/zh-cn') diff --git a/files/zh-cn/_redirects.txt b/files/zh-cn/_redirects.txt index 6789bcbf8c..1b57d8f385 100644 --- a/files/zh-cn/_redirects.txt +++ b/files/zh-cn/_redirects.txt @@ -1094,6 +1094,10 @@ /zh-CN/docs/Learn/HTML/Forms/Your_first_HTML_form /zh-CN/docs/Learn/Forms/Your_first_form /zh-CN/docs/Learn/HTML/Multimedia_and_embedding/其他嵌入技术 /zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Other_embedding_technologies /zh-CN/docs/Learn/HTML/Multimedia_and_embedding/在网页中添加矢量图形 /zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Adding_vector_graphics_to_the_Web +/zh-CN/docs/Learn/JavaScript/Asynchronous/Async_await /zh-CN/docs/conflicting/Learn/JavaScript/Asynchronous/Promises +/zh-CN/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach /zh-CN/docs/conflicting/Learn/JavaScript/Asynchronous +/zh-CN/docs/Learn/JavaScript/Asynchronous/Concepts /zh-CN/docs/conflicting/Learn/JavaScript/Asynchronous/Introducing +/zh-CN/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals /zh-CN/docs/conflicting/Learn/JavaScript/Asynchronous_ae5a561b0ec11fc53c167201aa8af5df /zh-CN/docs/Learn/JavaScript/First_steps/变量 /zh-CN/docs/Learn/JavaScript/First_steps/Variables /zh-CN/docs/Learn/JavaScript/First_steps/第一点 /zh-CN/docs/Learn/JavaScript/First_steps/A_first_splash /zh-CN/docs/Learn/JavaScript/Objects/Inheritance /zh-CN/docs/Learn/JavaScript/Objects/Classes_in_JavaScript @@ -1273,6 +1277,7 @@ /zh-CN/docs/Web/API/Document/onfullscreenchange /zh-CN/docs/conflicting/Web/API/Document/fullscreenchange_event /zh-CN/docs/Web/API/Document/onfullscreenerror /zh-CN/docs/Web/API/Document/fullscreenerror_event /zh-CN/docs/Web/API/Document/onreadystatechange /en-US/docs/Web/API/Document/readystatechange_event +/zh-CN/docs/Web/API/Document/onvisibilitychange /zh-CN/docs/conflicting/Web/API/Document/visibilitychange_event /zh-CN/docs/Web/API/Document/rouchmove_event /zh-CN/docs/Web/API/Document/touchmove_event /zh-CN/docs/Web/API/DocumentOrShadowRoot/activeElement /zh-CN/docs/Web/API/Document/activeElement /zh-CN/docs/Web/API/DocumentOrShadowRoot/elementFromPoint /zh-CN/docs/Web/API/Document/elementFromPoint @@ -1466,6 +1471,7 @@ /zh-CN/docs/Web/API/ParentNode.childElementCount /zh-CN/docs/Web/API/Element/childElementCount /zh-CN/docs/Web/API/ParentNode/childElementCount /zh-CN/docs/Web/API/Element/childElementCount /zh-CN/docs/Web/API/Performance.now() /zh-CN/docs/Web/API/Performance/now +/zh-CN/docs/Web/API/Performance/onresourcetimingbufferfull /zh-CN/docs/Web/API/Performance/resourcetimingbufferfull_event /zh-CN/docs/Web/API/Performance/内存 /zh-CN/docs/Web/API/Performance/memory /zh-CN/docs/Web/API/Position /zh-CN/docs/Web/API/GeolocationPosition /zh-CN/docs/Web/API/Position/coords /zh-CN/docs/Web/API/GeolocationPosition/coords @@ -1599,6 +1605,7 @@ /zh-CN/docs/Web/API/Window/crypto /zh-CN/docs/Web/API/crypto_property /zh-CN/docs/Web/API/Window/minimize /zh-CN/docs/conflicting/Web/API/Window /zh-CN/docs/Web/API/Window/mozAnimationStartTIme /zh-CN/docs/Web/API/Animation/startTime +/zh-CN/docs/Web/API/Window/onappinstalled /zh-CN/docs/Web/API/Window/appinstalled_event /zh-CN/docs/Web/API/Window/onbeforeunload /zh-CN/docs/Web/API/WindowEventHandlers/onbeforeunload /zh-CN/docs/Web/API/Window/ongamepadconnected /zh-CN/docs/conflicting/Web/API/Window/gamepadconnected_event /zh-CN/docs/Web/API/Window/ongamepaddisconnected /zh-CN/docs/Web/API/Window/gamepaddisconnected_event @@ -2284,12 +2291,12 @@ /zh-CN/docs/learn/JavaScript/Building_blocks/事件 /zh-CN/docs/Learn/JavaScript/Building_blocks/Events /zh-CN/docs/learn/JavaScript/Building_blocks/相片走廊 /zh-CN/docs/Learn/JavaScript/Building_blocks/Image_gallery /zh-CN/docs/learn/JavaScript/异步 /zh-CN/docs/Learn/JavaScript/Asynchronous -/zh-CN/docs/learn/JavaScript/异步/Async_await /zh-CN/docs/Learn/JavaScript/Asynchronous/Async_await -/zh-CN/docs/learn/JavaScript/异步/Choosing_the_right_approach /zh-CN/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach +/zh-CN/docs/learn/JavaScript/异步/Async_await /zh-CN/docs/conflicting/Learn/JavaScript/Asynchronous/Promises +/zh-CN/docs/learn/JavaScript/异步/Choosing_the_right_approach /zh-CN/docs/conflicting/Learn/JavaScript/Asynchronous /zh-CN/docs/learn/JavaScript/异步/Promises语法 /zh-CN/docs/Learn/JavaScript/Asynchronous/Promises -/zh-CN/docs/learn/JavaScript/异步/概念 /zh-CN/docs/Learn/JavaScript/Asynchronous/Concepts +/zh-CN/docs/learn/JavaScript/异步/概念 /zh-CN/docs/conflicting/Learn/JavaScript/Asynchronous/Introducing /zh-CN/docs/learn/JavaScript/异步/简介 /zh-CN/docs/Learn/JavaScript/Asynchronous/Introducing -/zh-CN/docs/learn/JavaScript/异步/超时和间隔 /zh-CN/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals +/zh-CN/docs/learn/JavaScript/异步/超时和间隔 /zh-CN/docs/conflicting/Learn/JavaScript/Asynchronous_ae5a561b0ec11fc53c167201aa8af5df /zh-CN/docs/learn/Performance/CSS_performance /zh-CN/docs/Learn/Performance/CSS /zh-CN/docs/learn/Performance/dns-prefetch /zh-CN/docs/Web/Performance/dns-prefetch /zh-CN/docs/learn/Performance/感知性能 /zh-CN/docs/Learn/Performance/perceived_performance diff --git a/files/zh-cn/_wikihistory.json b/files/zh-cn/_wikihistory.json index 04de070975..79ea90a611 100644 --- a/files/zh-cn/_wikihistory.json +++ b/files/zh-cn/_wikihistory.json @@ -4173,40 +4173,6 @@ "oceanMIH" ] }, - "Learn/JavaScript/Asynchronous/Async_await": { - "modified": "2020-12-08T06:58:32.883Z", - "contributors": [ - "byrde", - "woniuxingdong", - "qwei", - "plainnany", - "jakio6", - "awarmy", - "cochn", - "wangfangping" - ] - }, - "Learn/JavaScript/Asynchronous/Choosing_the_right_approach": { - "modified": "2020-12-08T07:27:41.218Z", - "contributors": [ - "byrde", - "icetea_cover", - "rubyKC", - "shangruitong", - "PYGC", - "wangfangping", - "tjls" - ] - }, - "Learn/JavaScript/Asynchronous/Concepts": { - "modified": "2020-07-16T22:33:29.726Z", - "contributors": [ - "alice201601", - "grape", - "HermitSun", - "oceanMIH" - ] - }, "Learn/JavaScript/Asynchronous/Introducing": { "modified": "2020-12-09T00:17:16.227Z", "contributors": [ @@ -4236,22 +4202,6 @@ "zijieee" ] }, - "Learn/JavaScript/Asynchronous/Timeouts_and_intervals": { - "modified": "2020-08-14T06:09:20.310Z", - "contributors": [ - "Pada", - "You2er", - "WinterCicada", - "zhangbig0", - "mizhon", - "yuqing521", - "Alendia", - "grape", - "wangfangping", - "puddlejumper26", - "oceanMIH" - ] - }, "Learn/JavaScript/Building_blocks/Events": { "modified": "2020-08-04T06:06:58.173Z", "contributors": [ @@ -11302,12 +11252,6 @@ "githubyangwei" ] }, - "Web/API/Document/onvisibilitychange": { - "modified": "2020-10-15T22:25:06.098Z", - "contributors": [ - "wangzhongchunongithub" - ] - }, "Web/API/Document/open": { "modified": "2020-10-15T21:52:24.168Z", "contributors": [ @@ -18297,7 +18241,7 @@ "ziyunfei" ] }, - "Web/API/Performance/onresourcetimingbufferfull": { + "Web/API/Performance/resourcetimingbufferfull_event": { "modified": "2019-03-23T22:09:51.120Z", "contributors": [ "Pada" @@ -22603,6 +22547,13 @@ "ziyunfei" ] }, + "Web/API/Window/appinstalled_event": { + "modified": "2019-03-18T21:40:44.436Z", + "contributors": [ + "Aaron_Zhung", + "echoArray" + ] + }, "Web/API/Window/applicationCache": { "modified": "2019-03-23T23:31:44.274Z", "contributors": [ @@ -23104,13 +23055,6 @@ "charlie" ] }, - "Web/API/Window/onappinstalled": { - "modified": "2019-03-18T21:40:44.436Z", - "contributors": [ - "Aaron_Zhung", - "echoArray" - ] - }, "Web/API/Window/onbeforeinstallprompt": { "modified": "2019-03-23T22:11:25.289Z", "contributors": [ @@ -46637,6 +46581,56 @@ "shenhao" ] }, + "conflicting/Learn/JavaScript/Asynchronous": { + "modified": "2020-12-08T07:27:41.218Z", + "contributors": [ + "byrde", + "icetea_cover", + "rubyKC", + "shangruitong", + "PYGC", + "wangfangping", + "tjls" + ] + }, + "conflicting/Learn/JavaScript/Asynchronous/Introducing": { + "modified": "2020-07-16T22:33:29.726Z", + "contributors": [ + "alice201601", + "grape", + "HermitSun", + "oceanMIH" + ] + }, + "conflicting/Learn/JavaScript/Asynchronous/Promises": { + "modified": "2020-12-08T06:58:32.883Z", + "contributors": [ + "byrde", + "woniuxingdong", + "qwei", + "plainnany", + "jakio6", + "awarmy", + "cochn", + "wangfangping" + ] + }, + "conflicting/Learn/JavaScript/Asynchronous_ae5a561b0ec11fc53c167201aa8af5df": { + "modified": "2020-08-14T06:09:20.310Z", + "contributors": [ + "Pada", + "You2er", + "WinterCicada", + "zhangbig0", + "mizhon", + "yuqing521", + "Alendia", + "grape", + "wangfangping", + "puddlejumper26", + "oceanMIH" + ] + }, "conflicting/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents": { "modified": "2019-03-23T23:14:19.406Z", "contributors": [ @@ -46836,6 +46830,12 @@ "sunnylost" ] }, + "conflicting/Web/API/Document/visibilitychange_event": { + "modified": "2020-10-15T22:25:06.098Z", + "contributors": [ + "wangzhongchunongithub" + ] + }, "conflicting/Web/API/DocumentOrShadowRoot/elementFromPoint": { "modified": "2019-03-23T23:19:33.886Z", "contributors": [ diff --git a/files/zh-cn/conflicting/learn/javascript/asynchronous/index.html b/files/zh-cn/conflicting/learn/javascript/asynchronous/index.html new file mode 100644 index 0000000000..278746ae22 --- /dev/null +++ b/files/zh-cn/conflicting/learn/javascript/asynchronous/index.html @@ -0,0 +1,524 @@ +--- +title: 选择正确的方法 +slug: conflicting/Learn/JavaScript/Asynchronous +translation_of: Learn/JavaScript/Asynchronous/Choosing_the_right_approach +original_slug: Learn/JavaScript/Asynchronous/Choosing_the_right_approach +--- +
{{LearnSidebar}}
+ +
{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}
+ +

为了完成这个模块,我们将简要讨论之前章节谈论过编码技术和功能,看看你应该使用哪一个,并提供适当的建议和提醒。随着时间的推移,我们可能会添加到此资源中。

+ + + + + + + + + + + + +
预备条件:基本的计算机素养,对JavaScript基础知识的合理理解。
目标:能够在使用不同的异步编程技术时做出合理的选择。
+ +

异步回调

+ +

通常在旧式API中找到,涉及将函数作为参数传递给另一个函数,然后在异步操作完成时调用该函数,以便回调可以依次对结果执行某些操作。这是promise的前身;它不那么高效或灵活。仅在必要时使用。

+ + + + + + + + + + + + + + + + + + + +
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoYes (recursive callbacks)Yes (nested callbacks)No
+ +

代码示例

+ +

通过XMLHttpRequest API加载资源的示例(run it live,并查看see the source):

+ +
function loadAsset(url, type, callback) {
+  let xhr = new XMLHttpRequest();
+  xhr.open('GET', url);
+  xhr.responseType = type;
+
+  xhr.onload = function() {
+    callback(xhr.response);
+  };
+
+  xhr.send();
+}
+
+function displayImage(blob) {
+  let objectURL = URL.createObjectURL(blob);
+
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+}
+
+loadAsset('coffee.jpg', 'blob', displayImage);
+ +

缺陷

+ + + +

浏览器兼容性

+ +

非常好的一般支持,尽管API中回调的确切支持取决于特定的API。有关更具体的支持信息,请参阅您正在使用的API的参考文档。

+ +

更多信息

+ + + +

setTimeout()

+ +

setTimeout() 是一种允许您在经过任意时间后运行函数的方法

+ + + + + + + + + + + + + + + + + +
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
YesYes (recursive timeouts)Yes (nested timeouts)No
+ +

代码示例

+ +

这里浏览器将在执行匿名函数之前等待两秒钟,然后将显示警报消息(see it running livesee the source code):

+ +
let myGreeting = setTimeout(function() {
+  alert('Hello, Mr. Universe!');
+}, 2000)
+ +

缺陷

+ +

您可以使用递归的setTimeout()调用以类似于setInterval()的方式重复运行函数,使用如下代码:

+ +
let i = 1;
+setTimeout(function run() {
+  console.log(i);
+  i++;
+
+  setTimeout(run, 100);
+}, 100);
+ +

递归setTimeout()setInterval()之间存在差异:

+ + + +

当你的代码有可能比你分配的时间间隔更长时间运行时,最好使用递归的setTimeout() ––这将使执行之间的时间间隔保持不变,无论代码执行多长时间,你不会得到错误。

+ +

浏览器兼容性

+ +

{{Compat("api.WindowOrWorkerGlobalScope.setTimeout")}}

+ +

更多信息

+ + + +

setInterval()

+ +

setInterval()函数允许重复执行一个函数,并设置时间间隔。不如requestAnimationFrame()有效率,但允许您选择运行速率/帧速率。

+ + + + + + + + + + + + + + + + + +
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoYesNo (unless it is the same one)No
+ +

代码示例

+ +

以下函数创建一个新的Date()对象,使用toLocaleTimeString()从中提取时间字符串,然后在UI中显示它。然后我们使用setInterval()每秒运行一次,创建每秒更新一次的数字时钟的效果(see this livesee the source):

+ +
function displayTime() {
+   let date = new Date();
+   let time = date.toLocaleTimeString();
+   document.getElementById('demo').textContent = time;
+}
+
+const createClock = setInterval(displayTime, 1000);
+ +

缺陷

+ + + +

浏览器兼容性

+ +

{{Compat("api.WindowOrWorkerGlobalScope.setInterval")}}

+ +

更多信息

+ + + +

requestAnimationFrame()

+ +

requestAnimationFrame()是一种允许您以给定当前浏览器/系统的最佳帧速率重复且高效地运行函数的方法。除非您需要特定的速率帧,否则您应该尽可能使用它而不要去使用setInterval()/recursive setTimeout()

+ + + + + + + + + + + + + + + + + +
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoYesNo (unless it is the same one)No
+ +

代码示例

+ +

一个简单的动画旋转器;你可以查看example live on GitHub(参见 source code ):

+ +
const spinner = document.querySelector('div');
+let rotateCount = 0;
+let startTime = null;
+let rAF;
+
+function draw(timestamp) {
+  if(!startTime) {
+    startTime = timestamp;
+  }
+
+  let rotateCount = (timestamp - startTime) / 3;
+
+  spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
+
+  if(rotateCount > 359) {
+    rotateCount = 0;
+  }
+
+  rAF = requestAnimationFrame(draw);
+}
+
+draw();
+ +

缺陷

+ + + +

浏览器兼容性

+ +

{{Compat("api.Window.requestAnimationFrame")}}

+ +

更多信息

+ + + +

Promises

+ +

Promises 是一种JavaScript功能,允许您运行异步操作并等到它完全完成后再根据其结果运行另一个操作。 Promise是现代异步JavaScript的支柱。

+ + + + + + + + + + + + + + + + + +
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoNoYesSee Promise.all(), below
+ +

代码示例

+ +

以下代码从服务器获取图像并将其显示在 {{htmlelement("img")}} 元素中;(see it live alsothe source code):

+ +
fetch('coffee.jpg')
+.then(response => response.blob())
+.then(myBlob => {
+  let objectURL = URL.createObjectURL(myBlob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+})
+.catch(e => {
+  console.log('There has been a problem with your fetch operation: ' + e.message);
+});
+ +

缺陷

+ +

Promise链可能很复杂,难以解析。如果你嵌套了许多promises,你最终可能会遇到类似的麻烦来回调地狱。例如:

+ +
remotedb.allDocs({
+  include_docs: true,
+  attachments: true
+}).then(function (result) {
+  var docs = result.rows;
+  docs.forEach(function(element) {
+    localdb.put(element.doc).then(function(response) {
+      alert("Pulled doc with id " + element.doc._id + " and added to local db.");
+    }).catch(function (err) {
+      if (err.name == 'conflict') {
+        localdb.get(element.doc._id).then(function (resp) {
+          localdb.remove(resp._id, resp._rev).then(function (resp) {
+// et cetera...
+ +

最好使用promises的链功能,这样使用更平顺,更易于解析的结构:

+ +
remotedb.allDocs(...).then(function (resultOfAllDocs) {
+  return localdb.put(...);
+}).then(function (resultOfPut) {
+  return localdb.get(...);
+}).then(function (resultOfGet) {
+  return localdb.put(...);
+}).catch(function (err) {
+  console.log(err);
+});
+ +

乃至:

+ +
remotedb.allDocs(...)
+.then(resultOfAllDocs => {
+  return localdb.put(...);
+})
+.then(resultOfPut => {
+  return localdb.get(...);
+})
+.then(resultOfGet => {
+  return localdb.put(...);
+})
+.catch(err => console.log(err));
+ +

这涵盖了很多基础知识。对于更完整的论述,请参阅诺兰劳森的We have a problem with promises

+ +

浏览器兼容性

+ +

{{Compat("javascript.builtins.Promise")}}

+ +

更多信息

+ + + +

Promise.all()

+ +

一种JavaScript功能,允许您等待多个promises完成,然后根据所有其他promises的结果运行进一步的操作。

+ + + + + + + + + + + + + + + + + +
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoNoNoYes
+ +

代码示例

+ +

以下示例从服务器获取多个资源,并使用Promise.all()等待所有资源可用,然后显示所有这些资源––  see it live,并查看source code

+ +
function fetchAndDecode(url, type) {
+  // Returning the top level promise, so the result of the entire chain is returned out of the function
+  return fetch(url).then(response => {
+    // Depending on what type of file is being fetched, use the relevant function to decode its contents
+    if(type === 'blob') {
+      return response.blob();
+    } else if(type === 'text') {
+      return response.text();
+    }
+  })
+  .catch(e => {
+    console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
+  });
+}
+
+// Call the fetchAndDecode() method to fetch the images and the text, and store their promises in variables
+let coffee = fetchAndDecode('coffee.jpg', 'blob');
+let tea = fetchAndDecode('tea.jpg', 'blob');
+let description = fetchAndDecode('description.txt', 'text');
+
+// Use Promise.all() to run code only when all three function calls have resolved
+Promise.all([coffee, tea, description]).then(values => {
+  console.log(values);
+  // Store each value returned from the promises in separate variables; create object URLs from the blobs
+  let objectURL1 = URL.createObjectURL(values[0]);
+  let objectURL2 = URL.createObjectURL(values[1]);
+  let descText = values[2];
+
+  // Display the images in <img> elements
+  let image1 = document.createElement('img');
+  let image2 = document.createElement('img');
+  image1.src = objectURL1;
+  image2.src = objectURL2;
+  document.body.appendChild(image1);
+  document.body.appendChild(image2);
+
+  // Display the text in a paragraph
+  let para = document.createElement('p');
+  para.textContent = descText;
+  document.body.appendChild(para);
+});
+ +

缺陷

+ + + +

浏览器兼容性

+ +

{{Compat("javascript.builtins.Promise.all")}}

+ +

更多信息

+ + + +

Async/await

+ +

构造在promises之上的语法糖,允许您使用更像编写同步回调代码的语法来运行异步操作。

+ + + + + + + + + + + + + + + + + +
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoNoYesYes (in combination with Promise.all())
+ +

代码示例

+ +

以下示例是我们之前看到的简单承诺示例的重构,该示例获取并显示图像,使用async / await编写(see it live,并查看source code):

+ +
async function myFetch() {
+  let response = await fetch('coffee.jpg');
+  let myBlob = await response.blob();
+
+  let objectURL = URL.createObjectURL(myBlob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+}
+
+myFetch();
+ +

缺陷

+ + + +

浏览器兼容性

+ +

{{Compat("javascript.statements.async_function")}}

+ +

更多信息

+ + + +

{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}

+ +

本章内容

+ + diff --git a/files/zh-cn/conflicting/learn/javascript/asynchronous/introducing/index.html b/files/zh-cn/conflicting/learn/javascript/asynchronous/introducing/index.html new file mode 100644 index 0000000000..291cf15837 --- /dev/null +++ b/files/zh-cn/conflicting/learn/javascript/asynchronous/introducing/index.html @@ -0,0 +1,163 @@ +--- +title: 通用异步编程概念 +slug: conflicting/Learn/JavaScript/Asynchronous/Introducing +tags: + - JavaScript + - Promises + - Threads + - 学习 + - 异步 + - 阻塞 +translation_of: Learn/JavaScript/Asynchronous/Concepts +original_slug: Learn/JavaScript/Asynchronous/Concepts +--- +
{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}
+ +

在本文中,我们将介绍与异步编程相关的一些重要概念,以及它们在浏览器和JavaScript里的体现。在学习本系列的其他文章之前,你应该先理解这些概念。

+ + + + + + + + + + + + +
预备条件:拥有基本的计算机知识,对JavaScript原理有一定了解。
目标:理解异步编程的基本概念,以及异步编程在浏览器和JavaScript里面的表现。
+ +

异步?

+ +

通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕.

+ +

Mac 用户有时会经历过这种旋转的彩虹光标(常称为沙滩球),操作系统通过这个光标告诉用户:“现在运行的程序正在等待其他的某一件事情完成,才能继续运行,都这么长的时间了,你一定在担心到底发生了什么事情”。

+ +

multi-colored macos beachball busy spinner

+ +

这是令人沮丧的体验,没有充分利用计算机的计算能力 — 尤其是在计算机普遍都有多核CPU的时代,坐在那里等待毫无意义,你完全可以在另一个处理器内核上干其他的工作,同时计算机完成耗时任务的时候通知你。这样你可以同时完成其他工作,这就是异步编程的出发点。你正在使用的编程环境(就web开发而言,编程环境就是web浏览器)负责为你提供异步运行此类任务的API。

+ +

产生阻塞的代码

+ +

异步技术非常有用,特别是在web编程。当浏览器里面的一个web应用进行密集运算还没有把控制权返回给浏览器的时候,整个浏览器就像冻僵了一样,这叫做阻塞;这时候浏览器无法继续处理用户的输入并执行其他任务,直到web应用交回处理器的控制。

+ +

我们来看一些阻塞的例子。

+ +

例子: simple-sync.html  (see it running live), 在按钮上添加了一个事件监听器,当按钮被点击,它就开始运行一个非常耗时的任务(计算1千万个日期,并在console里显示最后一个日期),然后在DOM里面添加一个段落:

+ +
const btn = document.querySelector('button');
+btn.addEventListener('click', () => {
+  let myDate;
+  for(let i = 0; i < 10000000; i++) {
+    let date = new Date();
+    myDate = date
+  }
+
+  console.log(myDate);
+
+  let pElem = document.createElement('p');
+  pElem.textContent = 'This is a newly-added paragraph.';
+  document.body.appendChild(pElem);
+});
+ +

运行这个例子的时候,打开JavaScript console,然后点击按钮 — 你会注意到,直到日期的运算结束,最后一个日期在console上显示出来,段落才会出现在网页上。代码按照源代码的顺序执行,只有前面的代码结束运行,后面的代码才会执行。

+ +
+

Note: 这个例子不现实:在实际情况中一般不会发生,没有谁会计算1千万次日期,它仅仅提供一个非常直观的体验.

+
+ +

第二个例子, simple-sync-ui-blocking.html (see it live), 我们模拟一个在现实的网页可能遇到的情况:因为渲染UI而阻塞用户的互动,这个例子有2个按钮:

+ + + +
function expensiveOperation() {
+  for(let i = 0; i < 1000000; i++) {
+    ctx.fillStyle = 'rgba(0,0,255, 0.2)';
+    ctx.beginPath();
+    ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false);
+    ctx.fill()
+  }
+}
+
+fillBtn.addEventListener('click', expensiveOperation);
+
+alertBtn.addEventListener('click', () =>
+  alert('You clicked me!')
+);
+ +

如果你点击第一个按钮,然后快速点击第二个,会注意到alert消息并没有出现,只有等到圆圈都画完以后,才会出现:因为第一个操作没有完成之前阻塞了第二个操作的运行.

+ +
+

Note: 当然,这个例子也很丑陋,因为我们只是在模拟阻塞效果。但在现实中,这是一个很常见的问题。开发人员们一直在努力缓解它。

+
+ +

为什么是这样? 答案是:JavaScript一般来说是单线程的(single threaded接着我们来介绍线程的概念。

+ +

线程

+ +

一个线程是一个基本的处理过程,程序用它来完成任务。每个线程一次只能执行一个任务:

+ +
Task A --> Task B --> Task C
+ +

每个任务顺序执行,只有前面的结束了,后面的才能开始。

+ +

正如我们之前所说,现在的计算机大都有多个内核(core),因此可以同时执行多个任务。支持多线程的编程语言可以使用计算机的多个内核,同时完成多个任务:

+ +
Thread 1: Task A --> Task B
+Thread 2: Task C --> Task D
+ +

JavaScript 是单线程的

+ +

JavaScript 传统上是单线程的。即使有多个内核,也只能在单一线程上运行多个任务,此线程称为主线程(main thread)。我们上面的例子运行如下:

+ +
Main thread: Render circles to canvas --> Display alert()
+ +

经过一段时间,JavaScript获得了一些工具来帮助解决这种问题。通过 Web workers 可以把一些任务交给一个名为worker的单独的线程,这样就可以同时运行多个JavaScript代码块。一般来说,用一个worker来运行一个耗时的任务,主线程就可以处理用户的交互(避免了阻塞)

+ +
Main thread: Task A --> Task C
+Worker thread: Expensive task B
+ +

记住这些,请查看simple-sync-worker.html (see it running live) , 再次打开浏览器的JavaScript 控制台。这个例子重写了前例:在一个单独的worker线程中计算一千万次日期,你再点击按钮,现在浏览器可以在日期计算完成之前显示段落,阻塞消失了。

+ +

异步代码

+ +

web workers相当有用,但是他们确实也有局限。主要的一个问题是他们不能访问 {{Glossary("DOM")}} — 不能让一个worker直接更新UI。我们不能在worker里面渲染1百万个蓝色圆圈,它基本上只能做算数的苦活。

+ +

其次,虽然在worker里面运行的代码不会产生阻塞,但是基本上还是同步的。当一个函数依赖于几个在它之前运行的过程的结果,这就会成为问题。考虑下面的情况:

+ +
Main thread: Task A --> Task B
+ +

在这种情况下,比如说Task A 正在从服务器上获取一个图片之类的资源,Task B 准备在图片上加一个滤镜。如果开始运行Task A 后立即尝试运行Task B,你将会得到一个错误,因为图像还没有获取到。

+ +
Main thread: Task A --> Task B --> |Task D|
+Worker thread: Task C -----------> |      |
+ +

在这种情况下,假设Task D 要同时使用 Task B 和Task C的结果,如果我们能保证这两个结果同时提供,程序可能正常运行,但是这不太可能。如果Task D 尝试在其中一个结果尚未可用的情况下就运行,程序就会抛出一个错误。

+ +

为了解决这些问题,浏览器允许我们异步运行某些操作。像Promises 这样的功能就允许让一些操作运行 (比如:从服务器上获取图片),然后等待直到结果返回,再运行其他的操作:

+ +
Main thread: Task A                   Task B
+    Promise:      |__async operation__|
+ +

由于操作发生在其他地方,因此在处理异步操作的时候,主线程不会被阻塞。

+ +

我们将在下一篇文章中开始研究如何编写异步代码。 非常令人兴奋,对吧? 继续阅读!

+ +

总结

+ +

围绕异步编程领域,现代软件设计正在加速旋转,就为了让程序在一个时间内做更多的事情。当你使用更新更强大的API时,你会发现在更多的情况下,使用异步编程是唯一的途径。以前写异步代码很困难,现在也需要你来适应,但是已经变容易了很多。在余下的部分,我们将进一步探讨异步代码的重要性,以及如何设计代码来防止前面已经提到过的问题。

+ +

模块大纲

+ + diff --git a/files/zh-cn/conflicting/learn/javascript/asynchronous/promises/index.html b/files/zh-cn/conflicting/learn/javascript/asynchronous/promises/index.html new file mode 100644 index 0000000000..3c73fecd3d --- /dev/null +++ b/files/zh-cn/conflicting/learn/javascript/asynchronous/promises/index.html @@ -0,0 +1,380 @@ +--- +title: async和await:让异步编程更简单 +slug: conflicting/Learn/JavaScript/Asynchronous/Promises +translation_of: Learn/JavaScript/Asynchronous/Async_await +original_slug: Learn/JavaScript/Asynchronous/Async_await +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}
+ +

async functions 和 await 关键字是最近添加到JavaScript语言里面的。它们是ECMAScript 2017 JavaScript版的一部分(参见ECMAScript Next support in Mozilla)。简单来说,它们是基于promises的语法糖,使异步代码更易于编写和阅读。通过使用它们,异步代码看起来更像是老式同步代码,因此它们非常值得学习。本文为您提供了您需要了解的内容。

+ + + + + + + + + + + + +
先决条件:基本的计算机知识,较好理解 JavaScript 基础,以及理解一般异步代码和 promises 。
目标:理解并使用 promise
+ +

async/await 基础

+ +

在代码中使用 async / await 有两个部分。

+ +

async 关键字

+ +

首先,我们使用 async 关键字,把它放在函数声明之前,使其成为 async function。异步函数是一个知道怎样使用 await 关键字调用异步代码的函数。

+ +

尝试在浏览器的JS控制台中键入以下行:

+ +
function hello() { return "Hello" };
+hello();
+ +

该函数返回“Hello” —— 没什么特别的,对吧?

+ +

如果我们将其变成异步函数呢?请尝试以下方法:

+ +
async function hello() { return "Hello" };
+hello();
+ +

哈。现在调用该函数会返回一个 promise。这是异步函数的特征之一 —— 它保证函数的返回值为 promise。

+ +

你也可以创建一个异步函数表达式(参见 async function expression ),如下所示:

+ +
let hello = async function() { return "Hello" };
+hello();
+ +

你可以使用箭头函数:

+ +
let hello = async () => { return "Hello" };
+ +

这些都基本上是一样的。

+ +

要实际使用promise完成时返回的值,我们可以使用.then()块,因为它返回的是 promise:

+ +
hello().then((value) => console.log(value))
+ +

甚至只是简写如

+ +
hello().then(console.log)
+ +

这就像我们在上一篇文章中看到的那样。

+ +

将 async 关键字加到函数申明中,可以告诉它们返回的是 promise,而不是直接返回值。此外,它避免了同步函数为支持使用 await 带来的任何潜在开销。在函数声明为 async 时,JavaScript引擎会添加必要的处理,以优化你的程序。爽!

+ +

await关键字

+ +

当 await 关键字与异步函数一起使用时,它的真正优势就变得明显了 —— 事实上, await 只在异步函数里面才起作用。它可以放在任何异步的,基于 promise 的函数之前。它会暂停代码在该行上,直到 promise 完成,然后返回结果值。在暂停的同时,其他正在等待执行的代码就有机会执行了。

+ +

您可以在调用任何返回Promise的函数时使用 await,包括Web API函数。

+ +

这是一个简单的示例:

+ +
async function hello() {
+  return greeting = await Promise.resolve("Hello");
+};
+
+hello().then(alert);
+ +

当然,上面的示例不是很有用,但它确实展示了语法。让我们继续,看一个真实示例。

+ +

 使用 async/await 重写 promise 代码

+ +

让我们回顾一下我们在上一篇文章中简单的 fetch 示例:

+ +
fetch('coffee.jpg')
+.then(response => response.blob())
+.then(myBlob => {
+  let objectURL = URL.createObjectURL(myBlob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+})
+.catch(e => {
+  console.log('There has been a problem with your fetch operation: ' + e.message);
+});
+ +

到现在为止,你应该对 promises 及其工作方式有一个较好的理解。让我们将其转换为使用async / await看看它使事情变得简单了多少:

+ +
async function myFetch() {
+  let response = await fetch('coffee.jpg');
+  let myBlob = await response.blob();
+
+  let objectURL = URL.createObjectURL(myBlob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+}
+
+myFetch()
+.catch(e => {
+  console.log('There has been a problem with your fetch operation: ' + e.message);
+});
+ +

它使代码简单多了,更容易理解 —— 去除了到处都是 .then() 代码块!

+ +

由于 async 关键字将函数转换为 promise,您可以重构以上代码 —— 使用 promise 和 await 的混合方式,将函数的后半部分抽取到新代码块中。这样做可以更灵活:

+ +
async function myFetch() {
+  let response = await fetch('coffee.jpg');
+  return await response.blob();
+}
+
+myFetch().then((blob) => {
+  let objectURL = URL.createObjectURL(blob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+});
+ +

您可以尝试自己输入示例,或运行我们的 live example (另请参阅source code)。

+ +

它到底是如何工作的?

+ +

您会注意到我们已经将代码封装在函数中,并且我们在 function 关键字之前包含了 async 关键字。这是必要的 –– 您必须创建一个异步函数来定义一个代码块,在其中运行异步代码; await 只能在异步函数内部工作。

+ +

myFetch()函数定义中,您可以看到代码与先前的 promise 版本非常相似,但存在一些差异。不需要附加 .then() 代码块到每个promise-based方法的结尾,你只需要在方法调用前添加 await 关键字,然后把结果赋给变量。await 关键字使JavaScript运行时暂停于此行,允许其他代码在此期间执行,直到异步函数调用返回其结果。一旦完成,您的代码将继续从下一行开始执行。例如:

+ +
let response = await fetch('coffee.jpg');
+ +

解析器会在此行上暂停,直到当服务器返回的响应变得可用时。此时 fetch() 返回的 promise 将会完成(fullfilled),返回的 response 会被赋值给 response 变量。一旦服务器返回的响应可用,解析器就会移动到下一行,从而创建一个Blob。Blob这行也调用基于异步promise的方法,因此我们也在此处使用await。当操作结果返回时,我们将它从myFetch()函数中返回。

+ +

这意味着当我们调用myFetch()函数时,它会返回一个promise,因此我们可以将.then()链接到它的末尾,在其中我们处理显示在屏幕上的blob

+ +

你可能已经觉得“这真的很酷!”,你是对的 —— 用更少的.then()块来封装代码,同时它看起来很像同步代码,所以它非常直观。

+ +

添加错误处理

+ +

如果你想添加错误处理,你有几个选择。

+ +

您可以将同步的 try...catch 结构和 async/await 一起使用 。此示例扩展了我们上面展示的第一个版本代码:

+ +
async function myFetch() {
+  try {
+    let response = await fetch('coffee.jpg');
+    let myBlob = await response.blob();
+
+    let objectURL = URL.createObjectURL(myBlob);
+    let image = document.createElement('img');
+    image.src = objectURL;
+    document.body.appendChild(image);
+  } catch(e) {
+    console.log(e);
+  }
+}
+
+myFetch();
+ +

catch() {} 代码块会接收一个错误对象 e ; 我们现在可以将其记录到控制台,它将向我们提供详细的错误消息,显示错误被抛出的代码中的位置。

+ +

如果你想使用我们上面展示的第二个(重构)代码版本,你最好继续混合方式并将 .catch() 块链接到 .then() 调用的末尾,就像这样:

+ +
async function myFetch() {
+  let response = await fetch('coffee.jpg');
+  return await response.blob();
+}
+
+myFetch().then((blob) => {
+  let objectURL = URL.createObjectURL(blob);
+  let image = document.createElement('img');
+  image.src = objectURL;
+  document.body.appendChild(image);
+})
+.catch((e) =>
+  console.log(e)
+);
+ +

这是因为 .catch() 块将捕获来自异步函数调用和promise链中的错误。如果您在此处使用了try/catch 代码块,则在调用 myFetch() 函数时,您仍可能会收到未处理的错误。

+ +

您可以在GitHub上找到这两个示例:

+ + + +

等待Promise.all()

+ +

async / await 建立在 promises 之上,因此它与promises提供的所有功能兼容。这包括Promise.all() –– 你完全可以通过调用 await Promise.all() 将所有结果返回到变量中,就像同步代码一样。让我们再次回到上一篇中看到的例子。在单独的选项卡中打开它,以便您可以与下面显示的新版本进行比较和对比。

+ +

将其转换为 async / await(请参阅 样例 和 源码),现在看起来像这样:

+ +
async function fetchAndDecode(url, type) {
+  let response = await fetch(url);
+
+  let content;
+
+  if(type === 'blob') {
+    content = await response.blob();
+  } else if(type === 'text') {
+    content = await response.text();
+  }
+
+  return content;
+}
+
+async function displayContent() {
+  let coffee = fetchAndDecode('coffee.jpg', 'blob');
+  let tea = fetchAndDecode('tea.jpg', 'blob');
+  let description = fetchAndDecode('description.txt', 'text');
+
+  let values = await Promise.all([coffee, tea, description]);
+
+  let objectURL1 = URL.createObjectURL(values[0]);
+  let objectURL2 = URL.createObjectURL(values[1]);
+  let descText = values[2];
+
+  let image1 = document.createElement('img');
+  let image2 = document.createElement('img');
+  image1.src = objectURL1;
+  image2.src = objectURL2;
+  document.body.appendChild(image1);
+  document.body.appendChild(image2);
+
+  let para = document.createElement('p');
+  para.textContent = descText;
+  document.body.appendChild(para);
+}
+
+displayContent()
+.catch((e) =>
+  console.log(e)
+);
+ +

可以看到 fetchAndDecode() 函数只进行了一丁点的修改就转换成了异步函数。请看Promise.all() 行:

+ +
let values = await Promise.all([coffee, tea, description]);
+ +

在这里,通过使用await,我们能够在三个promise的结果都可用的时候,放入values数组中。这看起来非常像同步代码。我们需要将所有代码封装在一个新的异步函数displayContent() 中,尽管没有减少很多代码,但能够将大部分代码从 .then() 代码块移出,使代码得到了简化,更易读。

+ +

为了错误处理,我们在 displayContent() 调用中包含了一个 .catch() 代码块;这将处理两个函数中出现的错误。

+ +
+

注意: 也可以在异步函数中使用同步 finally 代码块代替 .finally() 异步代码块,以显示操作如何进行的最终报告——您可以在我们的 live example (查看源代码)中看到这一点。

+
+ +

async/await的缺陷

+ +

了解Async/await是非常有用的,但还有一些缺点需要考虑。

+ +

Async/await 让你的代码看起来是同步的,在某种程度上,也使得它的行为更加地同步。 await 关键字会阻塞其后的代码,直到promise完成,就像执行同步操作一样。它确实可以允许其他任务在此期间继续运行,但您自己的代码被阻塞。

+ +

这意味着您的代码可能会因为大量await的promises相继发生而变慢。每个await都会等待前一个完成,而你实际想要的是所有的这些promises同时开始处理(就像我们没有使用async/await时那样)。

+ +

有一种模式可以缓解这个问题——通过将 Promise 对象存储在变量中来同时开始它们,然后等待它们全部执行完毕。让我们看一些证明这个概念的例子。

+ +

我们有两个可用的例子 —— slow-async-await.html(参见source code)和fast-async-await.html(参见source code)。它们都以自定义promise函数开始,该函数使用setTimeout() 调用伪造异步进程:

+ +
function timeoutPromise(interval) {
+  return new Promise((resolve, reject) => {
+    setTimeout(function(){
+      resolve("done");
+    }, interval);
+  });
+};
+ +

然后每个包含一个 timeTest() 异步函数,等待三个 timeoutPromise() 调用:

+ +
async function timeTest() {
+  ...
+}
+ +

每一个都以记录开始时间结束,查看 timeTest() promise 需要多长时间才能完成,然后记录结束时间并报告操作总共需要多长时间:

+ +
let startTime = Date.now();
+timeTest().then(() => {
+  let finishTime = Date.now();
+  let timeTaken = finishTime - startTime;
+  alert("Time taken in milliseconds: " + timeTaken);
+})
+ +

timeTest() 函数在每种情况下都不同。

+ +

slow-async-await.html示例中,timeTest() 如下所示:

+ +
async function timeTest() {
+  await timeoutPromise(3000);
+  await timeoutPromise(3000);
+  await timeoutPromise(3000);
+}
+ +

在这里,我们直接等待所有三个timeoutPromise()调用,使每个调用3秒钟。后续的每一个都被迫等到最后一个完成 - 如果你运行第一个例子,你会看到弹出框报告的总运行时间大约为9秒。

+ +

fast-async-await.html示例中,timeTest() 如下所示:

+ +
async function timeTest() {
+  const timeoutPromise1 = timeoutPromise(3000);
+  const timeoutPromise2 = timeoutPromise(3000);
+  const timeoutPromise3 = timeoutPromise(3000);
+
+  await timeoutPromise1;
+  await timeoutPromise2;
+  await timeoutPromise3;
+}
+ +

在这里,我们将三个Promise对象存储在变量中,这样可以同时启动它们关联的进程。

+ +

接下来,我们等待他们的结果 - 因为promise都在基本上同时开始处理,promise将同时完成;当您运行第二个示例时,您将看到弹出框报告总运行时间仅超过3秒!

+ +

您必须仔细测试您的代码,并在性能开始受损时牢记这一点。

+ +

另一个小小的不便是你必须将等待执行的promise封装在异步函数中。

+ +

Async/await 的类方法

+ +

最后值得一提的是,我们可以在类/对象方法前面添加async,以使它们返回promises,并await它们内部的promises。查看 ES class code we saw in our object-oriented JavaScript article,然后查看使用异步方法的修改版本:

+ +
class Person {
+  constructor(first, last, age, gender, interests) {
+    this.name = {
+      first,
+      last
+    };
+    this.age = age;
+    this.gender = gender;
+    this.interests = interests;
+  }
+
+  async greeting() {
+    return await Promise.resolve(`Hi! I'm ${this.name.first}`);
+  };
+
+  farewell() {
+    console.log(`${this.name.first} has left the building. Bye for now!`);
+  };
+}
+
+let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);
+ +

第一个实例方法可以使用如下:

+ +
han.greeting().then(console.log);
+ +

浏览器的支持

+ +

决定是否使用 async/await 时的一个考虑因素是支持旧浏览器。它们适用于大多数浏览器的现代版本,与promise相同; 主要的支持问题存在于Internet Explorer和Opera Mini。

+ +

如果你想使用async/await但是担心旧的浏览器支持,你可以考虑使用BabelJS库 —— 这允许你使用最新的JavaScript编写应用程序,让Babel找出用户浏览器需要的更改。在遇到不支持async/await 的浏览器时,Babel的 polyfill 可以自动提供适用于旧版浏览器的实现。

+ +

总结

+ +

async/await提供了一种很好的,简化的方法来编写更易于阅读和维护的异步代码。即使浏览器支持在撰写本文时比其他异步代码机制更受限制,但无论是现在还是将来,都值得学习和考虑使用。

+ +

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}

+ +

本章内容

+ + diff --git a/files/zh-cn/conflicting/learn/javascript/asynchronous_ae5a561b0ec11fc53c167201aa8af5df/index.html b/files/zh-cn/conflicting/learn/javascript/asynchronous_ae5a561b0ec11fc53c167201aa8af5df/index.html new file mode 100644 index 0000000000..a3500922af --- /dev/null +++ b/files/zh-cn/conflicting/learn/javascript/asynchronous_ae5a561b0ec11fc53c167201aa8af5df/index.html @@ -0,0 +1,618 @@ +--- +title: '合作异步JavaScript: 超时和间隔' +slug: conflicting/Learn/JavaScript/Asynchronous_ae5a561b0ec11fc53c167201aa8af5df +translation_of: Learn/JavaScript/Asynchronous/Timeouts_and_intervals +original_slug: Learn/JavaScript/Asynchronous/Timeouts_and_intervals +--- +
{{LearnSidebar}}
+ + + +
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}
+ + + +

在这里,我们将讨论传统的JavaScript方法,这些方法可以在一段时间或一段规则间隔(例如,每秒固定的次数)之后,以异步方式运行代码,并讨论它们的用处,以及它们的固有问题。

+ + + + + + + + + + + + +
预备条件:基本的计算机知识,对JavaScript基本原理有较好的理解。
目标:了解异步循环和间隔及其用途。
+ +

介绍

+ +

很长一段时间以来,web平台为JavaScript程序员提供了许多函数,这些函数允许您在一段时间间隔过后异步执行代码,或者重复异步执行代码块,直到您告诉它停止为止。这些都是:

+ +
+
setTimeout()
+
在指定的时间后执行一段代码.
+
setInterval()
+
以固定的时间间隔,重复运行一段代码.
+
requestAnimationFrame()
+
setInterval()的现代版本;在浏览器下一次重新绘制显示之前执行指定的代码块,从而允许动画在适当的帧率下运行,而不管它在什么环境中运行.
+
+ +

这些函数设置的异步代码实际上在主线程上运行(在其指定的计时器过去之后)。

+ +

在 setTimeout() 调用执行之前或 setInterval() 迭代之间可以(并且经常会)运行其他代码。根据这些操作的处理器密集程度,它们可以进一步延迟异步代码,因为任何异步代码仅在主线程可用后才执行(换句话说,当调用栈为空时)。在阅读本文时,您将学到更多关于此问题的信息。

+ +

无论如何,这些函数用于在web站点或应用程序上运行不间断的动画和其他后台处理。在下面的部分中,我们将向您展示如何使用它们。

+ +

setTimeout()

+ +

正如前述, setTimeout() 在指定的时间后执行一段特定代码. 它需要如下参数:

+ + + +
+

Note: 指定的时间(或延迟)不能保证在指定的确切时间之后执行,而是最短的延迟执行时间。在主线程上的堆栈为空之前,传递给这些函数的回调将无法运行。

+ +

结果,像 setTimeout(fn, 0) 这样的代码将在堆栈为空时立即执行,而不是立即执行。如果执行类似 setTimeout(fn, 0) 之类的代码,之后立即运行从 1 到 100亿 的循环之后,回调将在几秒后执行。 

+
+ +

在下面的示例中,浏览器将在执行匿名函数之前等待两秒钟,然后显示alert消息 (see it running live, and see the source code):

+ +
let myGreeting = setTimeout(function() {
+  alert('Hello, Mr. Universe!');
+}, 2000)
+ +

我们指定的函数不必是匿名的。我们可以给函数一个名称,甚至可以在其他地方定义它,并将函数引用传递给 setTimeout() 。以下两个版本的代码片段相当于第一个版本:

+ +
// With a named function
+let myGreeting = setTimeout(function sayHi() {
+  alert('Hello, Mr. Universe!');
+}, 2000)
+
+// With a function defined separately
+function sayHi() {
+  alert('Hello Mr. Universe!');
+}
+
+let myGreeting = setTimeout(sayHi, 2000);
+ +

例如,如果我们有一个函数既需要从超时调用,也需要响应某个事件,那么这将非常有用。此外它也可以帮助保持代码整洁,特别是当超时回调超过几行代码时。

+ +

setTimeout() 返回一个标志符变量用来引用这个间隔,可以稍后用来取消这个超时任务,下面就会学到 {{anch("Clearing timeouts")}} 。

+ +

传递参数给setTimeout() 

+ +

我们希望传递给setTimeout()中运行的函数的任何参数,都必须作为列表末尾的附加参数传递给它。

+ +

例如,我们可以重构之前的函数,这样无论传递给它的人的名字是什么,它都会向它打招呼:

+ +
function sayHi(who) {
+  alert('Hello ' + who + '!');
+}
+ +

人名可以通过第三个参数传进 setTimeout()

+ +
let myGreeting = setTimeout(sayHi, 2000, 'Mr. Universe');
+ +

清除超时

+ +

最后,如果创建了 timeout,您可以通过调用clearTimeout(),将setTimeout()调用的标识符作为参数传递给它,从而在超时运行之前取消。要取消上面的超时,你需要这样做:

+ +
clearTimeout(myGreeting);
+ +
+

注意: 请参阅 greeter-app.html 以获得稍微复杂一点的演示,该演示允许您在表单中设置要打招呼的人的姓名,并使用单独的按钮取消问候语(see the source code also)。

+
+ +

setInterval()

+ +

当我们需要在一段时间之后运行一次代码时,setTimeout()可以很好地工作。但是当我们需要反复运行代码时会发生什么,例如在动画的情况下?

+ +

这就是setInterval()的作用所在。这与setTimeout()的工作方式非常相似,只是作为第一个参数传递给它的函数,重复执行的时间不少于第二个参数给出的毫秒数,而不是一次执行。您还可以将正在执行的函数所需的任何参数作为 setInterval() 调用的后续参数传递。

+ +

让我们看一个例子。下面的函数创建一个新的Date()对象,使用toLocaleTimeString()从中提取一个时间字符串,然后在UI中显示它。然后,我们使用setInterval()每秒运行该函数一次,创建一个每秒更新一次的数字时钟的效果。

+ +

(see this live, and also see the source):

+ +
function displayTime() {
+   let date = new Date();
+   let time = date.toLocaleTimeString();
+   document.getElementById('demo').textContent = time;
+}
+
+const createClock = setInterval(displayTime, 1000);
+ +

setTimeout()一样, setInterval() 返回一个确定的值,稍后你可以用它来取消间隔任务。

+ +

清除intervals

+ +

setInterval()永远保持运行任务,除非我们做点什么——我们可能会想阻止这样的任务,否则当浏览器无法完成任何进一步的任务时我们可能得到错误, 或者动画处理已经完成了。我们可以用与停止超时相同的方法来实现这一点——通过将setInterval()调用返回的标识符传递给clearInterval()函数:

+ +
const myInterval = setInterval(myFunction, 2000);
+
+clearInterval(myInterval);
+ +

主动学习:创建秒表!

+ +

话虽如此,我们还是要给你一个挑战。以我们的setInterval-clock.html为例,修改它以创建您自己的简单秒表。

+ +

你要像前面一样显示时间,但是在这里,你需要:

+ + + +

提示:

+ + + +
+

Note: 如果您在操作过程有困难,请参考 see it runing live (see the source code also).

+
+ +

关于 setTimeout() 和 setInterval() 需要注意的几点

+ +

当使用 setTimeout()setInterval()的时候,有几点需要额外注意。 现在让我们回顾一下:

+ +

递归的timeouts

+ +

还有另一种方法可以使用setTimeout():我们可以递归调用它来重复运行相同的代码,而不是使用setInterval()

+ +

下面的示例使用递归setTimeout()每100毫秒运行传递来的函数:

+ +
let i = 1;
+
+setTimeout(function run() {
+  console.log(i);
+  i++;
+  setTimeout(run, 100);
+}, 100);
+ +

将上面的示例与下面的示例进行比较 ––这使用 setInterval() 来实现相同的效果:

+ +
let i = 1;
+
+setInterval(function run() {
+  console.log(i);
+  i++
+}, 100);
+ +

递归setTimeout()和setInterval()有何不同?

+ +

上述代码的两个版本之间的差异是微妙的。

+ + + +

当你的代码有可能比你分配的时间间隔,花费更长时间运行时,最好使用递归的 setTimeout() - 这将使执行之间的时间间隔保持不变,无论代码执行多长时间,你不会得到错误。

+ +

立即超时

+ +

使用0用作setTimeout()的回调函数会立刻执行,但是在主线程代码运行之后执行。

+ +

举个例子,下面的代码(see it live) 输出一个包含警报的"Hello",然后在您点击第一个警报的OK之后立即弹出“world”。

+ +
setTimeout(function() {
+  alert('World');
+}, 0);
+
+alert('Hello');
+ +

如果您希望设置一个代码块以便在所有主线程完成运行后立即运行,这将很有用。将其放在异步事件循环中,这样它将随后直接运行。

+ +

使用 clearTimeout() or clearInterval()清除

+ +

clearTimeout()clearInterval() 都使用相同的条目列表进行清除。有趣的是,这意味着你可以使用任一一种方法来清除 setTimeout() 和 setInterval()。

+ +

但为了保持一致性,你应该使用 clearTimeout() 来清除 setTimeout() 条目,使用 clearInterval() 来清除 setInterval() 条目。 这样有助于避免混乱。

+ +

requestAnimationFrame()

+ +

requestAnimationFrame() 是一个专门的循环函数,旨在浏览器中高效运行动画。它基本上是现代版本的setInterval() —— 它在浏览器重新加载显示内容之前执行指定的代码块,从而允许动画以适当的帧速率运行,不管其运行的环境如何。

+ +

它是针对setInterval() 遇到的问题创建的,比如 setInterval()并不是针对设备优化的帧率运行,有时会丢帧。还有即使该选项卡不是活动的选项卡或动画滚出页面等问题 。

+ +

(在CreativeJS上了解有关此内容的更多信息).

+ +
+

注意: 你可以在课程中其他地方找到requestAnimationFrame() 的使用范例—参见 Drawing graphics, 和 Object building practice

+
+ +

该方法将重新加载页面之前要调用的回调函数作为参数。这是您将看到的常见表达:

+ +
function draw() {
+   // Drawing code goes here
+   requestAnimationFrame(draw);
+}
+
+draw();
+ +

这个想法是要定义一个函数,在其中更新动画 (例如,移动精灵,更新乐谱,刷新数据等),然后调用它来开始这个过程。在函数的末尾,以 requestAnimationFrame() 传递的函数作为参数进行调用,这指示浏览器在下一次显示重新绘制时再次调用该函数。然后这个操作连续运行, 因为requestAnimationFrame() 是递归调用的。

+ +
+

注意: 如果要执行某种简单的常规DOM动画, CSS 动画 可能更快,因为它们是由浏览器的内部代码计算而不是JavaScript直接计算的。但是,如果您正在做一些更复杂的事情,并且涉及到在DOM中不能直接访问的对象(such as 2D Canvas API or WebGL objects), requestAnimationFrame() 在大多数情况下是更好的选择。

+
+ +

你的动画跑得有多快?

+ +

动画的平滑度直接取决于动画的帧速率,并以每秒帧数(fps)为单位进行测量。这个数字越高,动画看起来就越平滑。

+ +

由于大多数屏幕的刷新率为60Hz,因此在使用web浏览器时,可以达到的最快帧速率是每秒60帧(FPS)。然而,更多的帧意味着更多的处理,这通常会导致卡顿和跳跃-也称为丢帧或跳帧。

+ +

如果您有一个刷新率为60Hz的显示器,并且希望达到60fps,则大约有16.7毫秒(1000/60)来执行动画代码来渲染每个帧。这提醒我们,我们需要注意每次通过动画循环时要运行的代码量。

+ +

requestAnimationFrame() 总是试图尽可能接近60帧/秒的值,当然有时这是不可能的如果你有一个非常复杂的动画,你是在一个缓慢的计算机上运行它,你的帧速率将更少。requestAnimationFrame() 会尽其所能利用现有资源提升帧速率。

+ +

 requestAnimationFrame() 与 setInterval() 和 setTimeout()有什么不同?

+ +

让我们进一步讨论一下 requestAnimationFrame() 方法与前面介绍的其他方法的区别. 下面让我们看一下代码:

+ +
function draw() {
+   // Drawing code goes here
+   requestAnimationFrame(draw);
+}
+
+draw();
+ +

现在让我们看看如何使用setInterval():

+ +
function draw() {
+   // Drawing code goes here
+}
+
+setInterval(draw, 17);
+ +

如前所述,我们没有为requestAnimationFrame();指定时间间隔;它只是在当前条件下尽可能快速平稳地运行它。如果动画由于某些原因而处于屏幕外浏览器也不会浪费时间运行它。

+ +

 另一方面setInterval()需要指定间隔。我们通过公式1000毫秒/60Hz得出17的最终值,然后将其四舍五入。四舍五入是一个好主意,浏览器可能会尝试运行动画的速度超过60fps,它不会对动画的平滑度有任何影响。如前所述,60Hz是标准刷新率。

+ +

包括时间戳

+ +

传递给 requestAnimationFrame() 函数的实际回调也可以被赋予一个参数(一个时间戳值),表示自 requestAnimationFrame() 开始运行以来的时间。这是很有用的,因为它允许您在特定的时间以恒定的速度运行,而不管您的设备有多快或多慢。您将使用的一般模式如下所示:

+ +
let startTime = null;
+
+function draw(timestamp) {
+    if(!startTime) {
+      startTime = timestamp;
+    }
+
+   currentTime = timestamp - startTime;
+
+   // Do something based on current time
+
+   requestAnimationFrame(draw);
+}
+
+draw();
+ +

浏览器支持

+ +

 与setInterval()setTimeout() 相比最近的浏览器支持requestAnimationFrame()

+ +

requestAnimationFrame().在Internet Explorer 10及更高版本中可用。因此,除非您的代码需要支持旧版本的IE,否则没有什么理由不使用requestAnimationFrame() 。

+ +

一个简单的例子

+ +

学习上述理论已经足够了,下面让我们仔细研究并构建自己的requestAnimationFrame() 示例。我们将创建一个简单的“旋转器动画”(spinner animation),即当应用程序忙于连接到服务器时可能会显示的那种动画。

+ +
+

注意: 一般来说,像以下例子中如此简单的动画应用CSS动画来实现,这里使用requestAnimationFrame()只是为了帮助解释其用法。requestAnimationFrame()正常应用于如逐帧更新游戏画面这样的复杂动画。

+
+ +
    +
  1. +

    首先, 下载我们的网页模板

    +
  2. +
  3. +

    放置一个空的 {{htmlelement("div")}} 元素进入 {{htmlelement("body")}}, 然后在其中加入一个 ↻ 字符.这是一个将循环的字符将在我们的例子中作为我们的旋转器(spinner)。

    +
  4. +
  5. +

    用任何你喜欢的方法应用下述的CSS到HTML模板中。这些在页面上设置了一个红色背景,将<body>的高度设置为100%<html>的高度,并将<div>水平和竖直居中。

    + +
    html {
    +  background-color: white;
    +  height: 100%;
    +}
    +
    +body {
    +  height: inherit;
    +  background-color: red;
    +  margin: 0;
    +  display: flex;
    +  justify-content: center;
    +  align-items: center;
    +}
    +
    +div {
    +  display: inline-block;
    +  font-size: 10rem;
    +}
    +
  6. +
  7. +

    插入一个 {{htmlelement("script")}}元素在 </body> 标签之上。

    +
  8. +
  9. +

    插入下述的JavaScript在你的 <script> 元素中。这里我们存储了一个<div>的引用在一个常量中,设置rotateCount变量为 0, 设置一个未初始化的变量之后将会用作容纳一个requestAnimationFrame() 的调用, 然后设置一个 startTime 变量为 null,它之后将会用作存储 requestAnimationFrame() 的起始时间.。

    + +
    const spinner = document.querySelector('div');
    +let rotateCount = 0;
    +let rAF;
    +let startTime = null;
    +
  10. +
  11. +

    在之前的代码下面, 插入一个 draw() 函数将被用作容纳我们的动画代码,并且包含了 时间戳 参数。

    + +
    function draw(timestamp) {
    +
    +}
    +
  12. +
  13. +

    draw()中, 加入下述的几行。 如果起始时间还没有被赋值的话,将timestamp 传给它(这只将发生在循环中的第一步)。 并赋值给rotateCount ,以旋转 旋转器(spinning)(此处取(当前时间戳 – 起始时间戳) / 3,以免它转得太快):

    + +
      if (!startTime) {
    +   startTime = timestamp;
    +  }
    +
    +  rotateCount = (timestamp - startTime) / 3;
    +
    +
  14. +
  15. +

    draw()内我们刚刚添加的代码之后,添加以下代码 — 此处是在检查rotateCount 的值是否超过了359 (e.g. 360, 一个正圆的度数)。 如果是,与360取模(值除以 360 后剩下的余数),使得圆圈的动画能以合理的低值连续播放。需要注意的是,这样的操作并不是必要的,只是比起类似于“128000度”的值,运行在 0-359 度之间会使你的操作更容易些。

    + +
    if (rotateCount > 359) {
    +  rotateCount %= 360;
    +}
    +
  16. +
  17. +

    接下来,在上一个块下面添加以下行以实际旋转 旋转(spinner)器:

    + +
    spinner.style.transform = `rotate(${rotateCount}deg)`;
    +
  18. +
  19. +

    在函数draw()内的最下方,插入下面这行代码。这是整个操作中最关键的部分 — 我们将前面定义的变量设置为以draw()函数为参数的活动requestAnimation()调用。 这样动画就开始了,以尽可能接近60 FPS的速率不断地运行draw()函数。

    + +
    rAF = requestAnimationFrame(draw);
    +
  20. +
  21. +

    draw() 函数定义下方,添加对 draw() 函数的调用以启动动画。

    +
  22. +
  23. +
    draw();
    +
  24. +
+ +
+

Note: You can find this example live on GitHub (see the source code also).

+
+ +

撤销requestAnimationFrame()

+ +

requestAnimationFrame()可用与之对应的cancelAnimationFrame()方法“撤销”(不同于“set…”类方法的“清除”,此处更接近“撤销”之意)。

+ +

该方法以requestAnimationFrame()的返回值为参数,此处我们将该返回值存在变量 rAF 中:

+ +
cancelAnimationFrame(rAF);
+ +

主动学习: 启动和停止旋转器(spinner)

+ +

在这个练习中,我们希望你能对cancelAnimationFrame()方法做一些测试,在之前的示例中添加新内容。你需要在示例中添加一个事件监听器,用于当鼠标在页面任意位置点击时,启动或停止旋转器。 

+ +

一些提示:

+ + + +
+

Note: 先自己尝试一下,如果你确实遇到困难,可以参看我们的在线示例源代码

+
+ +

限制 (节流) requestAnimationFrame()动画

+ +

requestAnimationFrame() 的限制之一是无法选择帧率。在大多数情况下,这不是问题,因为通常希望动画尽可能流畅地运行。但是,当要创建老式的8位类型的动画时,怎么办?

+ +

例如,在我们的 Drawing Graphics 文章中的猴子岛行走动画中的一个问题:

+ +

{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}

+ +

在此示例中,必须为角色在屏幕上的位置及显示的精灵设置动画。精灵动画中只有6帧。如果通过 requestAnimationFrame() 为屏幕上显示的每个帧显示不同的精灵帧,则 Guybrush 的四肢移动太快,动画看起来很荒谬。因此,此示例使用以下代码限制精灵循环的帧率:

+ +
if (posX % 13 === 0) {
+  if (sprite === 5) {
+    sprite = 0;
+  } else {
+    sprite++;
+  }
+}
+ +

因此,代码每13个动画帧循环一次精灵。

+ +

...实际上,大约是每 6.5 帧,因为我们将每帧更新 posX 2个单位值(角色在屏幕上的位置)

+ +
if(posX > width/2) {
+  newStartPos = -( (width / 2) + 102 );
+  posX = Math.ceil(newStartPos / 13) * 13;
+  console.log(posX);
+} else {
+  posX += 2;
+}
+ +

这是用于计算如何更新每个动画帧中的位置的代码。

+ +

用于限制动画的方法将取决于特定代码。例如,在前面的旋转器实例中,可以通过仅在每帧中增加 rotateCount 一个单位(而不是两个单位)来使其运动速度变慢。

+ +

主动学习:反应游戏

+ +

对于本文的最后部分,将创建一个 2 人反应游戏。游戏将有两个玩家,其中一个使用 A 键控制游戏,另一个使用 L 键控制游戏。

+ +

按下 开始 按钮后,像前面看到的旋转器那样的将显示 5 到 10 秒之间的随机时间。之后将出现一条消息,提示 “PLAYERS GO!!”。一旦这发生,第一个按下其控制按钮的玩家将赢得比赛。

+ +

{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}

+ +

让我们来完成以下工作:

+ +
    +
  1. +

    首先,下载 starter file for the app 。其中包含完成的 HTML 结构和 CSS 样式,为我们提供了一个游戏板,其中显示了两个玩家的信息(如上所示),但 spinner 和 结果段落重叠显示,只需要编写 JavaScript 代码。

    +
  2. +
  3. +

    在页面上空的 {{htmlelement("script")}} 元素里,首先添加以下几行代码,这些代码定义了其余代码中需要的一些常量和变量:

    + +
    const spinner = document.querySelector('.spinner p');
    +const spinnerContainer = document.querySelector('.spinner');
    +let rotateCount = 0;
    +let startTime = null;
    +let rAF;
    +const btn = document.querySelector('button');
    +const result = document.querySelector('.result');
    + +

    这些是:

    + +
      +
    1. 对旋转器的引用,因此可以对其进行动画处理。
    2. +
    3. 包含旋转器 {{htmlelement("div")}} 元素的引用。用于显示和隐藏它。
    4. +
    5. 旋转计数。这确定了显示在动画每一帧上的旋转器旋转了多少。
    6. +
    7. 开始时间为null。当旋转器开始旋转时,它将赋值为 开始时间。
    8. +
    9. 一个未初始化的变量,用于之后存储使 旋转器 动画化的  {{domxref("Window.requestAnimationFrame", "requestAnimationFrame()")}}  调用。
    10. +
    11. 开始按钮的引用。
    12. +
    13. 结果字段的引用。
    14. +
    +
  4. +
  5. +

    接下来,在前几行代码下方,添加以下函数。它只接收两个数字并返回一个在两个数字之间的随机数。稍后将需要它来生成随机超时间隔。

    + +
    function random(min,max) {
    +  var num = Math.floor(Math.random()*(max-min)) + min;
    +  return num;
    +}
    +
  6. +
  7. +

    接下来添加 draw() 函数以使 旋转器 动画化。这与之前的简单旋转器示例中的版本非常相似:

    + +
      function draw(timestamp) {
    +    if(!startTime) {
    +     startTime = timestamp;
    +    }
    +
    +    let rotateCount = (timestamp - startTime) / 3;
    +
    +    if(rotateCount > 359) {
    +      rotateCount %= 360;
    +    }
    +
    +    spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
    +    rAF = requestAnimationFrame(draw);
    +  }
    +
  8. +
  9. +

    现在是时候在页面首次加载时设置应用程序的初始状态了。添加以下两行,它们使用 display: none; 隐藏结果段落和旋转器容器。

    + +
    result.style.display = 'none';
    +spinnerContainer.style.display = 'none';
    +
  10. +
  11. +

    接下来,定义一个 reset() 函数。该函数在游戏结束后将游戏设置回初始状态以便再次开启游戏。在代码底部添加以下内容:

    + +
    function reset() {
    +  btn.style.display = 'block';
    +  result.textContent = '';
    +  result.style.display = 'none';
    +}
    +
  12. +
  13. 好的,准备充分!现在该使游戏变得可玩了!将以下代码块添加到代码中。 start() 函数调用 draw() 以启动 旋转器,并在UI中显示它,隐藏“开始”按钮,这样您就无法通过同时启动多次来弄乱游戏,并运行一个经过5到10秒的随机间隔后,会运行 setEndgame() 函数的 setTimeout() 。下面的代码块还将一个事件侦听器添加到按钮上,以在单击它时运行 start() 函数。 +
    btn.addEventListener('click', start);
    +
    +function start() {
    +  draw();
    +  spinnerContainer.style.display = 'block';
    +  btn.style.display = 'none';
    +  setTimeout(setEndgame, random(5000,10000));
    +}
    +
  14. +
  15. 添加以下方法到代码:
  16. +
+ +
function setEndgame() {
+  cancelAnimationFrame(rAF);
+  spinnerContainer.style.display = 'none';
+  result.style.display = 'block';
+  result.textContent = 'PLAYERS GO!!';
+
+  document.addEventListener('keydown', keyHandler);
+
+  function keyHandler(e) {
+    console.log(e.key);
+    if(e.key === 'a') {
+      result.textContent = 'Player 1 won!!';
+    } else if(e.key === 'l') {
+      result.textContent = 'Player 2 won!!';
+    }
+
+    document.removeEventListener('keydown', keyHandler);
+    setTimeout(reset, 5000);
+  };
+}
+ +

逐步执行以下操作:

+ +
    +
  1. 首先通过 {{domxref("window.cancelAnimationFrame", "cancelAnimationFrame()")}}  取消 旋转器 动画(清理不必要的流程总是一件好事),隐藏 旋转器 容器。
  2. +
  3. 接下来,显示结果段落并将其文本内容设置为“ PLAYERS GO !!”。向玩家发出信号,表示他们现在可以按下按钮来取胜。
  4. +
  5. keydown 事件侦听器附加到 document。当按下任何按钮时,keyHandler() 函数将运行。
  6. +
  7. 在 keyHandler() 里,在 keyHandler() 内部,代码包括作为参数的事件对象作为参数(用 e 表示)- 其 {{domxref("KeyboardEvent.key", "key")}} 属性包含刚刚按下的键,可以通过这个对象来对特定的操作和特定的按键做出响应。
  8. +
  9. 将变量 isOver 设置为 false ,这样我们就可以跟踪是否按下了正确的按键以使玩家1或2获胜。我们不希望游戏在按下错误的键后结束。
  10. +
  11. e.key 输出到控制台,这是找出所按的不同键的键值的有用方法。
  12. +
  13. e.key 为“ a”时,显示一条消息说玩家1获胜;当 e.key 为“ l”时,显示消息说玩家2获胜。 (注意:这仅适用于小写的a和l - 如果提交了大写的 A 或 L(键加上 Shift),则将其视为另一个键!)如果按下了其中一个键,请将 isOver 设置为 true
  14. +
  15. 仅当 isOvertrue 时,才使用 {{domxref("EventTarget.removeEventListener", "removeEventListener()")}} 删除 keydown 事件侦听器,以便一旦产生获胜的按键,就不再有键盘输入可以弄乱最终游戏结果。您还可以使用 setTimeout() 在5秒钟后调用 reset()-如前所述,此函数将游戏重置为原始状态,以便可以开始新游戏。
  16. +
+ +

就这样-一切都完成了!

+ +
+

Note: 如果卡住了, check out our version of the reaction game (see the source code also).

+
+ +

结论

+ +

就是这样-异步循环和间隔的所有要点在一篇文章中介绍了。您会发现这些方法在许多情况下都很有用,但请注意不要过度使用它们!因为它们仍然在主线程上运行,所以繁重的回调(尤其是那些操纵DOM的回调)会在不注意的情况下降低页面的速度。

+ +

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}

+ +

In this module

+ + diff --git a/files/zh-cn/conflicting/web/api/document/visibilitychange_event/index.html b/files/zh-cn/conflicting/web/api/document/visibilitychange_event/index.html new file mode 100644 index 0000000000..e79fcf4cf9 --- /dev/null +++ b/files/zh-cn/conflicting/web/api/document/visibilitychange_event/index.html @@ -0,0 +1,54 @@ +--- +title: Document.onvisibilitychange +slug: conflicting/Web/API/Document/visibilitychange_event +translation_of: Web/API/Document/onvisibilitychange +original_slug: Web/API/Document/onvisibilitychange +--- +
{{ApiRef('DOM')}}
+ +

Document.onvisibilitychange 是一个事件处理方法,它将在该对象的 visibilitychange事件被触发时调用。

+ +

Syntax

+ +
obj.onvisibilitychange = function;
+
+ + + +

例子

+ +
document.onvisibilitychange = function() {
+  console.log("Visibility of page has changed!");
+};
+
+ +

标准

+ + + + + + + + + + + + + + +
SpecificationStatusComment
{{SpecName('Page Visibility API','#onvisiblitychange-event-handler','onvisibilitychange')}}{{Spec2('Page Visibility API')}}Initial definition.
+ +

浏览器兼容性

+ + + +

{{Compat("api.Document.onvisibilitychange")}}

+ +

另见

+ + diff --git a/files/zh-cn/learn/javascript/asynchronous/async_await/index.html b/files/zh-cn/learn/javascript/asynchronous/async_await/index.html deleted file mode 100644 index d584914106..0000000000 --- a/files/zh-cn/learn/javascript/asynchronous/async_await/index.html +++ /dev/null @@ -1,380 +0,0 @@ ---- -title: async和await:让异步编程更简单 -slug: Learn/JavaScript/Asynchronous/Async_await -translation_of: Learn/JavaScript/Asynchronous/Async_await -original_slug: learn/JavaScript/异步/Async_await ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}
- -

async functions 和 await 关键字是最近添加到JavaScript语言里面的。它们是ECMAScript 2017 JavaScript版的一部分(参见ECMAScript Next support in Mozilla)。简单来说,它们是基于promises的语法糖,使异步代码更易于编写和阅读。通过使用它们,异步代码看起来更像是老式同步代码,因此它们非常值得学习。本文为您提供了您需要了解的内容。

- - - - - - - - - - - - -
先决条件:基本的计算机知识,较好理解 JavaScript 基础,以及理解一般异步代码和 promises 。
目标:理解并使用 promise
- -

async/await 基础

- -

在代码中使用 async / await 有两个部分。

- -

async 关键字

- -

首先,我们使用 async 关键字,把它放在函数声明之前,使其成为 async function。异步函数是一个知道怎样使用 await 关键字调用异步代码的函数。

- -

尝试在浏览器的JS控制台中键入以下行:

- -
function hello() { return "Hello" };
-hello();
- -

该函数返回“Hello” —— 没什么特别的,对吧?

- -

如果我们将其变成异步函数呢?请尝试以下方法:

- -
async function hello() { return "Hello" };
-hello();
- -

哈。现在调用该函数会返回一个 promise。这是异步函数的特征之一 —— 它保证函数的返回值为 promise。

- -

你也可以创建一个异步函数表达式(参见 async function expression ),如下所示:

- -
let hello = async function() { return "Hello" };
-hello();
- -

你可以使用箭头函数:

- -
let hello = async () => { return "Hello" };
- -

这些都基本上是一样的。

- -

要实际使用promise完成时返回的值,我们可以使用.then()块,因为它返回的是 promise:

- -
hello().then((value) => console.log(value))
- -

甚至只是简写如

- -
hello().then(console.log)
- -

这就像我们在上一篇文章中看到的那样。

- -

将 async 关键字加到函数申明中,可以告诉它们返回的是 promise,而不是直接返回值。此外,它避免了同步函数为支持使用 await 带来的任何潜在开销。在函数声明为 async 时,JavaScript引擎会添加必要的处理,以优化你的程序。爽!

- -

await关键字

- -

当 await 关键字与异步函数一起使用时,它的真正优势就变得明显了 —— 事实上, await 只在异步函数里面才起作用。它可以放在任何异步的,基于 promise 的函数之前。它会暂停代码在该行上,直到 promise 完成,然后返回结果值。在暂停的同时,其他正在等待执行的代码就有机会执行了。

- -

您可以在调用任何返回Promise的函数时使用 await,包括Web API函数。

- -

这是一个简单的示例:

- -
async function hello() {
-  return greeting = await Promise.resolve("Hello");
-};
-
-hello().then(alert);
- -

当然,上面的示例不是很有用,但它确实展示了语法。让我们继续,看一个真实示例。

- -

 使用 async/await 重写 promise 代码

- -

让我们回顾一下我们在上一篇文章中简单的 fetch 示例:

- -
fetch('coffee.jpg')
-.then(response => response.blob())
-.then(myBlob => {
-  let objectURL = URL.createObjectURL(myBlob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-})
-.catch(e => {
-  console.log('There has been a problem with your fetch operation: ' + e.message);
-});
- -

到现在为止,你应该对 promises 及其工作方式有一个较好的理解。让我们将其转换为使用async / await看看它使事情变得简单了多少:

- -
async function myFetch() {
-  let response = await fetch('coffee.jpg');
-  let myBlob = await response.blob();
-
-  let objectURL = URL.createObjectURL(myBlob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-}
-
-myFetch()
-.catch(e => {
-  console.log('There has been a problem with your fetch operation: ' + e.message);
-});
- -

它使代码简单多了,更容易理解 —— 去除了到处都是 .then() 代码块!

- -

由于 async 关键字将函数转换为 promise,您可以重构以上代码 —— 使用 promise 和 await 的混合方式,将函数的后半部分抽取到新代码块中。这样做可以更灵活:

- -
async function myFetch() {
-  let response = await fetch('coffee.jpg');
-  return await response.blob();
-}
-
-myFetch().then((blob) => {
-  let objectURL = URL.createObjectURL(blob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-});
- -

您可以尝试自己输入示例,或运行我们的 live example (另请参阅source code)。

- -

它到底是如何工作的?

- -

您会注意到我们已经将代码封装在函数中,并且我们在 function 关键字之前包含了 async 关键字。这是必要的 –– 您必须创建一个异步函数来定义一个代码块,在其中运行异步代码; await 只能在异步函数内部工作。

- -

myFetch()函数定义中,您可以看到代码与先前的 promise 版本非常相似,但存在一些差异。不需要附加 .then() 代码块到每个promise-based方法的结尾,你只需要在方法调用前添加 await 关键字,然后把结果赋给变量。await 关键字使JavaScript运行时暂停于此行,允许其他代码在此期间执行,直到异步函数调用返回其结果。一旦完成,您的代码将继续从下一行开始执行。例如:

- -
let response = await fetch('coffee.jpg');
- -

解析器会在此行上暂停,直到当服务器返回的响应变得可用时。此时 fetch() 返回的 promise 将会完成(fullfilled),返回的 response 会被赋值给 response 变量。一旦服务器返回的响应可用,解析器就会移动到下一行,从而创建一个Blob。Blob这行也调用基于异步promise的方法,因此我们也在此处使用await。当操作结果返回时,我们将它从myFetch()函数中返回。

- -

这意味着当我们调用myFetch()函数时,它会返回一个promise,因此我们可以将.then()链接到它的末尾,在其中我们处理显示在屏幕上的blob

- -

你可能已经觉得“这真的很酷!”,你是对的 —— 用更少的.then()块来封装代码,同时它看起来很像同步代码,所以它非常直观。

- -

添加错误处理

- -

如果你想添加错误处理,你有几个选择。

- -

您可以将同步的 try...catch 结构和 async/await 一起使用 。此示例扩展了我们上面展示的第一个版本代码:

- -
async function myFetch() {
-  try {
-    let response = await fetch('coffee.jpg');
-    let myBlob = await response.blob();
-
-    let objectURL = URL.createObjectURL(myBlob);
-    let image = document.createElement('img');
-    image.src = objectURL;
-    document.body.appendChild(image);
-  } catch(e) {
-    console.log(e);
-  }
-}
-
-myFetch();
- -

catch() {} 代码块会接收一个错误对象 e ; 我们现在可以将其记录到控制台,它将向我们提供详细的错误消息,显示错误被抛出的代码中的位置。

- -

如果你想使用我们上面展示的第二个(重构)代码版本,你最好继续混合方式并将 .catch() 块链接到 .then() 调用的末尾,就像这样:

- -
async function myFetch() {
-  let response = await fetch('coffee.jpg');
-  return await response.blob();
-}
-
-myFetch().then((blob) => {
-  let objectURL = URL.createObjectURL(blob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-})
-.catch((e) =>
-  console.log(e)
-);
- -

这是因为 .catch() 块将捕获来自异步函数调用和promise链中的错误。如果您在此处使用了try/catch 代码块,则在调用 myFetch() 函数时,您仍可能会收到未处理的错误。

- -

您可以在GitHub上找到这两个示例:

- - - -

等待Promise.all()

- -

async / await 建立在 promises 之上,因此它与promises提供的所有功能兼容。这包括Promise.all() –– 你完全可以通过调用 await Promise.all() 将所有结果返回到变量中,就像同步代码一样。让我们再次回到上一篇中看到的例子。在单独的选项卡中打开它,以便您可以与下面显示的新版本进行比较和对比。

- -

将其转换为 async / await(请参阅 样例 和 源码),现在看起来像这样:

- -
async function fetchAndDecode(url, type) {
-  let response = await fetch(url);
-
-  let content;
-
-  if(type === 'blob') {
-    content = await response.blob();
-  } else if(type === 'text') {
-    content = await response.text();
-  }
-
-  return content;
-}
-
-async function displayContent() {
-  let coffee = fetchAndDecode('coffee.jpg', 'blob');
-  let tea = fetchAndDecode('tea.jpg', 'blob');
-  let description = fetchAndDecode('description.txt', 'text');
-
-  let values = await Promise.all([coffee, tea, description]);
-
-  let objectURL1 = URL.createObjectURL(values[0]);
-  let objectURL2 = URL.createObjectURL(values[1]);
-  let descText = values[2];
-
-  let image1 = document.createElement('img');
-  let image2 = document.createElement('img');
-  image1.src = objectURL1;
-  image2.src = objectURL2;
-  document.body.appendChild(image1);
-  document.body.appendChild(image2);
-
-  let para = document.createElement('p');
-  para.textContent = descText;
-  document.body.appendChild(para);
-}
-
-displayContent()
-.catch((e) =>
-  console.log(e)
-);
- -

可以看到 fetchAndDecode() 函数只进行了一丁点的修改就转换成了异步函数。请看Promise.all() 行:

- -
let values = await Promise.all([coffee, tea, description]);
- -

在这里,通过使用await,我们能够在三个promise的结果都可用的时候,放入values数组中。这看起来非常像同步代码。我们需要将所有代码封装在一个新的异步函数displayContent() 中,尽管没有减少很多代码,但能够将大部分代码从 .then() 代码块移出,使代码得到了简化,更易读。

- -

为了错误处理,我们在 displayContent() 调用中包含了一个 .catch() 代码块;这将处理两个函数中出现的错误。

- -
-

注意: 也可以在异步函数中使用同步 finally 代码块代替 .finally() 异步代码块,以显示操作如何进行的最终报告——您可以在我们的 live example (查看源代码)中看到这一点。

-
- -

async/await的缺陷

- -

了解Async/await是非常有用的,但还有一些缺点需要考虑。

- -

Async/await 让你的代码看起来是同步的,在某种程度上,也使得它的行为更加地同步。 await 关键字会阻塞其后的代码,直到promise完成,就像执行同步操作一样。它确实可以允许其他任务在此期间继续运行,但您自己的代码被阻塞。

- -

这意味着您的代码可能会因为大量await的promises相继发生而变慢。每个await都会等待前一个完成,而你实际想要的是所有的这些promises同时开始处理(就像我们没有使用async/await时那样)。

- -

有一种模式可以缓解这个问题——通过将 Promise 对象存储在变量中来同时开始它们,然后等待它们全部执行完毕。让我们看一些证明这个概念的例子。

- -

我们有两个可用的例子 —— slow-async-await.html(参见source code)和fast-async-await.html(参见source code)。它们都以自定义promise函数开始,该函数使用setTimeout() 调用伪造异步进程:

- -
function timeoutPromise(interval) {
-  return new Promise((resolve, reject) => {
-    setTimeout(function(){
-      resolve("done");
-    }, interval);
-  });
-};
- -

然后每个包含一个 timeTest() 异步函数,等待三个 timeoutPromise() 调用:

- -
async function timeTest() {
-  ...
-}
- -

每一个都以记录开始时间结束,查看 timeTest() promise 需要多长时间才能完成,然后记录结束时间并报告操作总共需要多长时间:

- -
let startTime = Date.now();
-timeTest().then(() => {
-  let finishTime = Date.now();
-  let timeTaken = finishTime - startTime;
-  alert("Time taken in milliseconds: " + timeTaken);
-})
- -

timeTest() 函数在每种情况下都不同。

- -

slow-async-await.html示例中,timeTest() 如下所示:

- -
async function timeTest() {
-  await timeoutPromise(3000);
-  await timeoutPromise(3000);
-  await timeoutPromise(3000);
-}
- -

在这里,我们直接等待所有三个timeoutPromise()调用,使每个调用3秒钟。后续的每一个都被迫等到最后一个完成 - 如果你运行第一个例子,你会看到弹出框报告的总运行时间大约为9秒。

- -

fast-async-await.html示例中,timeTest() 如下所示:

- -
async function timeTest() {
-  const timeoutPromise1 = timeoutPromise(3000);
-  const timeoutPromise2 = timeoutPromise(3000);
-  const timeoutPromise3 = timeoutPromise(3000);
-
-  await timeoutPromise1;
-  await timeoutPromise2;
-  await timeoutPromise3;
-}
- -

在这里,我们将三个Promise对象存储在变量中,这样可以同时启动它们关联的进程。

- -

接下来,我们等待他们的结果 - 因为promise都在基本上同时开始处理,promise将同时完成;当您运行第二个示例时,您将看到弹出框报告总运行时间仅超过3秒!

- -

您必须仔细测试您的代码,并在性能开始受损时牢记这一点。

- -

另一个小小的不便是你必须将等待执行的promise封装在异步函数中。

- -

Async/await 的类方法

- -

最后值得一提的是,我们可以在类/对象方法前面添加async,以使它们返回promises,并await它们内部的promises。查看 ES class code we saw in our object-oriented JavaScript article,然后查看使用异步方法的修改版本:

- -
class Person {
-  constructor(first, last, age, gender, interests) {
-    this.name = {
-      first,
-      last
-    };
-    this.age = age;
-    this.gender = gender;
-    this.interests = interests;
-  }
-
-  async greeting() {
-    return await Promise.resolve(`Hi! I'm ${this.name.first}`);
-  };
-
-  farewell() {
-    console.log(`${this.name.first} has left the building. Bye for now!`);
-  };
-}
-
-let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);
- -

第一个实例方法可以使用如下:

- -
han.greeting().then(console.log);
- -

浏览器的支持

- -

决定是否使用 async/await 时的一个考虑因素是支持旧浏览器。它们适用于大多数浏览器的现代版本,与promise相同; 主要的支持问题存在于Internet Explorer和Opera Mini。

- -

如果你想使用async/await但是担心旧的浏览器支持,你可以考虑使用BabelJS库 —— 这允许你使用最新的JavaScript编写应用程序,让Babel找出用户浏览器需要的更改。在遇到不支持async/await 的浏览器时,Babel的 polyfill 可以自动提供适用于旧版浏览器的实现。

- -

总结

- -

async/await提供了一种很好的,简化的方法来编写更易于阅读和维护的异步代码。即使浏览器支持在撰写本文时比其他异步代码机制更受限制,但无论是现在还是将来,都值得学习和考虑使用。

- -

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous/Choosing_the_right_approach", "Learn/JavaScript/Asynchronous")}}

- -

本章内容

- - diff --git a/files/zh-cn/learn/javascript/asynchronous/choosing_the_right_approach/index.html b/files/zh-cn/learn/javascript/asynchronous/choosing_the_right_approach/index.html deleted file mode 100644 index 4241740479..0000000000 --- a/files/zh-cn/learn/javascript/asynchronous/choosing_the_right_approach/index.html +++ /dev/null @@ -1,524 +0,0 @@ ---- -title: 选择正确的方法 -slug: Learn/JavaScript/Asynchronous/Choosing_the_right_approach -translation_of: Learn/JavaScript/Asynchronous/Choosing_the_right_approach -original_slug: learn/JavaScript/异步/Choosing_the_right_approach ---- -
{{LearnSidebar}}
- -
{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}
- -

为了完成这个模块,我们将简要讨论之前章节谈论过编码技术和功能,看看你应该使用哪一个,并提供适当的建议和提醒。随着时间的推移,我们可能会添加到此资源中。

- - - - - - - - - - - - -
预备条件:基本的计算机素养,对JavaScript基础知识的合理理解。
目标:能够在使用不同的异步编程技术时做出合理的选择。
- -

异步回调

- -

通常在旧式API中找到,涉及将函数作为参数传递给另一个函数,然后在异步操作完成时调用该函数,以便回调可以依次对结果执行某些操作。这是promise的前身;它不那么高效或灵活。仅在必要时使用。

- - - - - - - - - - - - - - - - - - - -
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoYes (recursive callbacks)Yes (nested callbacks)No
- -

代码示例

- -

通过XMLHttpRequest API加载资源的示例(run it live,并查看see the source):

- -
function loadAsset(url, type, callback) {
-  let xhr = new XMLHttpRequest();
-  xhr.open('GET', url);
-  xhr.responseType = type;
-
-  xhr.onload = function() {
-    callback(xhr.response);
-  };
-
-  xhr.send();
-}
-
-function displayImage(blob) {
-  let objectURL = URL.createObjectURL(blob);
-
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-}
-
-loadAsset('coffee.jpg', 'blob', displayImage);
- -

缺陷

- - - -

浏览器兼容性

- -

非常好的一般支持,尽管API中回调的确切支持取决于特定的API。有关更具体的支持信息,请参阅您正在使用的API的参考文档。

- -

更多信息

- - - -

setTimeout()

- -

setTimeout() 是一种允许您在经过任意时间后运行函数的方法

- - - - - - - - - - - - - - - - - -
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
YesYes (recursive timeouts)Yes (nested timeouts)No
- -

代码示例

- -

这里浏览器将在执行匿名函数之前等待两秒钟,然后将显示警报消息(see it running livesee the source code):

- -
let myGreeting = setTimeout(function() {
-  alert('Hello, Mr. Universe!');
-}, 2000)
- -

缺陷

- -

您可以使用递归的setTimeout()调用以类似于setInterval()的方式重复运行函数,使用如下代码:

- -
let i = 1;
-setTimeout(function run() {
-  console.log(i);
-  i++;
-
-  setTimeout(run, 100);
-}, 100);
- -

递归setTimeout()setInterval()之间存在差异:

- - - -

当你的代码有可能比你分配的时间间隔更长时间运行时,最好使用递归的setTimeout() ––这将使执行之间的时间间隔保持不变,无论代码执行多长时间,你不会得到错误。

- -

浏览器兼容性

- -

{{Compat("api.WindowOrWorkerGlobalScope.setTimeout")}}

- -

更多信息

- - - -

setInterval()

- -

setInterval()函数允许重复执行一个函数,并设置时间间隔。不如requestAnimationFrame()有效率,但允许您选择运行速率/帧速率。

- - - - - - - - - - - - - - - - - -
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoYesNo (unless it is the same one)No
- -

代码示例

- -

以下函数创建一个新的Date()对象,使用toLocaleTimeString()从中提取时间字符串,然后在UI中显示它。然后我们使用setInterval()每秒运行一次,创建每秒更新一次的数字时钟的效果(see this livesee the source):

- -
function displayTime() {
-   let date = new Date();
-   let time = date.toLocaleTimeString();
-   document.getElementById('demo').textContent = time;
-}
-
-const createClock = setInterval(displayTime, 1000);
- -

缺陷

- - - -

浏览器兼容性

- -

{{Compat("api.WindowOrWorkerGlobalScope.setInterval")}}

- -

更多信息

- - - -

requestAnimationFrame()

- -

requestAnimationFrame()是一种允许您以给定当前浏览器/系统的最佳帧速率重复且高效地运行函数的方法。除非您需要特定的速率帧,否则您应该尽可能使用它而不要去使用setInterval()/recursive setTimeout()

- - - - - - - - - - - - - - - - - -
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoYesNo (unless it is the same one)No
- -

代码示例

- -

一个简单的动画旋转器;你可以查看example live on GitHub(参见 source code ):

- -
const spinner = document.querySelector('div');
-let rotateCount = 0;
-let startTime = null;
-let rAF;
-
-function draw(timestamp) {
-  if(!startTime) {
-    startTime = timestamp;
-  }
-
-  let rotateCount = (timestamp - startTime) / 3;
-
-  spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
-
-  if(rotateCount > 359) {
-    rotateCount = 0;
-  }
-
-  rAF = requestAnimationFrame(draw);
-}
-
-draw();
- -

缺陷

- - - -

浏览器兼容性

- -

{{Compat("api.Window.requestAnimationFrame")}}

- -

更多信息

- - - -

Promises

- -

Promises 是一种JavaScript功能,允许您运行异步操作并等到它完全完成后再根据其结果运行另一个操作。 Promise是现代异步JavaScript的支柱。

- - - - - - - - - - - - - - - - - -
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoNoYesSee Promise.all(), below
- -

代码示例

- -

以下代码从服务器获取图像并将其显示在 {{htmlelement("img")}} 元素中;(see it live alsothe source code):

- -
fetch('coffee.jpg')
-.then(response => response.blob())
-.then(myBlob => {
-  let objectURL = URL.createObjectURL(myBlob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-})
-.catch(e => {
-  console.log('There has been a problem with your fetch operation: ' + e.message);
-});
- -

缺陷

- -

Promise链可能很复杂,难以解析。如果你嵌套了许多promises,你最终可能会遇到类似的麻烦来回调地狱。例如:

- -
remotedb.allDocs({
-  include_docs: true,
-  attachments: true
-}).then(function (result) {
-  var docs = result.rows;
-  docs.forEach(function(element) {
-    localdb.put(element.doc).then(function(response) {
-      alert("Pulled doc with id " + element.doc._id + " and added to local db.");
-    }).catch(function (err) {
-      if (err.name == 'conflict') {
-        localdb.get(element.doc._id).then(function (resp) {
-          localdb.remove(resp._id, resp._rev).then(function (resp) {
-// et cetera...
- -

最好使用promises的链功能,这样使用更平顺,更易于解析的结构:

- -
remotedb.allDocs(...).then(function (resultOfAllDocs) {
-  return localdb.put(...);
-}).then(function (resultOfPut) {
-  return localdb.get(...);
-}).then(function (resultOfGet) {
-  return localdb.put(...);
-}).catch(function (err) {
-  console.log(err);
-});
- -

乃至:

- -
remotedb.allDocs(...)
-.then(resultOfAllDocs => {
-  return localdb.put(...);
-})
-.then(resultOfPut => {
-  return localdb.get(...);
-})
-.then(resultOfGet => {
-  return localdb.put(...);
-})
-.catch(err => console.log(err));
- -

这涵盖了很多基础知识。对于更完整的论述,请参阅诺兰劳森的We have a problem with promises

- -

浏览器兼容性

- -

{{Compat("javascript.builtins.Promise")}}

- -

更多信息

- - - -

Promise.all()

- -

一种JavaScript功能,允许您等待多个promises完成,然后根据所有其他promises的结果运行进一步的操作。

- - - - - - - - - - - - - - - - - -
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoNoNoYes
- -

代码示例

- -

以下示例从服务器获取多个资源,并使用Promise.all()等待所有资源可用,然后显示所有这些资源––  see it live,并查看source code

- -
function fetchAndDecode(url, type) {
-  // Returning the top level promise, so the result of the entire chain is returned out of the function
-  return fetch(url).then(response => {
-    // Depending on what type of file is being fetched, use the relevant function to decode its contents
-    if(type === 'blob') {
-      return response.blob();
-    } else if(type === 'text') {
-      return response.text();
-    }
-  })
-  .catch(e => {
-    console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
-  });
-}
-
-// Call the fetchAndDecode() method to fetch the images and the text, and store their promises in variables
-let coffee = fetchAndDecode('coffee.jpg', 'blob');
-let tea = fetchAndDecode('tea.jpg', 'blob');
-let description = fetchAndDecode('description.txt', 'text');
-
-// Use Promise.all() to run code only when all three function calls have resolved
-Promise.all([coffee, tea, description]).then(values => {
-  console.log(values);
-  // Store each value returned from the promises in separate variables; create object URLs from the blobs
-  let objectURL1 = URL.createObjectURL(values[0]);
-  let objectURL2 = URL.createObjectURL(values[1]);
-  let descText = values[2];
-
-  // Display the images in <img> elements
-  let image1 = document.createElement('img');
-  let image2 = document.createElement('img');
-  image1.src = objectURL1;
-  image2.src = objectURL2;
-  document.body.appendChild(image1);
-  document.body.appendChild(image2);
-
-  // Display the text in a paragraph
-  let para = document.createElement('p');
-  para.textContent = descText;
-  document.body.appendChild(para);
-});
- -

缺陷

- - - -

浏览器兼容性

- -

{{Compat("javascript.builtins.Promise.all")}}

- -

更多信息

- - - -

Async/await

- -

构造在promises之上的语法糖,允许您使用更像编写同步回调代码的语法来运行异步操作。

- - - - - - - - - - - - - - - - - -
Useful for...
Single delayed operationRepeating operationMultiple sequential operationsMultiple simultaneous operations
NoNoYesYes (in combination with Promise.all())
- -

代码示例

- -

以下示例是我们之前看到的简单承诺示例的重构,该示例获取并显示图像,使用async / await编写(see it live,并查看source code):

- -
async function myFetch() {
-  let response = await fetch('coffee.jpg');
-  let myBlob = await response.blob();
-
-  let objectURL = URL.createObjectURL(myBlob);
-  let image = document.createElement('img');
-  image.src = objectURL;
-  document.body.appendChild(image);
-}
-
-myFetch();
- -

缺陷

- - - -

浏览器兼容性

- -

{{Compat("javascript.statements.async_function")}}

- -

更多信息

- - - -

{{PreviousMenu("Learn/JavaScript/Asynchronous/Async_await", "Learn/JavaScript/Asynchronous")}}

- -

本章内容

- - diff --git a/files/zh-cn/learn/javascript/asynchronous/concepts/index.html b/files/zh-cn/learn/javascript/asynchronous/concepts/index.html deleted file mode 100644 index 757eee777c..0000000000 --- a/files/zh-cn/learn/javascript/asynchronous/concepts/index.html +++ /dev/null @@ -1,163 +0,0 @@ ---- -title: 通用异步编程概念 -slug: Learn/JavaScript/Asynchronous/Concepts -tags: - - JavaScript - - Promises - - Threads - - 学习 - - 异步 - - 阻塞 -translation_of: Learn/JavaScript/Asynchronous/Concepts -original_slug: learn/JavaScript/异步/概念 ---- -
{{LearnSidebar}}{{NextMenu("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous")}}
- -

在本文中,我们将介绍与异步编程相关的一些重要概念,以及它们在浏览器和JavaScript里的体现。在学习本系列的其他文章之前,你应该先理解这些概念。

- - - - - - - - - - - - -
预备条件:拥有基本的计算机知识,对JavaScript原理有一定了解。
目标:理解异步编程的基本概念,以及异步编程在浏览器和JavaScript里面的表现。
- -

异步?

- -

通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕.

- -

Mac 用户有时会经历过这种旋转的彩虹光标(常称为沙滩球),操作系统通过这个光标告诉用户:“现在运行的程序正在等待其他的某一件事情完成,才能继续运行,都这么长的时间了,你一定在担心到底发生了什么事情”。

- -

multi-colored macos beachball busy spinner

- -

这是令人沮丧的体验,没有充分利用计算机的计算能力 — 尤其是在计算机普遍都有多核CPU的时代,坐在那里等待毫无意义,你完全可以在另一个处理器内核上干其他的工作,同时计算机完成耗时任务的时候通知你。这样你可以同时完成其他工作,这就是异步编程的出发点。你正在使用的编程环境(就web开发而言,编程环境就是web浏览器)负责为你提供异步运行此类任务的API。

- -

产生阻塞的代码

- -

异步技术非常有用,特别是在web编程。当浏览器里面的一个web应用进行密集运算还没有把控制权返回给浏览器的时候,整个浏览器就像冻僵了一样,这叫做阻塞;这时候浏览器无法继续处理用户的输入并执行其他任务,直到web应用交回处理器的控制。

- -

我们来看一些阻塞的例子。

- -

例子: simple-sync.html  (see it running live), 在按钮上添加了一个事件监听器,当按钮被点击,它就开始运行一个非常耗时的任务(计算1千万个日期,并在console里显示最后一个日期),然后在DOM里面添加一个段落:

- -
const btn = document.querySelector('button');
-btn.addEventListener('click', () => {
-  let myDate;
-  for(let i = 0; i < 10000000; i++) {
-    let date = new Date();
-    myDate = date
-  }
-
-  console.log(myDate);
-
-  let pElem = document.createElement('p');
-  pElem.textContent = 'This is a newly-added paragraph.';
-  document.body.appendChild(pElem);
-});
- -

运行这个例子的时候,打开JavaScript console,然后点击按钮 — 你会注意到,直到日期的运算结束,最后一个日期在console上显示出来,段落才会出现在网页上。代码按照源代码的顺序执行,只有前面的代码结束运行,后面的代码才会执行。

- -
-

Note: 这个例子不现实:在实际情况中一般不会发生,没有谁会计算1千万次日期,它仅仅提供一个非常直观的体验.

-
- -

第二个例子, simple-sync-ui-blocking.html (see it live), 我们模拟一个在现实的网页可能遇到的情况:因为渲染UI而阻塞用户的互动,这个例子有2个按钮:

- - - -
function expensiveOperation() {
-  for(let i = 0; i < 1000000; i++) {
-    ctx.fillStyle = 'rgba(0,0,255, 0.2)';
-    ctx.beginPath();
-    ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false);
-    ctx.fill()
-  }
-}
-
-fillBtn.addEventListener('click', expensiveOperation);
-
-alertBtn.addEventListener('click', () =>
-  alert('You clicked me!')
-);
- -

如果你点击第一个按钮,然后快速点击第二个,会注意到alert消息并没有出现,只有等到圆圈都画完以后,才会出现:因为第一个操作没有完成之前阻塞了第二个操作的运行.

- -
-

Note: 当然,这个例子也很丑陋,因为我们只是在模拟阻塞效果。但在现实中,这是一个很常见的问题。开发人员们一直在努力缓解它。

-
- -

为什么是这样? 答案是:JavaScript一般来说是单线程的(single threaded接着我们来介绍线程的概念。

- -

线程

- -

一个线程是一个基本的处理过程,程序用它来完成任务。每个线程一次只能执行一个任务:

- -
Task A --> Task B --> Task C
- -

每个任务顺序执行,只有前面的结束了,后面的才能开始。

- -

正如我们之前所说,现在的计算机大都有多个内核(core),因此可以同时执行多个任务。支持多线程的编程语言可以使用计算机的多个内核,同时完成多个任务:

- -
Thread 1: Task A --> Task B
-Thread 2: Task C --> Task D
- -

JavaScript 是单线程的

- -

JavaScript 传统上是单线程的。即使有多个内核,也只能在单一线程上运行多个任务,此线程称为主线程(main thread)。我们上面的例子运行如下:

- -
Main thread: Render circles to canvas --> Display alert()
- -

经过一段时间,JavaScript获得了一些工具来帮助解决这种问题。通过 Web workers 可以把一些任务交给一个名为worker的单独的线程,这样就可以同时运行多个JavaScript代码块。一般来说,用一个worker来运行一个耗时的任务,主线程就可以处理用户的交互(避免了阻塞)

- -
Main thread: Task A --> Task C
-Worker thread: Expensive task B
- -

记住这些,请查看simple-sync-worker.html (see it running live) , 再次打开浏览器的JavaScript 控制台。这个例子重写了前例:在一个单独的worker线程中计算一千万次日期,你再点击按钮,现在浏览器可以在日期计算完成之前显示段落,阻塞消失了。

- -

异步代码

- -

web workers相当有用,但是他们确实也有局限。主要的一个问题是他们不能访问 {{Glossary("DOM")}} — 不能让一个worker直接更新UI。我们不能在worker里面渲染1百万个蓝色圆圈,它基本上只能做算数的苦活。

- -

其次,虽然在worker里面运行的代码不会产生阻塞,但是基本上还是同步的。当一个函数依赖于几个在它之前运行的过程的结果,这就会成为问题。考虑下面的情况:

- -
Main thread: Task A --> Task B
- -

在这种情况下,比如说Task A 正在从服务器上获取一个图片之类的资源,Task B 准备在图片上加一个滤镜。如果开始运行Task A 后立即尝试运行Task B,你将会得到一个错误,因为图像还没有获取到。

- -
Main thread: Task A --> Task B --> |Task D|
-Worker thread: Task C -----------> |      |
- -

在这种情况下,假设Task D 要同时使用 Task B 和Task C的结果,如果我们能保证这两个结果同时提供,程序可能正常运行,但是这不太可能。如果Task D 尝试在其中一个结果尚未可用的情况下就运行,程序就会抛出一个错误。

- -

为了解决这些问题,浏览器允许我们异步运行某些操作。像Promises 这样的功能就允许让一些操作运行 (比如:从服务器上获取图片),然后等待直到结果返回,再运行其他的操作:

- -
Main thread: Task A                   Task B
-    Promise:      |__async operation__|
- -

由于操作发生在其他地方,因此在处理异步操作的时候,主线程不会被阻塞。

- -

我们将在下一篇文章中开始研究如何编写异步代码。 非常令人兴奋,对吧? 继续阅读!

- -

总结

- -

围绕异步编程领域,现代软件设计正在加速旋转,就为了让程序在一个时间内做更多的事情。当你使用更新更强大的API时,你会发现在更多的情况下,使用异步编程是唯一的途径。以前写异步代码很困难,现在也需要你来适应,但是已经变容易了很多。在余下的部分,我们将进一步探讨异步代码的重要性,以及如何设计代码来防止前面已经提到过的问题。

- -

模块大纲

- - diff --git a/files/zh-cn/learn/javascript/asynchronous/timeouts_and_intervals/index.html b/files/zh-cn/learn/javascript/asynchronous/timeouts_and_intervals/index.html deleted file mode 100644 index 0819a5aff0..0000000000 --- a/files/zh-cn/learn/javascript/asynchronous/timeouts_and_intervals/index.html +++ /dev/null @@ -1,618 +0,0 @@ ---- -title: '合作异步JavaScript: 超时和间隔' -slug: Learn/JavaScript/Asynchronous/Timeouts_and_intervals -translation_of: Learn/JavaScript/Asynchronous/Timeouts_and_intervals -original_slug: learn/JavaScript/异步/超时和间隔 ---- -
{{LearnSidebar}}
- - - -
{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}
- - - -

在这里,我们将讨论传统的JavaScript方法,这些方法可以在一段时间或一段规则间隔(例如,每秒固定的次数)之后,以异步方式运行代码,并讨论它们的用处,以及它们的固有问题。

- - - - - - - - - - - - -
预备条件:基本的计算机知识,对JavaScript基本原理有较好的理解。
目标:了解异步循环和间隔及其用途。
- -

介绍

- -

很长一段时间以来,web平台为JavaScript程序员提供了许多函数,这些函数允许您在一段时间间隔过后异步执行代码,或者重复异步执行代码块,直到您告诉它停止为止。这些都是:

- -
-
setTimeout()
-
在指定的时间后执行一段代码.
-
setInterval()
-
以固定的时间间隔,重复运行一段代码.
-
requestAnimationFrame()
-
setInterval()的现代版本;在浏览器下一次重新绘制显示之前执行指定的代码块,从而允许动画在适当的帧率下运行,而不管它在什么环境中运行.
-
- -

这些函数设置的异步代码实际上在主线程上运行(在其指定的计时器过去之后)。

- -

在 setTimeout() 调用执行之前或 setInterval() 迭代之间可以(并且经常会)运行其他代码。根据这些操作的处理器密集程度,它们可以进一步延迟异步代码,因为任何异步代码仅在主线程可用后才执行(换句话说,当调用栈为空时)。在阅读本文时,您将学到更多关于此问题的信息。

- -

无论如何,这些函数用于在web站点或应用程序上运行不间断的动画和其他后台处理。在下面的部分中,我们将向您展示如何使用它们。

- -

setTimeout()

- -

正如前述, setTimeout() 在指定的时间后执行一段特定代码. 它需要如下参数:

- - - -
-

Note: 指定的时间(或延迟)不能保证在指定的确切时间之后执行,而是最短的延迟执行时间。在主线程上的堆栈为空之前,传递给这些函数的回调将无法运行。

- -

结果,像 setTimeout(fn, 0) 这样的代码将在堆栈为空时立即执行,而不是立即执行。如果执行类似 setTimeout(fn, 0) 之类的代码,之后立即运行从 1 到 100亿 的循环之后,回调将在几秒后执行。 

-
- -

在下面的示例中,浏览器将在执行匿名函数之前等待两秒钟,然后显示alert消息 (see it running live, and see the source code):

- -
let myGreeting = setTimeout(function() {
-  alert('Hello, Mr. Universe!');
-}, 2000)
- -

我们指定的函数不必是匿名的。我们可以给函数一个名称,甚至可以在其他地方定义它,并将函数引用传递给 setTimeout() 。以下两个版本的代码片段相当于第一个版本:

- -
// With a named function
-let myGreeting = setTimeout(function sayHi() {
-  alert('Hello, Mr. Universe!');
-}, 2000)
-
-// With a function defined separately
-function sayHi() {
-  alert('Hello Mr. Universe!');
-}
-
-let myGreeting = setTimeout(sayHi, 2000);
- -

例如,如果我们有一个函数既需要从超时调用,也需要响应某个事件,那么这将非常有用。此外它也可以帮助保持代码整洁,特别是当超时回调超过几行代码时。

- -

setTimeout() 返回一个标志符变量用来引用这个间隔,可以稍后用来取消这个超时任务,下面就会学到 {{anch("Clearing timeouts")}} 。

- -

传递参数给setTimeout() 

- -

我们希望传递给setTimeout()中运行的函数的任何参数,都必须作为列表末尾的附加参数传递给它。

- -

例如,我们可以重构之前的函数,这样无论传递给它的人的名字是什么,它都会向它打招呼:

- -
function sayHi(who) {
-  alert('Hello ' + who + '!');
-}
- -

人名可以通过第三个参数传进 setTimeout()

- -
let myGreeting = setTimeout(sayHi, 2000, 'Mr. Universe');
- -

清除超时

- -

最后,如果创建了 timeout,您可以通过调用clearTimeout(),将setTimeout()调用的标识符作为参数传递给它,从而在超时运行之前取消。要取消上面的超时,你需要这样做:

- -
clearTimeout(myGreeting);
- -
-

注意: 请参阅 greeter-app.html 以获得稍微复杂一点的演示,该演示允许您在表单中设置要打招呼的人的姓名,并使用单独的按钮取消问候语(see the source code also)。

-
- -

setInterval()

- -

当我们需要在一段时间之后运行一次代码时,setTimeout()可以很好地工作。但是当我们需要反复运行代码时会发生什么,例如在动画的情况下?

- -

这就是setInterval()的作用所在。这与setTimeout()的工作方式非常相似,只是作为第一个参数传递给它的函数,重复执行的时间不少于第二个参数给出的毫秒数,而不是一次执行。您还可以将正在执行的函数所需的任何参数作为 setInterval() 调用的后续参数传递。

- -

让我们看一个例子。下面的函数创建一个新的Date()对象,使用toLocaleTimeString()从中提取一个时间字符串,然后在UI中显示它。然后,我们使用setInterval()每秒运行该函数一次,创建一个每秒更新一次的数字时钟的效果。

- -

(see this live, and also see the source):

- -
function displayTime() {
-   let date = new Date();
-   let time = date.toLocaleTimeString();
-   document.getElementById('demo').textContent = time;
-}
-
-const createClock = setInterval(displayTime, 1000);
- -

setTimeout()一样, setInterval() 返回一个确定的值,稍后你可以用它来取消间隔任务。

- -

清除intervals

- -

setInterval()永远保持运行任务,除非我们做点什么——我们可能会想阻止这样的任务,否则当浏览器无法完成任何进一步的任务时我们可能得到错误, 或者动画处理已经完成了。我们可以用与停止超时相同的方法来实现这一点——通过将setInterval()调用返回的标识符传递给clearInterval()函数:

- -
const myInterval = setInterval(myFunction, 2000);
-
-clearInterval(myInterval);
- -

主动学习:创建秒表!

- -

话虽如此,我们还是要给你一个挑战。以我们的setInterval-clock.html为例,修改它以创建您自己的简单秒表。

- -

你要像前面一样显示时间,但是在这里,你需要:

- - - -

提示:

- - - -
-

Note: 如果您在操作过程有困难,请参考 see it runing live (see the source code also).

-
- -

关于 setTimeout() 和 setInterval() 需要注意的几点

- -

当使用 setTimeout()setInterval()的时候,有几点需要额外注意。 现在让我们回顾一下:

- -

递归的timeouts

- -

还有另一种方法可以使用setTimeout():我们可以递归调用它来重复运行相同的代码,而不是使用setInterval()

- -

下面的示例使用递归setTimeout()每100毫秒运行传递来的函数:

- -
let i = 1;
-
-setTimeout(function run() {
-  console.log(i);
-  i++;
-  setTimeout(run, 100);
-}, 100);
- -

将上面的示例与下面的示例进行比较 ––这使用 setInterval() 来实现相同的效果:

- -
let i = 1;
-
-setInterval(function run() {
-  console.log(i);
-  i++
-}, 100);
- -

递归setTimeout()和setInterval()有何不同?

- -

上述代码的两个版本之间的差异是微妙的。

- - - -

当你的代码有可能比你分配的时间间隔,花费更长时间运行时,最好使用递归的 setTimeout() - 这将使执行之间的时间间隔保持不变,无论代码执行多长时间,你不会得到错误。

- -

立即超时

- -

使用0用作setTimeout()的回调函数会立刻执行,但是在主线程代码运行之后执行。

- -

举个例子,下面的代码(see it live) 输出一个包含警报的"Hello",然后在您点击第一个警报的OK之后立即弹出“world”。

- -
setTimeout(function() {
-  alert('World');
-}, 0);
-
-alert('Hello');
- -

如果您希望设置一个代码块以便在所有主线程完成运行后立即运行,这将很有用。将其放在异步事件循环中,这样它将随后直接运行。

- -

使用 clearTimeout() or clearInterval()清除

- -

clearTimeout()clearInterval() 都使用相同的条目列表进行清除。有趣的是,这意味着你可以使用任一一种方法来清除 setTimeout() 和 setInterval()。

- -

但为了保持一致性,你应该使用 clearTimeout() 来清除 setTimeout() 条目,使用 clearInterval() 来清除 setInterval() 条目。 这样有助于避免混乱。

- -

requestAnimationFrame()

- -

requestAnimationFrame() 是一个专门的循环函数,旨在浏览器中高效运行动画。它基本上是现代版本的setInterval() —— 它在浏览器重新加载显示内容之前执行指定的代码块,从而允许动画以适当的帧速率运行,不管其运行的环境如何。

- -

它是针对setInterval() 遇到的问题创建的,比如 setInterval()并不是针对设备优化的帧率运行,有时会丢帧。还有即使该选项卡不是活动的选项卡或动画滚出页面等问题 。

- -

(在CreativeJS上了解有关此内容的更多信息).

- -
-

注意: 你可以在课程中其他地方找到requestAnimationFrame() 的使用范例—参见 Drawing graphics, 和 Object building practice

-
- -

该方法将重新加载页面之前要调用的回调函数作为参数。这是您将看到的常见表达:

- -
function draw() {
-   // Drawing code goes here
-   requestAnimationFrame(draw);
-}
-
-draw();
- -

这个想法是要定义一个函数,在其中更新动画 (例如,移动精灵,更新乐谱,刷新数据等),然后调用它来开始这个过程。在函数的末尾,以 requestAnimationFrame() 传递的函数作为参数进行调用,这指示浏览器在下一次显示重新绘制时再次调用该函数。然后这个操作连续运行, 因为requestAnimationFrame() 是递归调用的。

- -
-

注意: 如果要执行某种简单的常规DOM动画, CSS 动画 可能更快,因为它们是由浏览器的内部代码计算而不是JavaScript直接计算的。但是,如果您正在做一些更复杂的事情,并且涉及到在DOM中不能直接访问的对象(such as 2D Canvas API or WebGL objects), requestAnimationFrame() 在大多数情况下是更好的选择。

-
- -

你的动画跑得有多快?

- -

动画的平滑度直接取决于动画的帧速率,并以每秒帧数(fps)为单位进行测量。这个数字越高,动画看起来就越平滑。

- -

由于大多数屏幕的刷新率为60Hz,因此在使用web浏览器时,可以达到的最快帧速率是每秒60帧(FPS)。然而,更多的帧意味着更多的处理,这通常会导致卡顿和跳跃-也称为丢帧或跳帧。

- -

如果您有一个刷新率为60Hz的显示器,并且希望达到60fps,则大约有16.7毫秒(1000/60)来执行动画代码来渲染每个帧。这提醒我们,我们需要注意每次通过动画循环时要运行的代码量。

- -

requestAnimationFrame() 总是试图尽可能接近60帧/秒的值,当然有时这是不可能的如果你有一个非常复杂的动画,你是在一个缓慢的计算机上运行它,你的帧速率将更少。requestAnimationFrame() 会尽其所能利用现有资源提升帧速率。

- -

 requestAnimationFrame() 与 setInterval() 和 setTimeout()有什么不同?

- -

让我们进一步讨论一下 requestAnimationFrame() 方法与前面介绍的其他方法的区别. 下面让我们看一下代码:

- -
function draw() {
-   // Drawing code goes here
-   requestAnimationFrame(draw);
-}
-
-draw();
- -

现在让我们看看如何使用setInterval():

- -
function draw() {
-   // Drawing code goes here
-}
-
-setInterval(draw, 17);
- -

如前所述,我们没有为requestAnimationFrame();指定时间间隔;它只是在当前条件下尽可能快速平稳地运行它。如果动画由于某些原因而处于屏幕外浏览器也不会浪费时间运行它。

- -

 另一方面setInterval()需要指定间隔。我们通过公式1000毫秒/60Hz得出17的最终值,然后将其四舍五入。四舍五入是一个好主意,浏览器可能会尝试运行动画的速度超过60fps,它不会对动画的平滑度有任何影响。如前所述,60Hz是标准刷新率。

- -

包括时间戳

- -

传递给 requestAnimationFrame() 函数的实际回调也可以被赋予一个参数(一个时间戳值),表示自 requestAnimationFrame() 开始运行以来的时间。这是很有用的,因为它允许您在特定的时间以恒定的速度运行,而不管您的设备有多快或多慢。您将使用的一般模式如下所示:

- -
let startTime = null;
-
-function draw(timestamp) {
-    if(!startTime) {
-      startTime = timestamp;
-    }
-
-   currentTime = timestamp - startTime;
-
-   // Do something based on current time
-
-   requestAnimationFrame(draw);
-}
-
-draw();
- -

浏览器支持

- -

 与setInterval()setTimeout() 相比最近的浏览器支持requestAnimationFrame()

- -

requestAnimationFrame().在Internet Explorer 10及更高版本中可用。因此,除非您的代码需要支持旧版本的IE,否则没有什么理由不使用requestAnimationFrame() 。

- -

一个简单的例子

- -

学习上述理论已经足够了,下面让我们仔细研究并构建自己的requestAnimationFrame() 示例。我们将创建一个简单的“旋转器动画”(spinner animation),即当应用程序忙于连接到服务器时可能会显示的那种动画。

- -
-

注意: 一般来说,像以下例子中如此简单的动画应用CSS动画来实现,这里使用requestAnimationFrame()只是为了帮助解释其用法。requestAnimationFrame()正常应用于如逐帧更新游戏画面这样的复杂动画。

-
- -
    -
  1. -

    首先, 下载我们的网页模板

    -
  2. -
  3. -

    放置一个空的 {{htmlelement("div")}} 元素进入 {{htmlelement("body")}}, 然后在其中加入一个 ↻ 字符.这是一个将循环的字符将在我们的例子中作为我们的旋转器(spinner)。

    -
  4. -
  5. -

    用任何你喜欢的方法应用下述的CSS到HTML模板中。这些在页面上设置了一个红色背景,将<body>的高度设置为100%<html>的高度,并将<div>水平和竖直居中。

    - -
    html {
    -  background-color: white;
    -  height: 100%;
    -}
    -
    -body {
    -  height: inherit;
    -  background-color: red;
    -  margin: 0;
    -  display: flex;
    -  justify-content: center;
    -  align-items: center;
    -}
    -
    -div {
    -  display: inline-block;
    -  font-size: 10rem;
    -}
    -
  6. -
  7. -

    插入一个 {{htmlelement("script")}}元素在 </body> 标签之上。

    -
  8. -
  9. -

    插入下述的JavaScript在你的 <script> 元素中。这里我们存储了一个<div>的引用在一个常量中,设置rotateCount变量为 0, 设置一个未初始化的变量之后将会用作容纳一个requestAnimationFrame() 的调用, 然后设置一个 startTime 变量为 null,它之后将会用作存储 requestAnimationFrame() 的起始时间.。

    - -
    const spinner = document.querySelector('div');
    -let rotateCount = 0;
    -let rAF;
    -let startTime = null;
    -
  10. -
  11. -

    在之前的代码下面, 插入一个 draw() 函数将被用作容纳我们的动画代码,并且包含了 时间戳 参数。

    - -
    function draw(timestamp) {
    -
    -}
    -
  12. -
  13. -

    draw()中, 加入下述的几行。 如果起始时间还没有被赋值的话,将timestamp 传给它(这只将发生在循环中的第一步)。 并赋值给rotateCount ,以旋转 旋转器(spinning)(此处取(当前时间戳 – 起始时间戳) / 3,以免它转得太快):

    - -
      if (!startTime) {
    -   startTime = timestamp;
    -  }
    -
    -  rotateCount = (timestamp - startTime) / 3;
    -
    -
  14. -
  15. -

    draw()内我们刚刚添加的代码之后,添加以下代码 — 此处是在检查rotateCount 的值是否超过了359 (e.g. 360, 一个正圆的度数)。 如果是,与360取模(值除以 360 后剩下的余数),使得圆圈的动画能以合理的低值连续播放。需要注意的是,这样的操作并不是必要的,只是比起类似于“128000度”的值,运行在 0-359 度之间会使你的操作更容易些。

    - -
    if (rotateCount > 359) {
    -  rotateCount %= 360;
    -}
    -
  16. -
  17. -

    接下来,在上一个块下面添加以下行以实际旋转 旋转(spinner)器:

    - -
    spinner.style.transform = `rotate(${rotateCount}deg)`;
    -
  18. -
  19. -

    在函数draw()内的最下方,插入下面这行代码。这是整个操作中最关键的部分 — 我们将前面定义的变量设置为以draw()函数为参数的活动requestAnimation()调用。 这样动画就开始了,以尽可能接近60 FPS的速率不断地运行draw()函数。

    - -
    rAF = requestAnimationFrame(draw);
    -
  20. -
  21. -

    draw() 函数定义下方,添加对 draw() 函数的调用以启动动画。

    -
  22. -
  23. -
    draw();
    -
  24. -
- -
-

Note: You can find this example live on GitHub (see the source code also).

-
- -

撤销requestAnimationFrame()

- -

requestAnimationFrame()可用与之对应的cancelAnimationFrame()方法“撤销”(不同于“set…”类方法的“清除”,此处更接近“撤销”之意)。

- -

该方法以requestAnimationFrame()的返回值为参数,此处我们将该返回值存在变量 rAF 中:

- -
cancelAnimationFrame(rAF);
- -

主动学习: 启动和停止旋转器(spinner)

- -

在这个练习中,我们希望你能对cancelAnimationFrame()方法做一些测试,在之前的示例中添加新内容。你需要在示例中添加一个事件监听器,用于当鼠标在页面任意位置点击时,启动或停止旋转器。 

- -

一些提示:

- - - -
-

Note: 先自己尝试一下,如果你确实遇到困难,可以参看我们的在线示例源代码

-
- -

限制 (节流) requestAnimationFrame()动画

- -

requestAnimationFrame() 的限制之一是无法选择帧率。在大多数情况下,这不是问题,因为通常希望动画尽可能流畅地运行。但是,当要创建老式的8位类型的动画时,怎么办?

- -

例如,在我们的 Drawing Graphics 文章中的猴子岛行走动画中的一个问题:

- -

{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}

- -

在此示例中,必须为角色在屏幕上的位置及显示的精灵设置动画。精灵动画中只有6帧。如果通过 requestAnimationFrame() 为屏幕上显示的每个帧显示不同的精灵帧,则 Guybrush 的四肢移动太快,动画看起来很荒谬。因此,此示例使用以下代码限制精灵循环的帧率:

- -
if (posX % 13 === 0) {
-  if (sprite === 5) {
-    sprite = 0;
-  } else {
-    sprite++;
-  }
-}
- -

因此,代码每13个动画帧循环一次精灵。

- -

...实际上,大约是每 6.5 帧,因为我们将每帧更新 posX 2个单位值(角色在屏幕上的位置)

- -
if(posX > width/2) {
-  newStartPos = -( (width / 2) + 102 );
-  posX = Math.ceil(newStartPos / 13) * 13;
-  console.log(posX);
-} else {
-  posX += 2;
-}
- -

这是用于计算如何更新每个动画帧中的位置的代码。

- -

用于限制动画的方法将取决于特定代码。例如,在前面的旋转器实例中,可以通过仅在每帧中增加 rotateCount 一个单位(而不是两个单位)来使其运动速度变慢。

- -

主动学习:反应游戏

- -

对于本文的最后部分,将创建一个 2 人反应游戏。游戏将有两个玩家,其中一个使用 A 键控制游戏,另一个使用 L 键控制游戏。

- -

按下 开始 按钮后,像前面看到的旋转器那样的将显示 5 到 10 秒之间的随机时间。之后将出现一条消息,提示 “PLAYERS GO!!”。一旦这发生,第一个按下其控制按钮的玩家将赢得比赛。

- -

{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}

- -

让我们来完成以下工作:

- -
    -
  1. -

    首先,下载 starter file for the app 。其中包含完成的 HTML 结构和 CSS 样式,为我们提供了一个游戏板,其中显示了两个玩家的信息(如上所示),但 spinner 和 结果段落重叠显示,只需要编写 JavaScript 代码。

    -
  2. -
  3. -

    在页面上空的 {{htmlelement("script")}} 元素里,首先添加以下几行代码,这些代码定义了其余代码中需要的一些常量和变量:

    - -
    const spinner = document.querySelector('.spinner p');
    -const spinnerContainer = document.querySelector('.spinner');
    -let rotateCount = 0;
    -let startTime = null;
    -let rAF;
    -const btn = document.querySelector('button');
    -const result = document.querySelector('.result');
    - -

    这些是:

    - -
      -
    1. 对旋转器的引用,因此可以对其进行动画处理。
    2. -
    3. 包含旋转器 {{htmlelement("div")}} 元素的引用。用于显示和隐藏它。
    4. -
    5. 旋转计数。这确定了显示在动画每一帧上的旋转器旋转了多少。
    6. -
    7. 开始时间为null。当旋转器开始旋转时,它将赋值为 开始时间。
    8. -
    9. 一个未初始化的变量,用于之后存储使 旋转器 动画化的  {{domxref("Window.requestAnimationFrame", "requestAnimationFrame()")}}  调用。
    10. -
    11. 开始按钮的引用。
    12. -
    13. 结果字段的引用。
    14. -
    -
  4. -
  5. -

    接下来,在前几行代码下方,添加以下函数。它只接收两个数字并返回一个在两个数字之间的随机数。稍后将需要它来生成随机超时间隔。

    - -
    function random(min,max) {
    -  var num = Math.floor(Math.random()*(max-min)) + min;
    -  return num;
    -}
    -
  6. -
  7. -

    接下来添加 draw() 函数以使 旋转器 动画化。这与之前的简单旋转器示例中的版本非常相似:

    - -
      function draw(timestamp) {
    -    if(!startTime) {
    -     startTime = timestamp;
    -    }
    -
    -    let rotateCount = (timestamp - startTime) / 3;
    -
    -    if(rotateCount > 359) {
    -      rotateCount %= 360;
    -    }
    -
    -    spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
    -    rAF = requestAnimationFrame(draw);
    -  }
    -
  8. -
  9. -

    现在是时候在页面首次加载时设置应用程序的初始状态了。添加以下两行,它们使用 display: none; 隐藏结果段落和旋转器容器。

    - -
    result.style.display = 'none';
    -spinnerContainer.style.display = 'none';
    -
  10. -
  11. -

    接下来,定义一个 reset() 函数。该函数在游戏结束后将游戏设置回初始状态以便再次开启游戏。在代码底部添加以下内容:

    - -
    function reset() {
    -  btn.style.display = 'block';
    -  result.textContent = '';
    -  result.style.display = 'none';
    -}
    -
  12. -
  13. 好的,准备充分!现在该使游戏变得可玩了!将以下代码块添加到代码中。 start() 函数调用 draw() 以启动 旋转器,并在UI中显示它,隐藏“开始”按钮,这样您就无法通过同时启动多次来弄乱游戏,并运行一个经过5到10秒的随机间隔后,会运行 setEndgame() 函数的 setTimeout() 。下面的代码块还将一个事件侦听器添加到按钮上,以在单击它时运行 start() 函数。 -
    btn.addEventListener('click', start);
    -
    -function start() {
    -  draw();
    -  spinnerContainer.style.display = 'block';
    -  btn.style.display = 'none';
    -  setTimeout(setEndgame, random(5000,10000));
    -}
    -
  14. -
  15. 添加以下方法到代码:
  16. -
- -
function setEndgame() {
-  cancelAnimationFrame(rAF);
-  spinnerContainer.style.display = 'none';
-  result.style.display = 'block';
-  result.textContent = 'PLAYERS GO!!';
-
-  document.addEventListener('keydown', keyHandler);
-
-  function keyHandler(e) {
-    console.log(e.key);
-    if(e.key === 'a') {
-      result.textContent = 'Player 1 won!!';
-    } else if(e.key === 'l') {
-      result.textContent = 'Player 2 won!!';
-    }
-
-    document.removeEventListener('keydown', keyHandler);
-    setTimeout(reset, 5000);
-  };
-}
- -

逐步执行以下操作:

- -
    -
  1. 首先通过 {{domxref("window.cancelAnimationFrame", "cancelAnimationFrame()")}}  取消 旋转器 动画(清理不必要的流程总是一件好事),隐藏 旋转器 容器。
  2. -
  3. 接下来,显示结果段落并将其文本内容设置为“ PLAYERS GO !!”。向玩家发出信号,表示他们现在可以按下按钮来取胜。
  4. -
  5. keydown 事件侦听器附加到 document。当按下任何按钮时,keyHandler() 函数将运行。
  6. -
  7. 在 keyHandler() 里,在 keyHandler() 内部,代码包括作为参数的事件对象作为参数(用 e 表示)- 其 {{domxref("KeyboardEvent.key", "key")}} 属性包含刚刚按下的键,可以通过这个对象来对特定的操作和特定的按键做出响应。
  8. -
  9. 将变量 isOver 设置为 false ,这样我们就可以跟踪是否按下了正确的按键以使玩家1或2获胜。我们不希望游戏在按下错误的键后结束。
  10. -
  11. e.key 输出到控制台,这是找出所按的不同键的键值的有用方法。
  12. -
  13. e.key 为“ a”时,显示一条消息说玩家1获胜;当 e.key 为“ l”时,显示消息说玩家2获胜。 (注意:这仅适用于小写的a和l - 如果提交了大写的 A 或 L(键加上 Shift),则将其视为另一个键!)如果按下了其中一个键,请将 isOver 设置为 true
  14. -
  15. 仅当 isOvertrue 时,才使用 {{domxref("EventTarget.removeEventListener", "removeEventListener()")}} 删除 keydown 事件侦听器,以便一旦产生获胜的按键,就不再有键盘输入可以弄乱最终游戏结果。您还可以使用 setTimeout() 在5秒钟后调用 reset()-如前所述,此函数将游戏重置为原始状态,以便可以开始新游戏。
  16. -
- -

就这样-一切都完成了!

- -
-

Note: 如果卡住了, check out our version of the reaction game (see the source code also).

-
- -

结论

- -

就是这样-异步循环和间隔的所有要点在一篇文章中介绍了。您会发现这些方法在许多情况下都很有用,但请注意不要过度使用它们!因为它们仍然在主线程上运行,所以繁重的回调(尤其是那些操纵DOM的回调)会在不注意的情况下降低页面的速度。

- -

{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}

- -

In this module

- - diff --git a/files/zh-cn/web/api/document/onvisibilitychange/index.html b/files/zh-cn/web/api/document/onvisibilitychange/index.html deleted file mode 100644 index 22fb0e4db4..0000000000 --- a/files/zh-cn/web/api/document/onvisibilitychange/index.html +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Document.onvisibilitychange -slug: Web/API/Document/onvisibilitychange -translation_of: Web/API/Document/onvisibilitychange ---- -
{{ApiRef('DOM')}}
- -

Document.onvisibilitychange 是一个事件处理方法,它将在该对象的 visibilitychange事件被触发时调用。

- -

Syntax

- -
obj.onvisibilitychange = function;
-
- - - -

例子

- -
document.onvisibilitychange = function() {
-  console.log("Visibility of page has changed!");
-};
-
- -

标准

- - - - - - - - - - - - - - -
SpecificationStatusComment
{{SpecName('Page Visibility API','#onvisiblitychange-event-handler','onvisibilitychange')}}{{Spec2('Page Visibility API')}}Initial definition.
- -

浏览器兼容性

- - - -

{{Compat("api.Document.onvisibilitychange")}}

- -

另见

- - diff --git a/files/zh-cn/web/api/performance/onresourcetimingbufferfull/index.html b/files/zh-cn/web/api/performance/onresourcetimingbufferfull/index.html deleted file mode 100644 index 7e26cf4793..0000000000 --- a/files/zh-cn/web/api/performance/onresourcetimingbufferfull/index.html +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: Performance.onresourcetimingbufferfull -slug: Web/API/Performance/onresourcetimingbufferfull -translation_of: Web/API/Performance/onresourcetimingbufferfull ---- -
{{APIRef("Resource Timing API")}}
- -

onresourcetimingbufferfull 属性是一个在{{event("resourcetimingbufferfull")}}事件触发时会被调用的 {{domxref("EventHandler","event handler")}} 。这个事件当浏览器的资源时间性能缓冲区已满时会触发。

- -

语法

- -
callback = performance.onresourcetimingbufferfull = buffer_full_cb;
-
- -

返回值

- -
-
callback
-
一个当{{event("resourcetimingbufferfull")}} 事件触发时调用的{{event("Event_handlers", "event handler")}} 。
-
- -

例子

- -

下面的示例在onresourcetimingbufferfull属性上设置一个回调函数。

- -
function buffer_full(event) {
-  console.log("WARNING: Resource Timing Buffer is FULL!");
-  performance.setResourceTimingBufferSize(200);
-}
-function init() {
-  // Set a callback if the resource buffer becomes filled
-  performance.onresourcetimingbufferfull = buffer_full;
-}
-<body onload="init()">
-
- -

规范

- - - - - - - - - - - - - - -
SpecificationStatusComment
{{SpecName('Resource Timing', '#dom-performance-onresourcetimingbufferfull', 'onresourcetimingbufferfull')}}{{Spec2('Resource Timing')}}Initial definition.
- -

浏览器兼容性

- -{{Compat("api.Performance.onresourcetimingbufferfull")}} - -

参见

- - diff --git a/files/zh-cn/web/api/performance/resourcetimingbufferfull_event/index.html b/files/zh-cn/web/api/performance/resourcetimingbufferfull_event/index.html new file mode 100644 index 0000000000..0b5890b069 --- /dev/null +++ b/files/zh-cn/web/api/performance/resourcetimingbufferfull_event/index.html @@ -0,0 +1,65 @@ +--- +title: Performance.onresourcetimingbufferfull +slug: Web/API/Performance/resourcetimingbufferfull_event +translation_of: Web/API/Performance/onresourcetimingbufferfull +original_slug: Web/API/Performance/onresourcetimingbufferfull +--- +
{{APIRef("Resource Timing API")}}
+ +

onresourcetimingbufferfull 属性是一个在{{event("resourcetimingbufferfull")}}事件触发时会被调用的 {{domxref("EventHandler","event handler")}} 。这个事件当浏览器的资源时间性能缓冲区已满时会触发。

+ +

语法

+ +
callback = performance.onresourcetimingbufferfull = buffer_full_cb;
+
+ +

返回值

+ +
+
callback
+
一个当{{event("resourcetimingbufferfull")}} 事件触发时调用的{{event("Event_handlers", "event handler")}} 。
+
+ +

例子

+ +

下面的示例在onresourcetimingbufferfull属性上设置一个回调函数。

+ +
function buffer_full(event) {
+  console.log("WARNING: Resource Timing Buffer is FULL!");
+  performance.setResourceTimingBufferSize(200);
+}
+function init() {
+  // Set a callback if the resource buffer becomes filled
+  performance.onresourcetimingbufferfull = buffer_full;
+}
+<body onload="init()">
+
+ +

规范

+ + + + + + + + + + + + + + +
SpecificationStatusComment
{{SpecName('Resource Timing', '#dom-performance-onresourcetimingbufferfull', 'onresourcetimingbufferfull')}}{{Spec2('Resource Timing')}}Initial definition.
+ +

浏览器兼容性

+ +{{Compat("api.Performance.onresourcetimingbufferfull")}} + +

参见

+ + diff --git a/files/zh-cn/web/api/window/appinstalled_event/index.html b/files/zh-cn/web/api/window/appinstalled_event/index.html new file mode 100644 index 0000000000..f5f098c903 --- /dev/null +++ b/files/zh-cn/web/api/window/appinstalled_event/index.html @@ -0,0 +1,49 @@ +--- +title: Window.onappinstalled +slug: Web/API/Window/appinstalled_event +translation_of: Web/API/Window/onappinstalled +original_slug: Web/API/Window/onappinstalled +--- +
{{APIRef}}
+ +

{{domxref("Window")}} 对象的 onappinstalled 属性用于处理 {{Event("appinstalled")}}  事件,该事件是一个实现了 {{domxref("Event")}}接口的简单事件,会在网页应用成功安装为渐进式网页应用时立即触发。

+ +

语法

+ +
window.onappinstalled = function(event) { ... };
+
+ +

示例

+ +
window.onappinstalled = function(ev) {
+  console.log('The application was installed.');
+};
+ +

规范

+ + + + + + + + + + + + + + + + +
SpecificationStatusComment
{{SpecName('Manifest', '#onappinstalled-attribute', 'Window.onappinstalled')}}{{Spec2('Manifest')}}Initial specification.
+ +

浏览器兼容性

+ +{{Compat("api.Window.onappinstalled")}} + +

相关文章

+ + diff --git a/files/zh-cn/web/api/window/onappinstalled/index.html b/files/zh-cn/web/api/window/onappinstalled/index.html deleted file mode 100644 index 21662ca3f6..0000000000 --- a/files/zh-cn/web/api/window/onappinstalled/index.html +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Window.onappinstalled -slug: Web/API/Window/onappinstalled -translation_of: Web/API/Window/onappinstalled ---- -
{{APIRef}}
- -

{{domxref("Window")}} 对象的 onappinstalled 属性用于处理 {{Event("appinstalled")}}  事件,该事件是一个实现了 {{domxref("Event")}}接口的简单事件,会在网页应用成功安装为渐进式网页应用时立即触发。

- -

语法

- -
window.onappinstalled = function(event) { ... };
-
- -

示例

- -
window.onappinstalled = function(ev) {
-  console.log('The application was installed.');
-};
- -

规范

- - - - - - - - - - - - - - - - -
SpecificationStatusComment
{{SpecName('Manifest', '#onappinstalled-attribute', 'Window.onappinstalled')}}{{Spec2('Manifest')}}Initial specification.
- -

浏览器兼容性

- -{{Compat("api.Window.onappinstalled")}} - -

相关文章

- - -- cgit v1.2.3-54-g00ecf