title: JavaScript高级程序设计第四版学习–第二十章
date: 2021-5-29 20:59:12
author: Xilong88
tags: JavaScript

本章内容
Atomics与SharedArrayBuffer
跨上下文消息
Encoding API
File API与Blob API
拖放
Notifications API
Page Visibility API
Streams API
计时API
Web组件
Web Cryptography API
可能出现的面试题:
1.如何跨文档传递信息,了解过XDM吗?
2.文件拖拽如何实现?
3.拖放事件的组成?
4.通知API了解过吗?
5.如何测试加载性能?(高精度时间戳怎么获取)
6.了解过影子DOM吗?

知识点:

Atomics与SharedArrayBuffer 略
Streams API 略
自定义元素 略
Web Cryptography API 略

1.跨上下文消息

跨文档消息 ,有时候也简称为XDM(cross-document messaging),是一种在不同执行上下文(如不同工作线程或不同源的页面)间传递信息的能力。

let iframeWindow = document.getElementById("myframe").contentWindow;
iframeWindow.postMessage("A secret", "http://www.wrox.com");

如果不想限制接收目标,则可以给postMessage() 的第二个参数传"*" ,但不推荐这么做。

传给onmessage 事件处理程序的event 对象包含以下3方面重要信息。

data :作为第一个参数传递给postMessage() 的字符串数据。
origin :发送消息的文档源,例如"http://www.wrox.com" 。
source :发送消息的文档中window 对象的代理。这个代理对象主
要用于在发送上一条消息的窗口中执行postMessage() 方法。如
果发送窗口有相同的源,那么这个对象应该就是window 对象。
window.addEventListener("message", (event) => {
  // 确保来自预期发送者
  if (event.origin == "http://www.wrox.com") {
    // 对数据进行一些处理
    processMessage(event.data);
    // 可选:向来源窗口发送一条消息
    event.source.postMessage("Received!", "http://p2p.wrox.com");
  }
});

2.Encoding API

批量编码是通过TextEncoder的实例完成

const textEncoder = new TextEncoder();
const decodedText = 'foo';
const encodedText = textEncoder.encode(decodedText);
// f的UTF-8编码是0x66(即十进制102)
// o的UTF-8编码是0x6F(即二进制111)
console.log(encodedText); // Uint8Array(3) [102, 111, 111]

编码器是用于处理字符的,有些字符(如表情符号)在最终返回的数组中可能会占多个索引:

const textEncoder = new TextEncoder();
const decodedText = '☺';
const encodedText = textEncoder.encode(decodedText);
// ☺的UTF-8编码是0xF0 0x9F 0x98 0x8A(即十进制240、159、152、138)
console.log(encodedText); // Uint8Array(4) [240, 159, 152, 138]

解码

const textDecoder = new TextDecoder();
// f的UTF-8编码是0x66(即十进制102)
// o的UTF-8编码是0x6F(即二进制111)
const encodedText = Uint8Array.of(102, 111, 111);
const decodedText = textDecoder.decode(encodedText);
console.log(decodedText); // foo

3.File API与Blob API

name :本地系统中的文件名。
size :以字节计的文件大小。
type :包含文件MIME类型的字符串。
lastModifiedDate :表示文件最后修改时间的字符串。这个属性只有Chome实现了。
let filesList = document.getElementById("files-list");
filesList.addEventListener("change", (event) => {
  let files = event.target.files,
      i = 0,
      len = files.length;
  while (i < len) {
    const f = files[i];
    console.log(`${f.name} (${f.type}, ${f.size} bytes)`);

    i++;
  }
});

FileReader 类型

FileReader 类型提供了几个读取文件数据的方法。

readAsText(file, encoding) :从文件中读取纯文本内容并保
存在result 属性中。第二个参数表示编码,是可选的。
readAsDataURL(file) :读取文件并将内容的数据URI保存
在result 属性中。
readAsBinaryString(file) :读取文件并将每个字符的二进制
数据保存在result 属性中。
readAsArrayBuffer(file) :读取文件并将文件内容以
ArrayBuffer 形式保存在result 属性。

因为这些读取方法是异步的,所以每个FileReader 会发布几个事件,其中3个最有用的事件是progress 、error 和load ,分别表示还有更多数据、发生了错误和读取完成。

progress 事件每50毫秒就会触发一次,其与XHR的progress 事件具有相同的信息:lengthComputable 、loaded 和total 。

在progress 事件中可以读取FileReader 的result 属性,即使其中尚未包含全部数据。

触发error 事件时,FileReader 的error 属性会包含错误信息。这个属性是一个对象,只包含一个属性:code 。这个错误码的值可能是1(未找到文件)、2(安全错误)、3(读取被中断)、4(文件不可读)或5(编码错误)。

let filesList = document.getElementById("files-list");
filesList.addEventListener("change", (event) => {
  let info = "",
      output = document.getElementById("output"),
      progress = document.getElementById("progress"),
      files = event.target.files,
      type = "default",
      reader = new FileReader();
  if (/image/.test(files[0].type)) {
    reader.readAsDataURL(files[0]);
    type = "image";
  } else {
    reader.readAsText(files[0]);
    type = "text";
  }
  reader.onerror = function() {
    output.innerHTML = "Could not read file, error code is " +
        reader.error.code;
  };
  reader.onprogress = function(event) {
    if (event.lengthComputable) {
      progress.innerHTML = `${event.loaded}/${event.total}`;
    }
  };
  reader.onload = function() {
    let html = "";
    switch(type) {
      case "image":
        html = `<img src="${reader.result}">`;
        break;
      case "text":

        html = reader.result;
        break;
    }
    output.innerHTML = html;
  };
});

FileReaderSync 类型

FileReaderSync 类型就是FileReader 的同步 版本

// worker.js
self.omessage = (messageEvent) => {
  const syncReader = new FileReaderSync();
  console.log(syncReader); // FileReaderSync {}
  // 读取文件时阻塞工作线程
  const result = syncReader.readAsDataUrl(messageEvent.data);
  // PDF文件的示例响应

  console.log(result); // data:application/pdf;base64,JVBERi0xLjQK...
  // 把URL发回去
  self.postMessage(result);
};

Blob 与部分读取

Blob 对象有一个size 属性和一个type 属性,还有一个slice() 方法用于进一步切分数据。另外也可以使用FileReader 从Blob 中读取数据。下面的例子只会读取文件的前32字节:

let filesList = document.getElementById("files-list");
filesList.addEventListener("change", (event) => {
  let info = "",
    output = document.getElementById("output"),
    progress = document.getElementById("progress"),
    files = event.target.files,
    reader = new FileReader(),
    blob = blobSlice(files[0], 0, 32);
  if (blob) {

    reader.readAsText(blob);
    reader.onerror = function() {
      output.innerHTML = "Could not read file, error code is " +
                 reader.error.code;
    };
    reader.onload = function() {
      output.innerHTML = reader.result;
    };
  } else {
    console.log("Your browser doesn't support slice().");
  }
});

window.URL.createObjectURL() 方法并传入File 或Blob 对象。

返回的URL就可以直接用

let filesList = document.getElementById("files-list");
filesList.addEventListener("change", (event) => {
  let info = "",
    output = document.getElementById("output"),
    progress = document.getElementById("progress"),
    files = event.target.files,
    reader = new FileReader(),
    url = window.URL.createObjectURL(files[0]);
  if (url) {
    if (/image/.test(files[0].type)) {
      output.innerHTML = `<img src="${url}">`;
    } else {
      output.innerHTML = "Not an image.";
    }
  } else {

    output.innerHTML = "Your browser doesn't support object URLs.";
  }
});

4.拖文件

event.dataTransfer.files可以读取到拖到浏览器里面的文件

let droptarget = document.getElementById("droptarget");
function handleEvent(event) {
  let info = "",
    output = document.getElementById("output"),
    files, i, len;
  event.preventDefault();
  if (event.type == "drop") {
    files = event.dataTransfer.files;
    i = 0;
    len = files.length;
    while (i < len) {
      info += `${files[i].name} (${files[i].type}, ${files[i].size} bytes)<br>`;
      i++;
    }
    output.innerHTML = info;

  }
}
droptarget.addEventListener("dragenter", handleEvent);
droptarget.addEventListener("dragover", handleEvent);
droptarget.addEventListener("drop", handleEvent);

要记得屏蔽掉拖进来和拖放结束的默认事件,不然会显示文件下载到了浏览器。

5.媒体元素
<audio><video>

省略了,要用再看

6.原生拖放
在某个元素被拖动时,会(按顺序)触发以下事
件:
(1) dragstart
(2) drag
(3) dragend

在按住鼠标键不放并开始移动鼠标的那一刻,被拖动元素上会触发dragstart 事件。此时光标会变成非放置符号(圆环中间一条斜杠),表示元素不能放到自身上。拖动开始时,可以在ondragstart 事件处理程序中通过JavaScript执行某些操作。
dragstart 事件触发后,只要目标还被拖动就会持续触发drag 事件。这个事件类似于mousemove ,即随着鼠标移动而不断触发。当拖动停止时(把元素放到有效或无效的放置目标上),会触发dragend 事件。

dataTransfer 对象

getData() 和setData()

// 传递文本
event.dataTransfer.setData("text", "some text");
let text = event.dataTransfer.getData("text");
// 传递URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
let url = event.dataTransfer.getData("URL");

dropEffect 与effectAllowed

dropEffect 属性可以告诉浏览器允许哪种放置行为。这个属性有以下4种可能的值。

"none" :被拖动元素不能放到这里。这是除文本框之外所有元素
的默认值。
"move" :被拖动元素应该移动到放置目标。
"copy" :被拖动元素应该复制到放置目标。
"link" :表示放置目标会导航到被拖动元素(仅在它是URL的情
况下)。

effectAllowed 属性表示对被拖动元素是否允许dropEffect 。这个属性有如下几个可能的值。

"uninitialized" :没有给被拖动元素设置动作。
"none" :被拖动元素上没有允许的操作。
"copy" :只允许"copy" 这种dropEffect 。
"link" :只允许"link" 这种dropEffect 。

"move" :只允许"move" 这种dropEffect 。
"copyLink" :允许"copy""link" 两种dropEffect 。
"copyMove" :允许"copy""move" 两种dropEffect 。
"linkMove" :允许"link""move" 两种dropEffect 。
"all" :允许所有dropEffect 。

可拖动能力

<!-- 禁止拖动图片 -->
<img src="smile.gif" draggable="false" alt="Smiley face">
<!-- 让元素可以拖动 -->
<div draggable="true">...</div>

7.显示和隐藏通知

Notification.requestPermission()
    .then((permission) => {
      console.log('User responded to permission request:', permission);
    });
new Notification('Title text!', {
  body: 'Body text!',
  image: 'path/to/image.png',
  vibrate: true
});

Notifications API
提供了4个用于添加回调的生命周期方法:

onshow 在通知显示时触发;
onclick 在通知被点击时触发;
onclose 在通知消失或通过close() 关闭时触发;
onerror 在发生错误阻止通知显示时触发。
const n = new Notification('foo');
n.onshow = () => console.log('Notification was shown!');
n.onclick = () => console.log('Notification was clicked!');
n.onclose = () => console.log('Notification was closed!');
n.onerror = () => console.log('Notification experienced an error!');

8.Page Visibility API

document.visibilityState 值,表示下面4种状态之一。

页面在后台标签页或浏览器中最小化了。
页面在前台标签页中。
实际页面隐藏了,但对页面的预览是可见的(例如在Windows7上,用户鼠标移到任务栏图标上会显示网页预览)。
页面在屏外预渲染。

visibilitychange 事件,该事件会在文档从隐藏变可见(或反之)时触发。document.hidden 布尔值,表示页面是否隐藏。这可能意味着页面在后台标签页或浏览器中被最小化了。这个值是为了向后兼容才继续被浏览器支持的,应该优先使用document.visibilityState

检测页面可见性。要想在页面从可见变为隐藏或从隐藏变为可见时得到通知,需要监听visibilitychange 事件。document.visibilityState 的值是以下三个字符串之一:
“hidden”
“visible”
“prerender”

9.计时API

window.performance上有很多计时API,相对于Date对象来说,更精准。

Performance 接口由多个API构成:

High Resolution Time API
Performance Timeline API
Navigation Timing API
User Timing API
Resource Timing API
Paint Timing API
const t0 = performance.now();
const t1 = performance.now();
console.log(t0);       // 1768.625000026077
console.log(t1);       // 1768.6300000059418
const duration = t1 – t0;
console.log(duration); // 0.004999979864805937

performance.timeOrigin 属性返回计时器初始化时全局系统时钟的值。

const relativeTimestamp = performance.now();
const absoluteTimestamp = performance.timeOrigin + relativeTimestamp;
console.log(relativeTimestamp); // 244.43500000052154
console.log(absoluteTimestamp); // 1561926208892.4001

每个Entry对象都有name 、entryType 、startTime 和duration 属性
记录着性能相关的东西。

console.log(performance.getEntries());
const entry = performance.getEntries()[0];
console.log(entry.name);      // "https://foo.com"
console.log(entry.entryType); // navigation
console.log(entry.startTime); // 0
console.log(entry.duration);  // 182.36500001512468

User Timing API
可以自定义标记,然后获取标记上的信息

performance.mark('foo');
console.log(performance.getEntriesByType('mark')[0]);
// PerformanceMark {
//   name: "foo",
//   entryType: "mark",
//   startTime: 269.8800000362098,
//   duration: 0
// }
performance.mark('foo');
for (let i = 0; i < 1E6; ++i) {}
performance.mark('bar');
const [endMark, startMark] = performance.getEntriesByType('mark');
console.log(startMark.startTime - endMark.startTime); // 1.3299999991431832

除了自定义性能条目,还可以生成PerformanceMeasure (性能度量)条目,对应由名字作为标识的两个标记之间的持续时间。PerformanceMeasure 的实例由performance.measure() 方法生成:

performance.mark('foo');
for (let i = 0; i < 1E6; ++i) {}
performance.mark('bar');
performance.measure('baz', 'foo', 'bar');
const [differenceMark] = performance.getEntriesByType('measure');
console.log(differenceMark);
// PerformanceMeasure {

//   name: "baz",
//   entryType: "measure",
//   startTime: 298.9800000214018,
//   duration: 1.349999976810068
// }

Navigation Timing API

Navigation Timing API提供了高精度时间戳,用于度量当前页面加载速度。浏览器会在导航事件发生时自动记录PerformanceNavigationTiming 条目。这个对象会捕获大量时间戳,用于描述页面是何时以及如何加载的

下面的例子计算了loadEventStart 和loadEventEnd 时间戳之间的差:

const [performanceNavigationTimingEntry] = performance.getEntriesByType('navigation');
console.log(performanceNavigationTimingEntry);
// PerformanceNavigationTiming {
//   connectEnd: 2.259999979287386
//   connectStart: 2.259999979287386
//   decodedBodySize: 122314
//   domComplete: 631.9899999652989
//   domContentLoadedEventEnd: 300.92499998863786
//   domContentLoadedEventStart: 298.8950000144541
//   domInteractive: 298.88499999651685
//   domainLookupEnd: 2.259999979287386
//   domainLookupStart: 2.259999979287386
//   duration: 632.819999998901
//   encodedBodySize: 21107
//   entryType: "navigation"
//   fetchStart: 2.259999979287386
//   initiatorType: "navigation"
//   loadEventEnd: 632.819999998901
//   loadEventStart: 632.0149999810383
//   name: " https://foo.com "
//   nextHopProtocol: "h2"
//   redirectCount: 0
//   redirectEnd: 0
//   redirectStart: 0

//   requestStart: 7.7099999762140214
//   responseEnd: 130.50999998813495
//   responseStart: 127.16999999247491
//   secureConnectionStart: 0
//   serverTiming: []
//   startTime: 0
//   transferSize: 21806
//   type: "navigate"
//   unloadEventEnd: 132.73999997181818
//   unloadEventStart: 132.41999997990206
//   workerStart: 0
// }
console.log(performanceNavigationTimingEntry.loadEventEnd –
            performanceNavigationTimingEntry.loadEventStart);
// 0.805000017862767

Resource Timing API

面的例子计算了加载一个特定资源所花的时间:

const performanceResourceTimingEntry = performance.getEntriesByType('resource')[0];
console.log(performanceResourceTimingEntry);
// PerformanceResourceTiming {
//   connectEnd: 138.11499997973442
//   connectStart: 138.11499997973442
//   decodedBodySize: 33808
//   domainLookupEnd: 138.11499997973442
//   domainLookupStart: 138.11499997973442
//   duration: 0
//   encodedBodySize: 33808
//   entryType: "resource"
//   fetchStart: 138.11499997973442
//   initiatorType: "link"
//   name: "https://static.foo.com/bar.png",

//   nextHopProtocol: "h2"
//   redirectEnd: 0
//   redirectStart: 0
//   requestStart: 138.11499997973442
//   responseEnd: 138.11499997973442
//   responseStart: 138.11499997973442
//   secureConnectionStart: 0
//   serverTiming: []
//   startTime: 138.11499997973442
//   transferSize: 0
//   workerStart: 0
// }
console.log(performanceResourceTimingEntry.responseEnd –
            performanceResourceTimingEntry.requestStart);
// 493.9600000507198

10.Web组件

HTML模板

<template id="foo">
  <p>I'm inside a template!</p>
</template>
const fragment = document.querySelector('#foo').content;
console.log(document.querySelector('p')); // null
console.log(fragment.querySelector('p')); // <p>...<p>

通过<template> 元素的content 属性可以取得这
个DocumentFragment 的引用:

console.log(document.querySelector('#foo').content); // #document-fragment

DocumentFragment 也是批量向HTML中添加元素的高效工具。因为只需要把操作一次DOM

// 开始状态:
// <div id="foo"></div>
//
// 期待的最终状态:
// <div id="foo">
//   <p></p>
//   <p></p>
//   <p></p>
// </div>
// 也可以使用document.createDocumentFragment()
const fragment = new DocumentFragment();
const foo = document.querySelector('#foo');
// 为DocumentFragment添加子元素不会导致布局重排
fragment.appendChild(document.createElement('p'));
fragment.appendChild(document.createElement('p'));
fragment.appendChild(document.createElement('p'));
console.log(fragment.children.length); // 3
foo.appendChild(fragment);
console.log(fragment.children.length); // 0
console.log(document.body.innerHTML);
// <div id="foo">
//   <p></p>

//   <p></p>
//   <p></p>
// </div>

影子DOM

attachShadow() 方法需要一个shadowRootInit 对象,返回影子DOM的实例。shadowRootInit 对象必须包含一个mode 属性,值为"open" 或"closed" 。对"open" 影子DOM的引用可以通过shadowRoot 属性在HTML元素上获得,而对"closed" 影子DOM的引用无法这样获取

document.body.innerHTML = `
  <div id="foo"></div>
  <div id="bar"></div>
`;
const foo = document.querySelector('#foo');
const bar = document.querySelector('#bar');
const openShadowDOM = foo.attachShadow({ mode: 'open' });
const closedShadowDOM = bar.attachShadow({ mode: 'closed' });

console.log(openShadowDOM);   // #shadow-root (open)
console.log(closedShadowDOM); // #shadow-root (closed)
console.log(foo.shadowRoot);  // #shadow-root (open)
console.log(bar.shadowRoot);  // null
for (let color of ['red', 'green', 'blue']) {
  const div = document.createElement('div');
  const shadowDOM = div.attachShadow({ mode: 'open' });
  document.body.appendChild(div);
  shadowDOM.innerHTML = `
    <p>Make me ${color}</p>
    <style>
    p {
      color: ${color};
    }
    </style>
  `;
}

合成与影子DOM槽位

document.body.innerHTML = `
<div id="foo">
  <p>Foo</p>
</div>
`;
document.querySelector('div')
    .attachShadow({ mode: 'open' })
    .innerHTML = `
      <div id="bar">
        <slot></slot>
      </div>`
console.log(document.querySelector('p').parentElement);
// <div id="foo"></div>

宿主的元素会放到槽位里面去

槽位可以命名

document.body.innerHTML = `
<div>
  <p slot="foo">Foo</p>
  <p slot="bar">Bar</p>
</div>
`;
document.querySelector('div')
    .attachShadow({ mode: 'open' })
    .innerHTML = `
    <slot name="bar"></slot>
    <slot name="foo"></slot>
    `;
// Renders:
// Bar
// Foo

事件会逃出影子DOM

// 创建一个元素作为影子宿主
document.body.innerHTML = `
<div onclick="console.log('Handled outside:', event.target)"></div>
`;
// 添加影子DOM并向其中插入HTML
document.querySelector('div')
  .attachShadow({ mode: 'open' })
  .innerHTML = `
<button onclick="console.log('Handled inside:', event.target)">Foo</button>
`;
// 点击按钮时:
// Handled inside:  <button οnclick="..."></button>
// Handled outside: <div οnclick="..."></div>
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐