《JavaScript 高级程序设计》第二十章 JavaScript API 学习记录

530 阅读19分钟

1、Atomics与SharedArrayBuffer

2、跨上下文消息

  • 跨文档消息(XDM),在不同执行上下文间传递信息的能力。
  • postMessage()方法通信。
    • 三个参数:消息、表示接收源的字符串和可选的可传输对象的数组。
    • 表示接收源的字符串对于安全很重要,限制交付数据的目标。
  • 接收到XDM消息后,window对象上会触发message事件,是异步触发的。
    • 事件的event对象包含
      • data:作为第一个参数传递给postMessage()的字符串数据
      • origin:发送消息的文档源
      • source:发送消息的文档中window对象代理。用来在发送上一条消息的窗口中执行postMessage()方法
  • 验证发送窗口的源很重要,保证数据不会意外传给未知页面。

3、Encoding API

  • 用于实现字符串与定性数组转换

  • TextEncoderTextEncoderStreamTextDecoderTextDecoderStream

1、文本编码

1、批量编码

const textEncoder = new TextEncoder()
const decodedText = 'foo'
const encodedText = textEncoder.encode(decodedText)
  • encodeInto()
    • 接收一个字符串和目标Unit8Array
    • 返回一个字典,包含read和written属性
    • 分别表示成功从源字符串读取了多少字符和向目标数组写入了多少字符。
    • 空间不足时,编码就会提前终止,返回的字典可以体现这个结果。

2、流编码

  • TextEncoderStream其实就是TransformStream形式的TextEncoder
  • 将解码后的文本流通过管道输入流编码器会得到编码后文本块的流。

2、文本解码

1、批量解码

const textDecoder = new Textdecoder()
const encodedText = Unit8Array.of(102, 111, 111)
const decodedText = textDecoder.decode(encodedText)

2、流解码

4、File API 与 Blob API

  • 目的是能以安全的方式访问客户端机器上的文件。

1、File类型

  • 以表单中的文件输入字段为基础,增加了直接访问文件信息的能力。

  • 为文件输入元素添加了files集合。当用户在文件字段中选择一个或多个文件时,这个files集合会包含一组File对象,表示被选中的文件。

    • name:本地系统中的文件名
    • size:以字节计的文件大小
    • type:包含文件MIME类型的字符串
    • lastModifiedDate:表示文件最后修改过事件的字符串
    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++;
      }
    });
    

2、FileReader类型

  • 异步文件读取机制,FileReader类似XMLHttpRequest,只不过是从文件系统读取文件,而不是从服务器读取数据,读取方法:

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

    • progress
      • 还有更多数据
      • 每50毫秒触发一次,与XHR的progress具有相同信息
        • lengthComputable
        • loaded
        • total
      • 可以读取FileReader的result属性,即使其中尚未包含全部数据
    • error
      • 发送了错误,会在由于某种原因无法读取文件时触发
      • FileReader的error属性包含错误信息
        • 属性是一个对象,对象的code属性表示错误码
          • 1 未找到文件
          • 2 安全错误
          • 3 读取被中断
          • 4 文件不可读
          • 5 编码错误
    • load
      • 读取完成后触发
      • 如果触发了error事件,则不会触发load
    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;
      };
    });
    
  • 提前结束文件读取调用abort()方法,触发abort事件

  • 在load、error和abort事件触发后,还会触发loadend事件表示所有读操作已经结束

3、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);
};

4、Blob与部分读取

  • File对象slice() 方法,读取部分文件
    • 接收两个参数:起始字节和要读取的字节数。
    • 返回Blob实例,Blob实际是File的超类。
  • blob表示二进制大对象,是对不可修改二进制数据的封装类型。
  • 包含字符串的数组、ArrayBuffers、ArrayBufferViews、其他Blob都可以创建blob。
  • Blob构造函数接收一个options参数,指定MIME类型:
new Blob(['foo']) 
// {size: 3, type: ""}

new Blob(['{"a": "b"}'], {type: 'application/json'}) 
// {size: 10, type: "application/json"}

new Blob(['<p>Foo</p>', '<p>Bar</p>'], {type: 'text/html'}) 
// {size: 20, type: "text/html"}
  • Blob对象有一个slice()方法用来进一步切分数据。
  • 也可以使用FileReader从Bolb中读取数据

5、对象URL与Blob

  • 也称Blob URL,是指存储在File或Blob中数据的URL。
  • 优点是不同把文件内容读取到JavaScript也可以使用文件。只要在适当位置提供对象URL即可。
  • 创建方法:window.URL.createObjectURL()
    • 传入File或Blob对象。
    • 返回指向内存中地址的字符串。
  • 移除方法:window.URL.revokeObjectURL()

6、读取拖放文件

  • 触发drop事件。
  • 通过event.dataTransfer.files读取到文件,保存着一组File对象。
  • 必须取消dropenter、dropover和drop的默认行为。

5、媒体元素

  • <audio><video>
    • poster 默认图片
    • controls 控制

1、属性

  • autoplay 自动播放
  • buffered已下载缓冲的时间范围
  • bufferedBytes 已下载缓冲的字节范围
  • bufferingRate 平均每秒下载的位数
  • bufferingThrottled 表示缓冲是否被浏览器截流
  • controls显示/隐藏内置控件
  • currentLoop 媒体已经播放的循环次数
  • currentSrc 当前播放媒体的URL
  • currentTime 已经播放的秒数
  • defaultPlaybackRate 取得或设置默认回放速率。默认1.0秒
  • duration 媒体的总秒数
  • ended 媒体是否播放完成
  • loop 取得或设置媒体是否循环
  • muted 取得或设置媒体是否静音
  • networkState 当前网络连接状态 0空 1加载中 2加载元数据 3加载了第一帧 4加载完成
  • paused 表示播放器是否暂停
  • playbackRate 取得或设置当前播放速率
  • played 到目前为止已经播放的时间范围
  • readyState 媒体是否已经准备就绪 0不可用 1可以显示当前帧 2媒体可以开始播放 3媒体可以从头播到尾
  • seekable 可以跳转的事件范围
  • seeking 媒体是否正在移动到媒体文件的新位置
  • src 媒体源
  • start 取得或设置媒体文件的位置,以秒为单位,从该处开始播放
  • totalBytes 资源需要的字节总数
  • videoHeight 视频的高度
  • videoWidth 视频的宽度
  • volume 取得或设置当前音量 0.0 - 1.0

2、事件

  • abort 下载被中断
  • canplay 回放可以开始 readyState为2
  • canplaythrough 回放可以连续播放 readyState为3
  • canshowcurrentframe 已经下载当前帧 readyState为1
  • dataunavailable 不能播放没有数据 readyState为0
  • durationchange duratino属性值发生变化
  • emptied 网络连接关闭了
  • empty 发生了错误,阻止媒体下载
  • ended 媒体已经播放完一遍,且停止了。
  • error 下载期间发生了网络错误
  • loadeddata 媒体第一帧已经下载
  • loadedmetadata 媒体元数据已经下载
  • loadstart 下载已经开始
  • pause 暂停
  • play 收到开始播放请求
  • playing 实际已经开始播放了。
  • progress 下载中
  • ratechange 媒体播放速率发送变化
  • seeked 跳转已经结束
  • seeking 回放已移动到新位置
  • stalled 浏览器尝试下载,但未收到数据
  • timeupdate 播放位置改变
  • volumechange volume或muted发生变化
  • waiting 暂停,以下载更多数据

3、自定义媒体播放器

4、检测编解码器

  • canPlayType()
    • 返回 probably、maybe 或空字符串

5、音频类型

  • 创建Audio新实例会开始下载指定的文件,下载后可以调用play()播放

6、原生拖放

  • 可以跨窗格、跨浏览器容器,甚至跨应用程序。

1、拖放事件

  • 在某个元素拖动时会触发
    • dragstart
    • drag
    • dragend
  • 把元素拖动到一个有效的放置目标上,会触发
    • dragenter
    • dragover
    • dragleave 或 drop

2、自定义放置目标

  • 通过覆盖dragenter 和 dragover事件的默认行为,可以转为有效防止目标

3、dataTransfer对象

  • event对象上的dataTransfer对象

    • 用于从被拖动元素向放置目标传递字符串数据。

    • getData()获取setData() 存储的值

    • setData() 第一个参数和getData() 的唯一参数是字符串,表示要设置的数据类型,text / URL

  • 从文本框拖动文本,浏览器会调用setData()将拖动文本以text存储起来,同样的拖动链接或图片,会以URL存储起来,放置到目标上会getData()获取数据。

  • 文本不会特殊对待,链接放到另一个浏览器窗口会导航到这个URL

4、dropEffect 与 effectAllowed

  • dataTransfer对象的其他属性

  • dropEffect属性告诉浏览器允许哪种放置行为

    • none不能放到这里
    • move 应该移动到放置目标
    • copy 应该复制到放置目标
    • link 放置目标会导航到被拖动元素
  • effectAllowed属性设置后dropEffect才有用,表示对被拖动元素是否允许dropEffect,必须在dragstart事件前设置

    • uninitialized 没有给被拖动元素设置动作
    • none 没有允许的操作
    • copy 只允许copy这种dropEffect
    • link只允许lin k这种dropEffect
    • move只允许move这种dropEffect
    • copyLink允许copy和link这种dropEffect
    • copyMove允许copy和move这种dropEffect
    • linkMove允许link和move这种dropEffect
    • all允许所有dropEffect

5、可拖动能力

  • draggable属性,表示元素是否可以被拖动

6、其他成员

  • dataTransfer对象的其他方法
    • addElement(element)为拖动操作添加元素,为了传输数据,不影响操作外观。
    • clearData(format)清除以特定格式存储的数据
    • setDragImage(element, x, y)允许指定拖动时显示在光标下的图片
    • types当前存储的数据类型列表

7、Notifications API

1、通知权限

  • 通知只能在运行在安全上下文的代码中被触发
  • 通知必须按照每个源的原则明确得到用户允许
Notification.requestPermission()
  .then((permission) => {
    console.log('User responded to permission request:', permission);
  });
  • granted 表示授权了。 denied表示拒绝了。

2、显示和隐藏通知

  • new Notification('Title text!')

  • 返回的Notification对象调用close()方法关闭通知

3、通知生命周期回调

  • onshow 在通知显示时触发
  • onclick 在通知被点击时触发
  • onclose 在通知消失或通过close()关闭时触发
  • onerror 在发生错误阻止通知显示时触发

8、Page Visibility API

  • document.visibilityState
    • 表示下面四个状态
      • 页面在后台标签页中或最小化了
      • 页面在前台标签页中
      • 实际页面隐藏了,但对页面的预览是可见的
      • 页面在屏外预渲染
    • 可能的值: hidden / visible / prerender
  • visibilitychange事件会在文档切换是否可见时触发
  • document.hidden 表示页面是否隐藏

9、Streams API

  • 解决Web应用有序地消费小信息块而不是大块信息。
    • 大块信息可能不会一次性都可用。
    • 大块数据可能需要分小部分处理

1、理解流

  • Stream API定义了三种流
    • 可读流:通过某个公共接口读取数据块的流。数据在内部从底层源进入流,然后由消费者进行处理。
    • 可写流:可以通过某个公共接口写入数据块的流。生产者将数据写入流,数据在内部传入底层数据槽(sink)
    • 转换流:由两种流组成,可写流用于接收数据(可写端),可读流用于输出数据(可读端)。这两个流之间是转换程序,可以根据需要检查和修改流内容。
  • 块、内部队列和反压
    • 流的基本单位是块。块可是任意数据结构,通常是定型数组
    • 每个块都是离散的流片段,可以作为一个整体来处理。
    • 块不是固定大小的,也不一定按固定间隔到达。
    • 理想流当中,块的大小通常近似相同,到达间隔也近似相等。
    • 由于数据进出速率不同,可能出现不匹配情况,流平衡的情形:
      • 流出口处理数据的速度比入口提供的速度快,流出口经常空闲,会浪费内存或计算资源。
      • 流入和流出均衡,理想状态。
      • 流入口提供数据的速度比流出口处理数据速度快。这种不平衡是有固定的问题,一定会在某个位置出现数据积压,流必须相应作出处理。
    • 块入列速度快于出列速度,内部队列会不断增大。会使用反压通知流入会停止发送数据,知道降到某个既定的阈值之下,即高水位线

2、可读流

1、ReadableStreamDeafaultController

async function* ints() {
  for(let i = 0; i < 5; i++) {
    yield await new Promise((resolve)=> setTimeout(resolve, 100, i))
  }
}
// enqueue() 把值传入控制器  close() 关闭流
const readableStream = new ReadableStream({
  async start(controller) {
    for await (let chunk of ints()) {
      controller.enqueue(chunk)
    }
    controller.close()
  }
})

2、ReadableStreamDeafultReader

  • 通过流的getReader()方法可以获取ReadableStreamDeafultReader实例
  • 调用这个方法会获得流的锁,保证只有这个读取器可以从流中读取值。
...
readableStream.locked // fasle
const readableStreamDeafultReader = readableStream.getReader()
readableStream.locked // true
  • 消费者使用这个读取器实例的read()方法可以读出值
...
// 消费者
(async function() {
  while(true) {
    const {done, value} = await readableStreamDeafultReader.read()
    if(done) {
      break
    }else {
      console.log(value)
    }
  }
})()

3、可写流

  • 可写流是底层数据槽的封装,底层数据槽处理通过流的公共接口写入的数据

1、创建WritableStream

  • 通过可写流的公共接口可以写入流,在传给WritableStream构造函数的underlyingSink参数中,通过实现write()来获得写入的数据
async function* ints() {
  for(let i = 0; i < 5; i++) {
    yield await new Promise((resolve)=> setTimeout(resolve, 100, i))
  }
}

const wirtableStream = new WritableStream({
  write(value) {
    console.log(value)
  }
})

2、WritableStreamDefaultWriter

  • 要把获得的数据写入流,可以通过流的getWriter()方法获取WritableStreamDefaultWriter实例,这样会获得流的锁,保证只有一个写入器可以向流中写入数据,
...
writableStream.locked // false
const writableStreamDefaultWriter = wirtableStream.getWriter()
writableStream.locked // true
  • 在向流写入数据前,生产者必须确保写入器可以接收值。
  • writableStreamDefaultWriter.ready返回一个期约,此期约会在能够向流中写入数据时解决。
  • 然后可以把值传给writableStreamDefaultWriter.write()方法。写入数据后,可以调用writableStreamDeafultWriter.close()关闭流
...
// 生产者
(async function() {
  for await (let chunk of ints()) {
    await writableStreamDeafultWriter.ready
    writableStreamDeafultWriter.write(chunk)
  }
  writableStreamDeafultWriter.close()
})()

4、转换流

  • 用于组合可读流和可写流。数据块在两个流之间转换是通过transform()完成的
async function* ints() {
  for(let i = 0; i < 5; i++) {
    yield await new Promise((resolve)=> setTimeout(resolve, 100, i))
  }
}

const {writable, readable} = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk * 2)
  }
})

const readableStreamDefaultReader = readable.getReader()
const writableStreamDefaultWriter = writable.getWriter()

// 消费者async function() {
  while(true) {
    const {done, value} = await readableStreamDeafultReader.read()
    if(done) {
      break;
    }else {
      console.log(value)
    }
  }
})()

// 生产者
(async function() {
  for await (let chunk of ints()) {
    await writableStreamDefaultWriter.ready
    writableStreamDefaultWriter.write(chunk)
  }
  writableStreamDefaultWriter.close()
})()

5、通过管道连接流

  • 流可以通过管道连接成一串,pipeThrough()方法把ReadableStream接入TransformStream。
async function* ints() {
  for(let i = 0; i < 5; i++) {
    yield await new Promise((resolve)=> setTimeout(resolve, 100, i))
  }
}

const integerStream = new ReadableStream({
  async start(controller) {
    for await (let chunk of ints()) {
      controller.enqueue(chunk)
    }
    controller.close()
  }
})

const doublingStream = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk * 2)
  }
})

// 通过管道连接流
const pipedStream = integerStream.pipeThrough(doublingStream)

// 从连接流的输出获得读取器
const pipedStreamDeafaultReader = pipedStream.getReader()

// 消费者
(async function() {
  while(true) {
    const {done, value} = await pipedStreamDeafaultReader.read()
    
    if(done) {
      break
    }else {
      console.log(value)
    }
  }
})()
  • pipeTo()也可以将ReadableStream连接到WritableStream
async function* ints() {
  for(let i = 0; i < 5; i++) {
    yield await new Promise((resolve)=> setTimeout(resolve, 100, i))
  }
}

const integerStream = new ReadableStream({
  async start(controller) {
    for await (let chunk of ints()) {
      controller.enqueue(chunk)
    }
    controller.close()
  }
})

const writableStream = new WritableStream({
  write(value) {
    console.log(value)
  }
})

const pipedStream = integetStream.pipeTo(writableStram)
  • 这里管道连接操作隐式从ReadableStream获得了一个读取器,并把产生的值填充到WritableStream

10、计时API

1、High Resolution Time API

  • Date.now()不要求计时精度的操作

    const t0 = Date.now()
    foo()
    const t1 = Date.now()
    const duration = t1 - t0
    conosle.log(duration)
    
    • doration 是 0,因为Date.now()是毫秒级,如果操作够快可能是0
    • duration是负值或极大值,如果操作执行时调节了系统时间会导致。
  • 准确度量事件的流逝window.performance.now(),返回一个微秒精度的浮点值

    • 采用相对度量,在执行上下文创建时从0开始计时。
    • 不同上下文之间如果没有共享参照点不可能直接比较
  • performance.timeOrigin属性返回计时器初始化时全局系统时钟的值

2、Performance Timeline API

  • 用于度量客户端延迟的工具扩展了Performance接口。
  • 浏览器会自动记录各种PerformanceEntry对象,使用performance.mark()可以记录自定义的PerformanceEntry对象。
  • 在一个执行上下文中被记录的所有性能条目可以通过performance.getEntries()获取
  • 返回的集合代表浏览器的性能时间线。每个对象都有name、entryType、startTime和duration属性

1、User Timeing API

  • 用于记录和分析自定义性能条目,使用performance.mark()
  • performance.getEntriesByType('mark')[0]
  • 在计算开始前和结束后各建立一个自定义性能条目可以计算时间差,最新的标记会被推到getEntriesByType返回数组的开始。
  • 用于生成性能度量条目,使用performance.measure()
    • 参数:name、startMark、endMark

2、Navigation Timing API

  • 用来度量当前页面的加载速度。浏览器会在导航事件发生时自动记录该条目

3、Resource Timing API

  • 用来度量当前页面加载时请求资源的速度,浏览器会在加载资源时自动记录。

11、Web组件

1、HTML模版

  • 缺少基于HTML解析构建DOM子树,然后需要时在叭这个子树渲染出来的机制。
  • 间接方式innerHTML把标记字符串转换为DOM元素,存在安全隐患。
  • 另一种方式使用document.createElement()创建每个元素,然后逐个添加到孤儿根结点,但特别麻烦。
  • 更好的方式是提前在页面中写出特殊标记,让浏览器自动将其解析为DOM子树。
<template id="foo">
	<p>I'm inside a template!</p>
</template>

1、使用DocumentFragment

  • 上面的文本不会渲染,因为<template>的内容不属于活动文档,索引查询不到<p>标签,因为存在于一个包含HTML模版中的DocumentFragment节点内

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

  • DocumentFragment是批量向HTML添加元素的高效工具,可以一次性添加所有子节点,最多只有一次布局重排。

  • document.createDocumentFragment()

  • 转移后DocumentFragment也会变空。

2、使用<template>标签

const fooElement = document.querySelector('#foo');
const barTemplate = document.querySelector('#bar');
const barFragment = barTemplate.content;
console.log(document.body.innerHTML);
// <div id="foo">
// </div>
// <template id="bar">
// 	<p></p>
// 	<p></p>
// 	<p></p>
// </template>
fooElement.appendChild(barFragment);
console.log(document.body.innerHTML);
// <div id="foo">
// 	<p></p>
// 	<p></p>
// 	<p></p>
// </div>
// <tempate id="bar"></template> 
  • 可以使用importNode()克隆DocumentFragment

3、模版脚本

  • 脚本执行可以推迟到将DocumentFragment内容实际添加到DOM树。

2、影子DOM

  • 可以将一个完整的DOM树作为节点添加到父DOM树。可以实现DOM的封装,CSS样式和CSS选择符可以限制在影子DOM子树而不是整个顶级DOM树中。
  • 影子DOM会实际渲染到页面上,而HTML模版不会

1、理解影子DOM

  • 把CSS限制在使用它们的DOM上

2、创建影子DOM

  • 并非所有元素都可以包含影子DOM。

  • 通过attachShadow()创建并添加给有效HTML元素的。容纳影子DOM的元素称为影子宿主。影子DOM的根节点称为影子根

  • attachShadow()需要一个shadowRootInit对象,返回影子DOM实例。

    • shadowRootInit包含mode的属性,值为open或closed
      • open 影子DOM的引用可以通过shadowRoot属性在HTML元素上获取
      • closed 影子DOM的引用无法这样获取
  • 一般需要创建保密影子DOM场景很少。

3、使用影子DOM

  • 创建后可以像使用常规DOM一样使用影子DOM。
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并非铁板一块,HTML元素可以在DOM树间无限制移动。

4、合成与影子DOM槽位

  • 影子DOM一添加到元素中,浏览器就会赋予它最高优先级,优先渲染它的内容而不是原来的文本。
  • 为了显示文本内容,需要使用<slot>标签指示浏览器在哪里放置原来的HTML。
document.body.innerHTML = `
<div id="foo">
 <p>Foo</p>
</div>
`; 
document.querySelector('div')
 .attachShadow({ mode: 'open' })
 .innerHTML = `<div id="bar">
 								<slot></slot>
 							<div>` 
  • 投射进去的内容就像自己存在于影子DOM中一样。检查页面会发现原来的内容十几岁替代了<slot>
  • 页面上看到内容在影子DOM中,但实际上只是DOM内容的投射,实际的元素仍然处于外部DOM中。
  • 除了默认槽位,可以使用命名槽位实现多个投射
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 

5、事件重定向

  • 如果影子DOM发生了浏览器事件,则需要一种方式让父DOM处理事件。事件会逃出影子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 onclick="..."></button>
// Handled outside: <div onclick="..."></div> 

3、自定义元素

1、创建自定义元素

  • 默认会变成一个HTMLElement实例

  • createElements.define()创建自定义元素

    class FooElement extends HTMLElement {}
    createElements.define('x-foo', FooElement)
    
  • 如果继承自元素类,可以使用is属性和extends选项将该标签指定为自定义元素

    class FooElement extends HTMLDivElement {
      constructor() {
        super()
        console.log('x-foo')
      }
    }
    createElements.define('x-foo', FooElement)
    document.body.innerHTML = `
    <div is="x-foo"></div>
    <div is="x-foo"></div>
    <div is="x-foo"></div>
    `
    // x-foo
    // x-foo
    // x-foo
    

2、添加Web组件的内容

  • 可以在构造函数中添加影子DOM

3、使用自定义元素生命周期方法

  • constructor()在创建元素实例或将已有DOM元素升级为自定义元素时调用
  • connectedCallback()在每次将这个自定义元素实例添加到DOM时调用
  • disconnectedCallback()在每次将这个自定义元素实例从DOM中移除时调用
  • attributeChangedCallback()在每次可观察属性的值发生变化时调用
  • adoptedCallback() 在通过document.adoptNode()将自定义元素实例移动到新文档对象时调用。

4、反射自定义元素属性

  • 使用获取函数和设置函数
  • 使用observedAttribute()获取函数让每次属性值改变都调用attributeChangedCallback()

5、升级自定义元素

  • customElements.get()返回相应自定义元素的类
  • customElements.whenDefined()返回一个期约,当有定义之后解决
  • customElements.upgrade()强制升级

12、Web Cryprography API

  • 一套密码学工具,包括生成、使用和应用加密密钥对,加密和解密信息,以及可靠地生成随机数

1、生成随机数

  • Math.random() 伪随机数,只是模拟了随机特性,算法固定。不适合加密计算

  • 密码学安全伪随机数生成器

    • 额外增加了一个熵作为输入,如测试硬件事件或其他无法预计行为的系统特征。
    • 计算速度明显慢。但难以预测,适合加密。
  • cypto.getRandomValues()会把随机值作为参数传给它的定型数组。

    const array = new Uint8Array(1);
    
    for (let i=0; i<5; ++i) {
     console.log(crypto.getRandomValues(array));
    }
    // Uint8Array [41]
    // Uint8Array [250]
    // Uint8Array [51]
    // Uint8Array [129]
    // Uint8Array [35] 
    
  • 最多生成2 ** 16字节

  • 重新实现Math.random()

    function randomFloat() {
      const fooArray = new Unit32Array(1)
      const maxUnit32 = 0xFFFFFFFF;
      return crypto.getRandomValues(fooArray)[0] / maxUnit32
    }
    

2、使用SubtleCrypto对象

  • window.crypto.subtle访问,只能在https下使用

1、生成密码学摘要

  • SHA-1:类似MD5,接收任意大小输入,生成160位消息散列,不安全。

  • SHA-2:相同耐碰撞单向压缩函数之上的一套散列函数,SHA-256、SHA-384、SHA-512,安全的。

  • SubtleCrypto.digest()用来生成消息摘要

2、CryptoKey与算法

  • CryptoKey支持多种加密算法,允许控制密钥抽取和使用
    • RSA:公钥密码系统,使用两大素数获得一对公钥和私钥。
    • ...

3、生成CryptoKey

  • SubtleCrypto.generateKey()生成随机CryptoKey。
  • 使用时需要给这个方法传入
    • 一个指定目标算法的参数对象
      • RSA RsaHashedKeyGenParams
      • ECC EcKeyGenParams
      • HMAC HmacGenParams
      • AES AesKeyGenParams
    • 一个表示密钥是否可以从CryptoKey对象中提取出来的布尔值
    • 一个表示这个密钥与哪个SubtleCrypto方法一起使用的字符串数组
      • encrypt、decrypt、sign、verify、deriveKey、deriveBits、wrapKey、unwrapKey

4、导出和导入密钥

  • exportKey()并制定目标格式(raw、pkcs8、spki或jwk)可以取得密钥
  • importKey()是与exportKey()相反的操作

5、从主密钥派生密钥

  • 通过可配置的属性从已有密钥获得新密钥

  • deriveKey()返回一个解决为CryptoKey的期约

  • deriveBits()返回一个解决为ArrayBuffer的期约

6、使用非对称密钥签名和验证消息

  • SubtleCrypto.sign() 签名 私钥签名
  • SubtleCrypto.verify() 验证 公钥验证

7、使用对称密钥加密和解密

  • SubtleCrypto.encrypt() 加密
  • SubtleCrypto.decrypt() 解密

8、包装和解包密钥

  • SubtleCrypto.wrapKey() 包装
  • SubtleCrypto.unwrapKey() 解包