|
4 | 4 | - HTML5 |
5 | 5 | --- |
6 | 6 |
|
7 | | -关于"连接"部分的学习分为两篇文章介绍,这是上篇,介绍Ajax、Comet、postMessage、Worker和WebSockets。 |
| 7 | +关于"连接"部分的学习分为两篇文章介绍,这是上篇,介绍Ajax、Comet和WebSocket。 |
8 | 8 |
|
9 | 9 | ## Ajax |
10 | 10 | ### 什么是Ajax |
@@ -318,324 +318,6 @@ Web服务器发起通信并异步发送消息到客户端,某种意义上,Aj |
318 | 318 |
|
319 | 319 | > 基于Ajax和Comet可以构建更高级的通信协议,比如RPC(远程过程调用)、发布订阅事件系统。 |
320 | 320 |
|
321 | | -## 跨域消息传递——postMessage |
322 | | -### 适用范围 |
323 | | -允许脚本显式打开的一个新窗口(window.open)或者嵌套其中的窗体(iframe)与当前窗口进行通信 |
324 | | - |
325 | | -### postMessage参数说明 |
326 | | -- 第一个参数表示要传递的消息 |
327 | | -- 第二个参数表示目标窗口的源,可以传递一个URL,仅协议、主机和端口号有效,其余部分会被忽略。若同源,传`/`,若无限制,传`*` |
328 | | - |
329 | | -### 示例 |
330 | | -- [Demo](https://github.com/muzhidong/blog-demo/tree/main/docs/01html/%E8%BF%9E%E6%8E%A5/postMessage) |
331 | | - |
332 | | -## Web Worker |
333 | | -### 背景 |
334 | | -设计成单线程的理论是,JS必须不能运行太长时间,否则会出现卡顿,浏览器无法对用户输入作出响应,而Web Worker弥补浏览器无法多线程的缺陷 |
335 | | - |
336 | | -### 概念 |
337 | | -创建新的运行时,有自己的栈、堆、队列,不影响页面的渲染 |
338 | | - |
339 | | -### 特点 |
340 | | -- 处理耗时操作 |
341 | | -- 同源限制 |
342 | | -- 无法访问window和document,不能操作DOM |
343 | | -- 无法使用文件系统API |
344 | | -- 与主线程不共用同一个上下文环境,即存在线程间数据共享、同步、通信等问题,在ES8提出了[SharedArrayBuffer和Atomics](/javascript/ES6+新特性你知道多少#es2017),实现线程间资源共享,解决线程间同步或通信问题。 |
345 | | - |
346 | | -### Worker API |
347 | | -- Worker |
348 | | - |
349 | | - 事件属性 |
350 | | - - onmessage |
351 | | - - onerror |
352 | | - - onmessageerror |
353 | | - ```javascript |
354 | | - // main.js |
355 | | - const worker = new Worker('./worker.js', { |
356 | | - // 设置worker名称 |
357 | | - name: 'test' |
358 | | - }) |
359 | | - worker.onmessageerror = function(e) {} |
360 | | - |
361 | | - // worker.js |
362 | | - console.log(self.name) |
363 | | - self.onmessageerror = function(e) {} |
364 | | - ``` |
365 | | - |
366 | | - 方法 |
367 | | - - postMessage |
368 | | - ```javascript |
369 | | - // 转移数据 |
370 | | - // 拷贝方式发送二进制数据,会造成性能问题,因为默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法叫做Transferable Objects,使用方式如下:worker.postMessage(arrayBuffer, [arrayBuffer]); |
371 | | - const ab = new ArrayBuffer(1); |
372 | | - worker.postMessage(ab, [ab]); |
373 | | - ``` |
374 | | - - terminate |
375 | | - ```javascript |
376 | | - // 主线程关闭Worker |
377 | | - worker.terminate(); |
378 | | - ``` |
379 | | - |
380 | | -- WorkerGlobalScope |
381 | | - - 除了window和document对象外,其他API基本可以使用 |
382 | | - - close:自行关闭worker |
383 | | - ```javascript |
384 | | - // Worker线程自行关闭Worker |
385 | | - self.close(); |
386 | | - ``` |
387 | | - - importScripts:同步加载多个脚本,当中有一个脚本加载出错,则剩余脚本不再载入和运行 |
388 | | - ```javascript |
389 | | - // Worker线程加载脚本 |
390 | | - importScripts('script1.js', 'script2.js'); |
391 | | - ``` |
392 | | - |
393 | | -> Worker执行模型:worker从上到下同步运行代码,然后进入一个异步阶段。当有监听消息,worker永远不会自动退出;而若没有监听消息,则直到所有任务相关的回调函数都被调用,且再也没有挂起的任务时,worker会自动退出 |
394 | | - |
395 | | -### Worker子类 |
396 | | -- SharedWorker |
397 | | - ```html |
398 | | - <h3>共享线程SharedWorker</h3> |
399 | | - <button id="likeBtn">点赞</button> |
400 | | - <p>收获了<span id="likedCount">0</span>个👍</p> |
401 | | - <script id="shared-worker" type="app/worker"> |
402 | | - console.log("shared-worker"); |
403 | | - let like = 0; |
404 | | - onconnect = function (e) { |
405 | | - const port = e.ports[0]; |
406 | | - port.onmessage = function () { |
407 | | - port.postMessage(++like); |
408 | | - }; |
409 | | - }; |
410 | | - </script> |
411 | | - <script> |
412 | | - const likeBtn = document.querySelector("#likeBtn"); |
413 | | - const likedCountEl = document.querySelector("#likedCount"); |
414 | | - |
415 | | - const blob = new Blob([document.querySelector('#shared-worker').textContent]); |
416 | | - const url = window.URL.createObjectURL(blob); |
417 | | - const worker = new SharedWorker(url); |
418 | | - |
419 | | - worker.port.start(); |
420 | | - likeBtn.addEventListener("click", function () { |
421 | | - worker.port.postMessage("like"); |
422 | | - }); |
423 | | - worker.port.onmessage = function (e) { |
424 | | - likedCountEl.innerHTML = e.data; |
425 | | - }; |
426 | | - </script> |
427 | | - ``` |
428 | | - |
429 | | -- ServiceWorker |
430 | | - |
431 | | - 用途 |
432 | | - - 离线资源缓存与更新 |
433 | | - - 后台消息传递 |
434 | | - - 网络代理 |
435 | | - - 消息推送 |
436 | | - |
437 | | - 使用注意事项 |
438 | | - - 不要给service-worker.js文件带版本号,防止文件变更,读取缓存 |
439 | | - - 不要给service-worker.js文件资源设置缓存 |
440 | | - |
441 | | - 示例 |
442 | | - ```javascript |
443 | | - // 询问用户刷新 |
444 | | - // 思路: |
445 | | - // 1、浏览器检测到存在新的SW时,安装并让它等待,同时触发updatefound事件(浏览器执行,无需编码) |
446 | | - // 2、监听updatefound事件,弹出一个提示条,询问用户是否更新SW |
447 | | - // 3、若用户确认,则向处在等待的SW发送消息,要求其执行skipWaiting并取得控制权 |
448 | | - // 4、SW的变化触发controllerchange事件,在该事件的回调中刷新页面 |
449 | | -
|
450 | | - // index.js |
451 | | - function emitUpdate() { |
452 | | - var event = document.createEvent('Event'); |
453 | | - event.initEvent('sw.update', true, true); |
454 | | - window.dispatchEvent(event); |
455 | | - } |
456 | | -
|
457 | | - if ('serviceWorker' in navigator) { |
458 | | - navigator.serviceWorker.register('/service-worker.js').then(function (reg) { |
459 | | - if (reg.waiting) { |
460 | | - emitUpdate(); |
461 | | - return; |
462 | | - } |
463 | | - // 监听updatefound事件 |
464 | | - reg.onupdatefound = function () { |
465 | | - var installingWorker = reg.installing; |
466 | | - installingWorker.onstatechange = function () { |
467 | | - switch (installingWorker.state) { |
468 | | - // 弹出一个提示条 |
469 | | - case 'installed': |
470 | | - if (navigator.serviceWorker.controller) { |
471 | | - emitUpdate(); |
472 | | - } |
473 | | - break; |
474 | | - } |
475 | | - }; |
476 | | - }; |
477 | | - }).catch(function(e) { |
478 | | - console.error('Error during service worker registration:', e); |
479 | | - }); |
480 | | -
|
481 | | - // 监听controllerchange事件 |
482 | | - let refreshing = false |
483 | | - navigator.serviceWorker.addEventListener('controllerchange', () => { |
484 | | - // 避免使用Chrome Dev Tools的Update on Reload功能时引发无限刷新 |
485 | | - if (refreshing) { |
486 | | - return |
487 | | - } |
488 | | - refreshing = true; |
489 | | - window.location.reload(); |
490 | | - }) |
491 | | - } |
492 | | -
|
493 | | - window.addEventListener('sw.update', function(){ |
494 | | - // 弹框 |
495 | | - }) |
496 | | -
|
497 | | - // 用户确认按钮点击事件 |
498 | | - confirmEl.addEventListener('click', function(){ |
499 | | - try { |
500 | | - navigator.serviceWorker.getRegistration().then(reg => { |
501 | | - reg.waiting.postMessage('skipWaiting'); |
502 | | - }); |
503 | | - } catch (e) { |
504 | | - window.location.reload(); |
505 | | - } |
506 | | - }) |
507 | | -
|
508 | | -
|
509 | | - // service-worker.js |
510 | | - // 接收消息,更新sw |
511 | | - self.addEventListener('message', event => { |
512 | | - if (event.data === 'skipWaiting') { |
513 | | - self.skipWaiting(); |
514 | | - } |
515 | | - }) |
516 | | - ``` |
517 | | - |
518 | | - ```javascript |
519 | | - // 监控页面崩溃 |
520 | | - // index.js |
521 | | - if(navigator.serviceWorker.controller !== null) { |
522 | | - // 心跳间隔 |
523 | | - const HEADBEAT_INTERVAL = 5000; |
524 | | - const sessionId = uuid() |
525 | | - const heartbeat = () => { |
526 | | - navigator.serviceWorker.controller.postMessage({ |
527 | | - type: 'heartbeat', |
528 | | - id: sessionId, |
529 | | - data: { |
530 | | - // 添加附加数据 |
531 | | - } |
532 | | - }) |
533 | | - } |
534 | | - // 心跳检测 |
535 | | - setInterval(heartbeat, HEADBEAT_INTERVAL) |
536 | | - heartbeat() |
537 | | -
|
538 | | - // 通知sw删除当前监控记录 |
539 | | - window.addEventListener('beforeunload', () => { |
540 | | - navigator.serviceWorker.controller.postMessage({ |
541 | | - type: 'unload', |
542 | | - id: sessionId |
543 | | - }) |
544 | | - }) |
545 | | - } |
546 | | -
|
547 | | - // worker.js |
548 | | - // 检查崩溃间隔 |
549 | | - const CHECK_CRASH_INTERVAL = 10000; |
550 | | - // 崩溃阈值 |
551 | | - const CRASH_THRESHOLD = 15000; |
552 | | - const pages = {} |
553 | | - let timer |
554 | | - const checkCrash = () => { |
555 | | - const now = Date.now() |
556 | | - for(let id in pages) { |
557 | | - const page = pages[id] |
558 | | - if(now - page.t > CRASH_THRESHOLD) { |
559 | | - // 上报crash |
560 | | - // 删除监控记录 |
561 | | - delete pages[id] |
562 | | - } |
563 | | - } |
564 | | - if(Object.keys(pages).length === 0) { |
565 | | - clearInterval(timer) |
566 | | - timer = null |
567 | | - } |
568 | | - } |
569 | | - self.addEventListener('message', event => { |
570 | | - const data = event.data |
571 | | - if(data.type === 'heartbeat') { |
572 | | - pages[data.id] = { |
573 | | - t: Date.now(), |
574 | | - } |
575 | | - if(!timer) { |
576 | | - timer = setInterval(checkCrash, CHECK_CRASH_INTERVAL) |
577 | | - } |
578 | | - } else if(data.type === 'unload') { |
579 | | - delete pages[data.id] |
580 | | - } |
581 | | - }) |
582 | | - ``` |
583 | | - |
584 | | - ```javascript |
585 | | - // 加速边缘计算 |
586 | | - self.addEventListener('fetch', event => { |
587 | | - event.respondWith(handle(event.request)) |
588 | | - }) |
589 | | - async function handle(request) { |
590 | | - const url = new URL(request.url) |
591 | | - if (url.pathname == "/") { |
592 | | - // 这是一个首页请求,重定向到特定国家的路径,如给美国用户发送“/US/” |
593 | | - const country = request.headers.get("CF-IpCountry") |
594 | | - url.pathname = "/" + country + "/" |
595 | | - return Response.redirect(url, 302) |
596 | | - } else if (url.pathname.startsWith("/images/")) { |
597 | | - // 这是一个图片请求,阻止第三方访问者盗链 |
598 | | - const referrer = request.headers.get("Referer") |
599 | | - if (referrer && new URL(referrer).hostname != url.hostname) { |
600 | | - return new Response("Hot linking not allowed.", { |
601 | | - status: 403 |
602 | | - }) |
603 | | - } |
604 | | - // 盗链检查通过,直接从谷歌云存储提供图片服务节省服务成本 |
605 | | - // 根据Cache-Control头信息,图片会在Cloudflare的边缘服务器缓存 |
606 | | - url.hostname = "example-bucket.storage.googleapis.com" |
607 | | - return fetch(url, request) |
608 | | - } else { |
609 | | - // 定期请求,转发给源服务器 |
610 | | - return fetch(request) |
611 | | - } |
612 | | - } |
613 | | - ``` |
614 | | - |
615 | | -### 示例 |
616 | | -- worker代码和主线程代码在同一页面 |
617 | | - ```html |
618 | | - <!-- worker脚本 --> |
619 | | - <!-- 注意script标签需指定id属性,且type属性是一个浏览器不认识的值 --> |
620 | | - <script id="worker" type="app/worker"> |
621 | | - addEventListener('message', function (e) { |
622 | | - postMessage("I'm fine."); |
623 | | - }, false); |
624 | | - </script> |
625 | | - <!-- 主线程脚本 --> |
626 | | - <script> |
627 | | - // Blob内容是子线程代码 |
628 | | - var blob = new Blob([document.querySelector('#worker').textContent]); |
629 | | - var url = window.URL.createObjectURL(blob); |
630 | | - var worker = new Worker(url); |
631 | | - worker.postMessage('How are you?') |
632 | | - worker.onmessage = function (e) { |
633 | | - console.log(e.data); |
634 | | - }; |
635 | | - </script> |
636 | | - ``` |
637 | | - |
638 | | -- [Worker and ServiceWorker Demo](https://github.com/muzhidong/blog-demo/tree/main/docs/01html/%E8%BF%9E%E6%8E%A5/Worker) |
639 | 321 |
|
640 | 322 | ## WebSocket |
641 | 323 | ### 概念 |
|
0 commit comments