JavaScript高级程序设计第四版学习--第二十章
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>
推荐阅读
-
javascript高级程序设计(第三版)学习笔记(一) 正则表达式整理
-
javascript高级程序设计学习历程
-
JavaScript高级程序设计(第三版)学习笔记(二)JavaScript使用
-
JavaScript高级程序设计(第三版)第二章读书笔记
-
JavaScript高级程序设计(第三版)学习笔记(三) JavaScript语法 草稿
-
JavaScript高级程序设计第四版学习--第二十六章
-
JavaScript高级程序设计第四版学习--第二十章
-
JavaScript高级程序设计(第四版)学习笔记(第二章 HTML中的JavaScript)
-
JavaScript高级程序设计第四版学习--第二十三章
-
JavaScript高级程序设计第四版学习--第四章