夜听城嚣 夜听城嚣
首页
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 前端基建与架构
  • 专题分享

    • Git入门与开发
    • 前端面试题汇总
    • HTML和CSS知识点
  • 项目实践
  • 抓包工具
  • 知识管理
  • 工程部署
  • 团队规范
bug知多少
  • 少年歌行
  • 青年随笔
  • 文海泛舟
  • 此事躬行

    • 项目各工种是如何协作的
    • TBA课程学习
收藏

dwfrost

前端界的小学生
首页
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 前端基建与架构
  • 专题分享

    • Git入门与开发
    • 前端面试题汇总
    • HTML和CSS知识点
  • 项目实践
  • 抓包工具
  • 知识管理
  • 工程部署
  • 团队规范
bug知多少
  • 少年歌行
  • 青年随笔
  • 文海泛舟
  • 此事躬行

    • 项目各工种是如何协作的
    • TBA课程学习
收藏
  • 第1章 什么是JavaScript
  • 第2章 HTML中的JavaScript
  • 第3章 语言基础
  • 第4章 变量、作用域与内存
  • 第5章 基本引用类型
  • 第6章 集合引用类型
  • 第7章 迭代器与生成器
  • 第8章 对象、类与面向对象编程
  • 第9章 代理与反射
  • 第10章 函数
  • 第11章 异步编程
  • 第12章 BOM
  • 第14章 DOM
  • 第15章 DOM扩展
  • 第16章 DOM2和DOM3
  • 第17章 事件
  • 第18章 动画与Canvas图形
  • 第19章 表单脚本
  • 第20章 JavaScript API
    • 第21章 错误处理与调试
    • 第23章 JSON
    • 第24章 网络请求与远程资源
    • 第25章 客户端存储
    • 第26章 模块
    • 第27章 工作者进程
    • 第28章 最佳实践
    • 《JavaScript高级程序设计》
    frost
    2022-02-23

    第20章 JavaScript API

    js的API非常丰富,但不一定都被浏览器实现了。本节选择Web开发中较常使用的API进行描述。

    # File API与Blob API

    浏览器可以通过<input type="file">来处理文件。

    # File类型

    当用户通过input选择一个或多个文件时,事件处理程序将得到files数组,每个file对象的属性有

    • name:本地系统中的文件名。
    • size:文件大小,单位是字节。
    • type:包含文件MIME类型的字符串。
    <input type="file" />
    <script>
      const input = document.querySelector('input')
      input.addEventListener('change', (event) => {
        const { files } = event.target
        for (let file of files) {
          console.log('name:', file.name) // name: JavaScript高级程序设计(第4版).pdf
          console.log('size:', file.size) // size: 14354022
          console.log('type:', file.type) // type: application/pdf
        }
      })
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # FileReader类型

    FileReader类型表示一种异步文件读取机制。可以从文件系统读取文件,它有几个方法:

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

    这几个方法可以用来灵活处理文件数据,比如想要得到图片,可以读取为数据URI;而想要解析文件内容,可以读取为文本。

    读取方法是异步的,可以监听到一些有用的事件,如

    • progress事件,每50ms触发一次。
    • error事件,读取文件发生异常时触发。
    • load事件,在文件加载成功后触发。

    下面这个demo介绍了这3个事件的使用。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <style>
          .progress {
            width: 200px;
            height: 30px;
            border: 1px solid;
            background-color: #fff;
          }
          .bar {
            width: 0;
            height: 100%;
            background-color: lightgreen;
          }
        </style>
      </head>
      <body>
        <input type="file" />
    
        <div class="progress">
          <div class="bar"></div>
        </div>
    
        <div class="content"></div>
      </body>
    </html>
    
    <script>
      const input = document.querySelector('input')
      const bar = document.querySelector('.bar')
      const content = document.querySelector('.content')
    
      input.addEventListener('change', (event) => {
        const {
          files: [file],
        } = event.target
        console.log('file', file)
        const { size, type } = file
    
        const isImg = /image/.test(type)
        console.log(isImg)
    
        let reader = new FileReader()
        if (isImg) {
          reader.readAsDataURL(file)
        } else {
          reader.readAsText(file)
        }
    
        reader.onerror = (e) => {
          console.log('e', e)
          console.log('error code', reader.error.code)
        }
        reader.onprogress = (ev) => {
          console.log('progress', ev)
          console.log(ev.loaded)
          const width = ev.loaded / ev.total
    
          console.log('width', width * 100 + '%')
          bar.style.width = width * 100 + '%'
        }
        reader.onload = (ev) => {
          console.log('onload', ev)
    
          let html = ''
    
          if (isImg) {
            html = `<img src="${reader.result}" />`
          } else {
            html = `<p>${reader.result}}</p>`
          }
          content.innerHTML = html
        }
      })
    </script>
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77

    # FileReaderSync类型

    是FileReader的同步版本。

    # Blob数据

    Blob是File的超类。它表示二进制大对象,不可修改。常用于数据切分和转化。

    file对象继承了blob对象的slice方法,可以对数据进行切分。

    注意红宝书此处有误(blobSlice),参考 (opens new window)。

        <input type="file" />
        <div class="content"></div>
    
    <script>
      const input = document.querySelector('input')
      const content = document.querySelector('.content')
    
      input.addEventListener('change', (event) => {
        const {
          files: [file],
        } = event.target
        console.log('file', file)
        const { size, type } = file
    
        // 切分前32字节
        const reader = new FileReader()
        const blobSlice = file.slice(0, 32) // 此处红宝书上有误,已修改
    
        if (blobSlice) {
          reader.readAsText(blobSlice)
          reader.onerror = () => {
            console.log('error')
          }
          reader.onload = () => {
            console.log('load', reader.result)
            content.innerHTML = reader.result
          }
        }
      })
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    # 对象URL与Blob

    对象URL即Blob URL,是指引用存储在File或Blob中数据的URL。

    它的优点在于,只需要提供对象URL,那么就可以在任何js中进行使用,而不需要每次都读取文件。

    创建对象URL:使用window.URL.createObjectURL,传入File或Blob对象,返回一个指向内存地址的字符串。这个内存地址可以直接在DOM中使用。

    如果不想使用URL了,可以调用window.URL.revokeObjectURL()释放内存。

        <input type="file" />
        <div class="content"></div>
    
    <script>
      const input = document.querySelector('input')
      const content = document.querySelector('.content')
    
      input.addEventListener('change', (event) => {
        const {
          files: [file],
        } = event.target
        console.log('file', file)
        const { size, type } = file
    
        const isImg = /image/.test(type)
        console.log(isImg)
    
        if (isImg) {
          const url = window.URL.createObjectURL(file)
    
          const img = document.createElement('img')
          img.src = URL.createObjectURL(file)
          img.height = 60
          img.onload = function () {
            URL.revokeObjectURL(this.src)
          }
          // 注意:直接使用innerHTML不会生效
          // content.innerHTML = `<img src="${url}}" />`
          content.appendChild(img)
        }
      })
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    # 媒体元素

    HTML5新增了<audio>和<video>标签来支持音频和视频。

    此外还可以直接使用Audio构造函数,这样就不用插入DOM即可调用。

    不过,由于h5兼容性,需要用户交互(点击)后才能播放。

      <body>
        <button class="play">play</button>
        <button class="pause">pause</button>
      </body>
    <script>
      let audio = new Audio('https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-hello-uniapp/2cc220e0-c27a-11ea-9dfb-6da8e309e0d8.mp3')
    
      const playBtn = document.querySelector('.play')
      const pauseBtn = document.querySelector('.pause')
      playBtn.onclick = function(){
        audio.play()
      }
      pauseBtn.onclick = function(){
        audio.pause()
      }
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    # 原生拖放

    拖放过程中有2类元素:被拖放元素和放置目标。

    # 拖放事件

    某个元素被拖动时,会触发下面的事件:

    • dragstart,按住鼠标并开始拖动的那一刻触发。
    • drag,只要目标还在被拖动就会持续触发,类似于mousemove。
    • dragend,当拖动停止时,会触发该事件。

    把元素拖动到一个有效的放置目标上时,会依次触发以下事件:

    • dragenter,把元素拖动到放置目标上时触发。
    • dragover,元素在放置目标范围内被拖动期间会持续触发。
    • dragleave,当元素被拖动到放置目标外触发。
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <style>
          div {
            width: 100px;
            height: 100px;
          }
          .red {
            background-color: red;
          }
          .blue {
            background-color: blue;
          }
        </style>
      </head>
      <body>
        <div class="red"></div>
        <div class="blue"></div>
      </body>
    </html>
    
    <script>
      const red = document.querySelector('.red')
      const blue = document.querySelector('.blue')
    
      red.onclick = function () {
        console.log(11)
      }
    
      red.ondragstart = function () {
        console.log('dragstart')
      }
      red.ondrag = function (e) {
        console.log('drag', e.x, e.y)
      }
      red.ondragend = function () {
        console.log('dragend')
      }
      blue.ondragenter = function (e) {
        e.preventDefault()
        console.log('dragenter')
      }
      blue.ondragover = function (e) {
        e.preventDefault()
        console.log('dragover', e.x, e.y)
      }
      blue.ondragleave = function () {
        console.log('dragleave')
      }
    </script>
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

    注意,元素默认是不允许放置的。可以通过阻止默认事件来修改,把元素转换为有效的放置目标。

    # dataTransfer对象

    在拖动过程中,event.dataTransfer对象用于从被拖动元素想放置目标传递字符串数据。

    dataTransfer对象有2个主要方法: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')
    
    1
    2
    3
    4
    5
    6
    7

    HTML5除了这2种类型,还扩展到了允许任何MIME类型。

    # 可拖动

    通常只有图片、链接和文本是可拖动的。如果想让其他元素具有可拖动能力,可以设置draggable属性为true。

    # Page Visibility API

    页面是否可见,可以监听visivilitychange事件。

    document.addEventListener('visibilitychange', function () {
        console.log(document.visibilityState)
        document.title = document.hidden ? '用户离开了' : '用户回来了'
      })
    
    1
    2
    3
    4

    document.visibilityState的值有3种:

    • 'hidden'
    • 'visible'
    • 'prerender'

    # Streams API (略)

    # Performance API

    Performance API 用于度量页面性能。

    # High Resolution Time API

    可以用来记录代码的执行时间。

    • performance.now(),返回一个微秒精度的浮点值。它采用相对度量时间。
    • Date.now(),与上一个相比,只有毫秒级精度,且如果系统时钟被调整,会受到影响。
      // performance.now()
      const t0 = performance.now()
      for (let i = 0; i < 100; i++) {
        const j = i * 2
      }
      const t1 = performance.now()
    
      console.log(t0) // 18.19999998807907
      console.log(t1) // 18.5
      console.log(t1 - t0) // 0.30000001192092896
    
      // Date.now()
      const t01 = Date.now()
      for (let i = 0; i < 100; i++) {
        const j = i * 2
      }
      const t11 = Date.now()
    
      console.log(t01) // 1647739237084
      console.log(t11) // 1647739237084
      console.log(t11 - t01) // 0
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # Performance Timeline API

    PerformanceEntry对象记录了浏览器的时间性能属性,也可以自定义。

    其中默认的PerformanceNavigationTiming会捕获大量时间戳,用于描述页面是何时加载的。

     // 浏览器的PerformanceEntry对象,是浏览器性能时间栈
      const [entry] = performance.getEntries()
      console.log(entry)
      // {
      //   connectEnd: 1.5999999642372131,
      //   connectStart: 1.5999999642372131,
      //   decodedBodySize: 0,
      //   domComplete: 1872.7999999523163,
      //   domContentLoadedEventEnd: 1872.7999999523163,
      //   domContentLoadedEventStart: 1872.7999999523163,
      //   domInteractive: 1872.699999988079,
      //   domainLookupEnd: 1.5999999642372131,
      //   domainLookupStart: 1.5999999642372131,
      //   duration: 1872.8999999761581,
      //   encodedBodySize: 0,
      //   entryType: 'navigation',
      //   fetchStart: 1.5999999642372131,
      //   initiatorType: 'navigation',
      //   loadEventEnd: 1872.8999999761581,
      //   loadEventStart: 1872.8999999761581,
      //   name: '',
      //   nextHopProtocol: '',
      //   redirectCount: 0,
      //   redirectEnd: 0,
      //   redirectStart: 0,
      //   requestStart: 1.5999999642372131,
      //   responseEnd: 5.099999964237213,
      //   responseStart: 1.5999999642372131,
      //   secureConnectionStart: 0,
      //   serverTiming: [],
      //   startTime: 0,
      //   transferSize: 300,
      //   type: 'reload',
      //   unloadEventEnd: 0,
      //   unloadEventStart: 0,
      //   workerStart: 0,
      // }
    
      // 自定义的PerformanceEntry对象
      performance.mark('foo')
      for (let i = 0; i < 1e6; i++) {}
      performance.mark('bar')
    
      const [startMark, endMark] = performance.getEntriesByType('mark')
      console.log(endMark.startTime - startMark.startTime) // 2.100000023841858
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45

    此外,还可以使用Resource Timing API来度量当前页面加载时请求资源的速度。

    Resource_Timing_API (opens new window)

     const [entry] = performance.getEntriesByType('resource')
      console.log(entry)
      // {
      //   connectEnd: 0,
      //   connectStart: 0,
      //   decodedBodySize: 0,
      //   domainLookupEnd: 0,
      //   domainLookupStart: 0,
      //   duration: 824.3999999761581,
      //   encodedBodySize: 0,
      //   entryType: 'resource',
      //   fetchStart: 9.199999988079071,
      //   initiatorType: 'link',
      //   name: 'https://vuex.vuejs.org/assets/style.34a39eef.css',
      //   nextHopProtocol: '',
      //   redirectEnd: 0,
      //   redirectStart: 0,
      //   requestStart: 0,
      //   responseEnd: 833.5999999642372,
      //   responseStart: 0,
      //   secureConnectionStart: 0,
      //   serverTiming: [],
      //   startTime: 9.199999988079071,
      //   transferSize: 0,
      //   workerStart: 0,
      // }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    # Web组件

    指用于增强DOM行为的工具,还没有形成统一规范。包括HTML模板、影子DOM、自定义元素。

    # HTML模板

    其核心内容是:

    • 可以让浏览器解析为DOM树
    • 但不会渲染
    • 可以批量向HTML中转移

    这个特性和fragment非常像,不过实现方式是使用HTML模板,如下:

        <template id="foo">
          <p>I am a p tag</p>
        </template>
    
    1
    2
    3

    它不会真实渲染,但会存在于DOM上,表现为:

        <template id="foo">
          #document-fragment
            <p>I am a p tag</p>
        </template>
    
    1
    2
    3
    4

    可以这样获取到这个DocumentFragment:

      const foo = document.querySelector('#foo')
      console.log(foo.content) // #document-fragment
    
    1
    2

    下面演示了将fragment添加到dom元素上的情形,注意fragment的dom操作不会重排布局。

      <body>
        <template id="foo">
          <p>I am a p tag</p>
        </template>
        <div class="box"></div>
      </body>
    <script>
      const foo = document.querySelector('#foo')
      const box = document.querySelector('.box')
      box.appendChild(foo.content)
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    再检查元素,发现结构发生了变化。

      <body>
        <template id="foo">
          #document-fragment
        </template>
        <div class="box">
          <p>I am a p tag</p>
        </div>
      </body>3
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 影子DOM

    影子DOM与HTML模板类似,都是类document结构;区别在于影子DOM的内容会实际渲染到页面上,而HTML模板不会。

    • 影子宿主:容纳影子DOM的元素

    • 影子根:影子DOM的根节点

    • attachShadow(), 创建影子DOM,入参必须传mode属性,可能的值有open和closed。注意closed类型的影子

    微信小程序的微信开发者工具,就应用了shadow-dom

        <div class="host"></div>
    <script>
      const host = document.querySelector('.host')
      const openShadow = host.attachShadow({ mode: 'open' })
      openShadow.innerHTML = '<div>hello</div>'
      console.log(openShadow)
      console.log(openShadow === host.shadowRoot) // true
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8

    检查元素如下

    <div class="host">
      #shadow-root(open)
        <div>hello</div>
    </div>
    
    1
    2
    3
    4

    影子DOM使用的初衷是限制css的作用范围,目的是指让css在影子DOM范围内生效。先看下没有影子DOM的场景:

    <style>
    			.red {
            color: red;
          }
          .blue {
            color: blue;
          }
          .green {
            color: green;
          }
    </style>
    
    		<div>
          <p class="red">red</p>
        </div>
        <div>
          <p class="blue">blue</p>
        </div>
        <div>
          <p class="green">green</p>
        </div>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    可以看到,通过设置类名,然后对每个类设置样式,就设置不同的样式了。不过这些类名相当于全局样式,也会影响到其他样式。而影子DOM是这样:

     for (let item of ['red', 'blue', 'green']) {
        const host = document.createElement('div')
        document.body.appendChild(host)
        const openShadow = host.attachShadow({ mode: 'open' })
        openShadow.innerHTML = `
          <p>${item}</p>
          <style>
            p {
              color: ${item};
            }
          </style>
        `
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    影子DOM是为自定义Web组件设计的,和Vue类似,它拥有自己的插槽,可以将影子宿主中的HTML投射到影子DOM中。

      for (let item of ['red', 'blue', 'green']) {
        const host = document.createElement('div')
        host.innerText = item
        document.body.appendChild(host)
        const openShadow = host.attachShadow({ mode: 'open' })
        openShadow.innerHTML = `
          <p><slot></slot></p>
          <style>
            p {
              color: ${item};
            }
          </style>
        `
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    除了默认slot,还有命名slot,如:

      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">haha</slot>
      <slot name="foo">heihei</slot>
      `
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    用法和Vue语法类似。

    影子DOM事件经过浏览器处理,会发生事件重定向。最终的结果是,宿主和影子DOM可以触发各自的事件,如下:

      document.body.innerHTML = `
      <div onclick="console.log('div:',event.target)">out</div>
      `
    
      document.querySelector('div').attachShadow({ mode: 'open' }).innerHTML = `
      <button onclick="console.log('button:',event.target)">inner</button>
      `
    
    1
    2
    3
    4
    5
    6
    7
    button: <button onclick=​"console.log('button:​',event.target)​">​inner​</button>​
    
    div: <div onclick=​"console.log('div:​',event.target)​">​#shadow-root (open)"out"</div>​
    
    1
    2
    3

    # 自定义元素

    自定义元素为Web开发提供了更多可能:

    • 可以在自定义标签出现时为它定义复杂的行为
    • 可以在DOM中将其纳入元素生命周期管理
    • 要使用全局属性customElements

    1.创建自定义元素

    如下创建了一个简单的自定义元素:

        <x-foo>hi</x-foo>
        <x-foo>hello</x-foo>
        <x-foo>hey</x-foo>
        <div is="y-foo">y-foo</div>
    
    <script>
      // 调用customElements.define()创建自定义元素 例x-foo
      class FooElement extends HTMLElement {
        constructor() {
          super()
          console.log('hello x-foo')
        }
      }
      customElements.define('x-foo', FooElement)
      console.log(document.querySelector('x-foo') instanceof FooElement)
      
      // 如果自定义元素继承了一个元素类,则可以使用is和extends选项将标签指定为该自定义元素 例y-foo
      class YFooElement extends HTMLDivElement {
        constructor() {
          super()
          console.log('hi y-foo')
        }
      }
      customElements.define('y-foo', YFooElement, { extends: 'div' })
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    2.结合Web组件

    接下来,将HTML模板、影子DOM和自定义元素结合使用,发挥Web组件的威力。

    首先为自定义元素添加影子DOM:

        <x-foo>hi</x-foo>
    <script>
      class FooElement extends HTMLElement {
        constructor() {
          super()
          this.attachShadow({ mode: 'open' })
          this.shadowRoot.innerHTML = `
          <p>I am custom</p>
          `
        }
      }
      customElements.define('x-foo', FooElement)
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    为避免使用innerHTML(导致维护困难),可以加入HTML模板。

      <body>
        <template id="x-foo-tpl">
          <p>I am custom</p>
        </template>
        <x-foo></x-foo>
    <script>
      class FooElement extends HTMLElement {
        constructor() {
          super()
          this.attachShadow({ mode: 'open' })
          const xFooTpl = document.querySelector('#x-foo-tpl')
          this.shadowRoot.appendChild(xFooTpl.content.cloneNode(true))
        }
      }
      customElements.define('x-foo', FooElement)
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    如此,html和js的职能分开,可以实现可复用的组件。

    3.自定义元素的生命周期

    • constructor():在创建元素实例时调用。
    • connectedCallback():在自定义元素挂载到DOM上时调用。
    • disconnectedCallback():在自定义元素从DOM移除时调用。
    • attributeChangedCallback():在每次可观察属性的值发生变化时调用。
    • adoptedCallback():在通过document.adoptNode()将这个自定义元素实例移动到新文档对象时调动。
      class FooElement extends HTMLElement {
        // constructor...
        connectedCallback() {
          console.log('connected')
        }
        disconnectedCallback() {
          console.log('disconnected')
        }
      }   
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    4.反射自定义元素属性

    DOM实体和js对象的属性应该同步变化。

    • js对象反射到DOM,可以通过getter和setter函数进行同步
    • DOM到js对象,需要使用observedAttributes()返回自定义元素属性的值,从而在attributeChangedCallback触发后进行修改。
    class FooElement extends HTMLElement {
        constructor() {
          super()
          this.attachShadow({ mode: 'open' })
          const xFooTpl = document.querySelector('#x-foo-tpl')
          this.shadowRoot.appendChild(xFooTpl.content.cloneNode(true))
    
          this.bar = 1
        }
        get bar() {
          return this.getAttribute('bar')
        }
        set bar(value) {
          this.setAttribute('bar', value)
        }
        static get observedAttributes() {
          return ['bar']
        }
        connectedCallback() {
          console.log('connected')
        }
        disconnectedCallback() {
          console.log('disconnected')
        }
        attributeChangedCallback(name, oldV, newV) {
          console.log('attributeChanged')
          console.log('name', name)
          console.log('oldV', oldV)
          console.log('newV', newV)
          if (oldV !== newV) {
            this[name] = newV
          }
        }
      }
      customElements.define('x-foo', FooElement)
    
      const el = document.querySelector('x-foo')
      el.setAttribute('bar', 2)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    5.升级自定义元素(略,目前还是草案)

    # Web Cryptography API

    # 生成随机数

    通常人们使用Math.random()来生成随机数,这种方式是伪随机,随机数顺序是确定的,如果知道其内部状态,就可以预测后续生成的伪随机值。所以不建议用来对数据加密。

    在伪随机的基础上,额外增加一个熵作为输入,这样生成的值就很难预测了,可以用于加密。

     function randomFloat() {
        // 生成32位随机值
        const array = new Uint32Array(1)
    
        // 最大值是2^32-1
        const maxUnit32 = Math.pow(2, 32) - 1
    
        // 用最大可能的值来除
        return crypto.getRandomValues(array)[0] / maxUnit32
      }
    
      console.log(randomFloat())
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    使用SubtleCrypto对象(略)

    #读书笔记
    上次更新: 2022/03/31, 08:04:02
    第19章 表单脚本
    第21章 错误处理与调试

    ← 第19章 表单脚本 第21章 错误处理与调试→

    最近更新
    01
    提交代码时修改commit消息
    04-09
    02
    如何快速定位bug
    02-20
    03
    云端web项目开发踩坑
    08-25
    更多文章>
    Theme by Vdoing | Copyright © 2021-2025 dwfrost | 粤ICP备2021118995号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    ×