美文网首页
新兴的 API

新兴的 API

作者: 了凡和纤风 | 来源:发表于2019-08-24 17:40 被阅读0次

    本章内容:创建平滑的动画、操作文件、使用 Web Workers 在后台执行 JavaScript

    随着 HTML5的出现,面向未来 Web 应用的 JavaScript API 也得到了极大的发展。他们都属于 “HTML5 相关的 API”

    一、requestAnimationFrame()

    长时间以来,计时器和循环间隔一直都是 JavaScript 动画的核心技术。虽然 CSS 变换及 动画为 Web 开发人员提供了实现动画的简单手段,但 JavaScript 动画开发领域的状况这些年并没有太大的变化。

    1.1、早期动画循环

    在 JavaScript 中创建动画的典型方式,就是使用 setInterval() 方法来控制所有动画。
    以下是一个 使用 setInterval() 的基本动画循环。

    ;(function() {
      function updateAnimations() {
        doAnimation1()
        doAnimation1()
      }
    
      setInterval(updateAnimations, 100)
    })()
    

    编写这种动画循环的关键是要知道延迟时间多长合适。
    一方面,循环间隔必须足够短,这样才能让不同的动画效果显得更平滑流程;
    另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。

    大多数电脑显示器的刷新评论是 60Hz,相当于每秒钟重绘60次。因此,最平滑的最佳循环间隔是 1000ms/60,约等于17ms。这个速度最接近浏览器的最高限速。

    1.2、循环间隔的问题

    知道什么时候绘制下一帧是保证动画平滑的关键。随着 <canvas> 元素越来越流行,新的基于浏览器的游戏也开始展露头角,面对不十分精确的 setInterval() 和 setTimeout(),开发人员一筹莫展。

    浏览器使用的计时器的精度进一步恶化了问题。浏览器使用的计时器并非精确到毫秒级别的。以下是几个浏览器的计时器精度

    • IE8 及更早版本的计时器精度为 15.625ms
    • IE9 及更晚版本的计时器精度为 4ms
    • Firefox 和 Safari 的计时器精度大约为 10ms
    • Chrome 的计时器精度为 4ms

    1.3、mozRequestAnimationFrame

    Mozilla 的 Robert O'Callahan 认识到了这个问题,提出了一个非常独特的方案。他指出,CSS变换和动画的优势在于浏览器知道动画什么时候开始,因此会计算出正确的循环间隔,在恰当的时候刷新UI。而对于 JavaScript 动画,浏览器无从知晓什么时候开始。因此他的方案就是创造一个新方法 mozRequestAnimationFrame(),通过它告诉浏览器某些 JavaScript 代码将要执行动画。这样浏览器可以在某些代码后进行适当的优化。

    mozRequestAnimationFrame() 方法接受一个参数,即在重绘屏幕前调用一个函数。这个函数负责改变下一次重绘时的 DOM 样式。
    为了创建动画循环,可以像以前使用 setTimeout() 一样,把多个对 mozRequestAnimationFrame() 的调用连缀起来。

      function updateProgress() {
        var div = document.getElementById('status')
        div.style.width = (parseInt(div.style.width, 10) + 5) + '%'
    
        if (div.style.left != '100%') mozRequestAnimationFrame(updateProgress)
      }
    
      mozRequestAnimationFrame(updateProgress)
    

    mozRequestAnimationFrame() 参数的函数也接受一个参数,它是一个时间码(从 1970 年 1月 1 日 起至今的毫秒数),表示下一次重绘的时间。 mozRequestAnimationFrame() 会根据这个时间码设定将来的某个时刻进行重绘,而根据这个时间码,你也能知道那个时刻是什么时间。然后,再优化动画效果就有了依据。

    通过 mozAnimationStartTime,可以知道上一次重绘的时间码

      function draw(timestamp) {
    
        // 计算两次重绘的时间
        var diff = timestamp - startTime
    
        // 使用diff 确定下一步的绘制时间
    
        // 把 startTime 重写为这一次的绘制时间
        startTime = timestamp
    
        // 重绘 UI
        mozRequestAnimationFrame(draw)
      }
    
      var startTime = mozAnimationStartTime
      mozRequestAnimationFrame(draw)
    

    第一次读取 mozAnimationStartTime 的值,必须在传递给 mozRequestAnimationFrame() 的回调函数外面进行。如果是在回调函数内部读取 mozAnimationStartTime, 得到的值与传入的时间码是相等的。

    1.4、webkitRequestAnimationFrame 与 msRequestAnimationFrame

    基于 mozRequestAnimationFrame(),Chrome 和 IE10+ 也都给出了自己的实现。这两个版本与 Mozilla 的版本有两个方面的微小差异。

    1. 不会给回调函数传递时间码,因此你无法知道下一次重绘将发生在什么时间。
      对应的也没有提供 mozAnimationStartTime 类似的属性
    2. Chrome 又增加了第二个可选的参数,即将要发生变化的 DOM 元素。知道了重绘将发生在页面中哪个特定元素的区域内,就可以将重回限制在该区域在。

    此外,Chrome 还提供了一个 webkitCancelAnimationFrame(),用于取消之前计划执行的重绘操作。

    假如你不需要知道精确的时间差,那么可以在 Firefox4+、IE10+、Chrome 中可以参考一下模式创建动画

      ;(function() {
        function draw(timestamp) {
    
          // 计算两次重绘的时间间隔
          var drawStart = (timestamp || Date.now()),
              diff = drawStart - startTime
          // 使用 diff 确定下一步的绘制时间
          console.log(diff)
          // 把 startTime 重写为这一次的绘制时间
          startTime = drawStart
    
          // 重绘 UI
          requestAnimationFrame(draw)
        }
    
        var requestAnimationFrame = window.requestAnimationFrame ||
                                    window.mozRequestAnimationFrame ||
                                    window.msRequestAnimationFrame ||
                                    window.webkitRequestAnimationFrame,
            startTime = window.mozAinmationStartTime || Date.now()
    
        requestAnimationFrame(draw)                                    
      })()
    

    目前,requestAnimationFrame() API,已经标准化,支持它的浏览器有 IE10+、Firefox11+、Chrome22+、Safari6+、Opera15+; Firefox4-10 需要 moz 前缀,Chrome10-21 需要 webkit前缀

    二、Page Visbility API

    不知道用户是不是正在与页面交互,这是困扰广大 Web 开发人员的一个主要问题。如果页面最小化 或者隐藏在了 其他标签页后面,那么有些功能是可以停下来的,比如轮询服务器或者某些动画效果。而 Page Visibility API(页面可见性 API)就是为了让开发人员知道页面是否对用户可见而推出的。

    这个 API 本身非常简单,由以下三部分组成:

    • document.hidden:表示页面是否隐藏的布尔值。页面隐藏包括页面在后台标签页中或者浏览器最小化
    • document.visiblityState:表示下列 4个可能状态的值
      • 页面在后台标签中中或浏览器最小化。
      • 页面在前台标签页中
      • 实际的页面已经隐藏,但用户可以看到页面的预览(就像在 win7 中,用户把鼠标移动到 任务栏的图标上,就可以显示浏览器中当前页面的预览)。
      • 页面在屏幕外执行预渲染处理
    • visibilitychange 事件:当文档从可见变为不可见或从不可见变为可见时,触发该事件。

    检查浏览器是否支持这个 API 的最佳方式如下:

    function isHiddenSupported() {
      return ('hidden' in document || 'msHidden' in document || 'webkitHidden' in document)
    }
    

    类似的,使用同一的模式可以检测页面是否隐藏:

    if (document.hidden || document.msHidden || document.webkitHidden ) {
      // 页面隐藏了
    } else {
      // 页面未隐藏
    }
    

    以上代码在不支持该 API 的浏览器中会提示页面隐藏。这个 Page Visibility API 有意设计的结果,目的是为了向后兼容


    可以像下面的例子一样,为每个事件都指定相同的事件处理程序:

    function handleVisibilityChange() {
    
      if (document.hidden || document.msHidden || document.webkitHidden) {
        console.log('Page is now hidden. ' + (new Date()))
      } else {
        console.log('Page is now visible. ' + (new Date()))
      }
      
    }
    
    document.onvisibilitychange = handleVisibilityChange
    document.onmsvisibilitychange = handleVisibilityChange
    document.onwebkitvisibilitychange = handleVisibilityChange
    

    document.visibilityState 属性的状态值:

    • hidden
    • visible
    • prerender
    • unloaded

    三、Geolocation API

    地理位置(geolocation)是一个得到广泛支持的新API。通过这套 API,JavaScript 代码能够访问到用户的当前位置信息。当然,访问之前必须得到用户的明确许可,即同意在页面中共享其位置信息

    Geolocation API 在浏览器中的实现是 navigator.geolocation 对象,这个对象包含3个办法:

    • getCurrentPosition(),调用这个方法就会触发请求用户共享地理定位信息的对话框。
      这个方法接受三个参数:成功的回调、可选的失败的回调函数、可选的选项对象

      • 成功的回调函数 会接收到一个 Positon 对象参数,该对象有两个属性:coordstimestamp

        • coords 对象包含下列与位置相关的信息。
          • latitude:以十进制度数表示的维度
          • longitude:以十进制度数表示的精度
          • accuracy:经、维度坐标的精度,以米为单位
            有些浏览器还可能会在 coords 对象中提供如下属性:
          • altitude:以米为单位的海拔高度,如果没有相关数据则值为 null
          • altitudeAccuracy:海拔高度的精度,以米为单位,数值越大越不精确
          • heading:指南针的方向,0° 表示正北,值为 NaN 表示没有检测到数据
          • speed:速度,即每秒移动多少米,如果没有相关数据则值为 null。

    在实际开发中,latitude 和 longitude 是大多数 Web 应用最常用到的属性。例如,以下代码将在地图上绘制用户的位置

    navigator.geolocation.getCurrentPosition(function (position) {
      drawMapCenteredAt(position.coords.latitude, position.coords.longitude)
    })
    
    • 失败的回调 接收到一个参数,在这个参数是一个对象,包含两个属性:
      • message:错误信息
      • code:错误类型
        • 用户拒绝共享(1)
        • 位置无效(2)
        • 超时(3)
    navigator.geolocation.getCurrentPosition(function (position) {
      drawMapCenteredAt(position.coords.latitude, position.coords.longitude)
    }, function(error) {
      console.log('Error code: ' + error.code)
      console.log('Error message' + error.message)
    })
    
    • 第三个参数是一个选项对象,用于设定信息的类型。可供设置的选项有三个:
      • enableHighAccuracy:布尔值,表示必须尽可能使用最精确的位置信息
      • timeout:以毫秒是表示的等待位置信息的最长事件
      • maximumAge:上一次取得的坐标信息的有效时间,以毫秒表示,如果时间到则重新获取新坐标信息
    navigator.geolocation.getCurrentPosition(function (position) {
      drawMapCenteredAt(position.coords.latitude, position.coords.longitude)
    }, function(error) {
      console.log('Error code: ' + error.code)
      console.log('Error message' + error.message)
    }, {
      enableHighAccuracy: true,
      timeout: 5000,
      maximumAge: 25000
    })
    

    这三个选项都是可选的,除非确实需要非常精确的信息,否则建议保持 enableHighAccuracy 的值为 false。类似的,如果不需要频繁更新用户的位置信息,那么可以将 maximumAge 设置为 Infinity,从而始终都使用上一次的坐标信息

    如果你希望跟踪用户的位置,可以使用另外一个方法 watchPosition()
    这个方法接收的参数与 getCurrentPosition() 方法完全相同。实际上,watchPositon() 与 定时调用 getCurrentPosition() 的效果相同。

    在第一次调用watchPosition() 方法后,回去的当前位置,执行成功回调或者错误回调。然后,watchPosition() 就地等待系统发出位置以改变的信号。

    调用 watchPosition() 会返回一个数值标识符。基于这个值 可以调用 clearWatch() 取消监控

    var watchId = navigator.geolocation.watchPosition(function(position) {
      console.log(position.coords.latitude, position.coords.longitude)
    }, function(error) {
      console.log('Error code: ' + error.code)
      console.log('Error message' + error.message)
    })
    
    
    navigator.geolocation.clearWatch(watchId)
    

    支持地理位置的浏览器有 IE9+、Firefox3.5+、Opera10.6+、Safari5+、Chrome、iOS版 Safari、Android版Webkit

    四、File API

    不能直接访问用户计算机中的文件,一直都是 Web 应用开发中的一大障碍。HTML5 在 DOM 中为文件输入元素添加了一个 files 集合。在通过文件输入字段选择了一或多个文件时,files 集合中将包含一组 File 对象,每个 File 对象对应着一个文件。

    每个 File 对象都有下列只读属性:

    • name:本地文件系统中的文件名
    • size:文件字节大小
    • type:字符串,文件的 MIME 类型
    • lastModifiedDate:字符串,文件上一次被修改的时间

    通过 侦听 change 事件并读取 files 集合就可以知道选择的每个文件的信息:

    var filesList = document.getElementById('files-list')
    filesList.onchange = function(event) {
      var files = event.target.files,
          i = 0,
          len = files.length
      
      while (i < len) {
        console.log(files[i].name + ' (' + files[i].type + ', ' + files[i].size + 'byte)' )
        i++
      }
    }
    

    4.1、FileReader 类型

    FileReader 类型实现的是一种异步文件读取机制可以把 FileReader 想象成 XMLHttpRequest, 区别只是他读取的是文件系统,而不是远程服务器。

    为了读取文件中的数据,FileReader 提供了如下几个方法:

    • readAsText(file[, encodin]):以纯文本形式读取文件,将读取到的文件保存在 result 属性中。第二个参数用于指定编码类型,可选
    • readAsDataURL(file):读取文件并将文件以数据URI的形式保存在 result 属性中
    • readAsBinaryString(file):读取文件并将一个字符串保存在 result 属性中,字符串中的 每个字符表示一个字节
    • readAsArrayBuffer(file):读取文件并将一个包含文件内容的 ArrayBuffer 保存在 result 属性中。

    由于读取是异步的,因此 FileReader 也提供了几个事件,其中最有用的三个事件是:

    • progress:是否又读取了新数据
      每过 50ms 左右,就会触发一次 progress 事件,通过事件对象可以获得 lengthComputable、loaded、total。另外,尽管可能没有包含全部数据,但每次 progress 事件中都可以通过 FileReader 的 result 属性读取到文件内容
    • error:是否发生了错误
      由于种种原因无法读取文件,就会触发 error 事件。触发 error 事件时,相关的信息将保存到 FileReader 的 error 属性中。这个属性中将保存一个对象,该对象只有一个属性 code,即错误码。
      • 1:表示未找到文件
      • 2:表示安全性错误
      • 3:表示读取中断
      • 4:表示文件不可读
      • 5:表示编码错误
    • load:是否已经读完了整个文件。
      文件加载后会触发 load 事件,如果发生了 error 事件,就不会发生 load 事件
    var filesList = document.getElementById('files-list')
    filesList.onchange = function(event) {
      var info = '',
          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], 'gb2312') // 中文编码
        type = 'text'
      }
    
      reader.onerror = function() { // 错误侦听
        console.log('Could not read file, error code: ' + reader.error.code)
      }
    
      reader.onprogress = function(event) { // 读取文件过程
        if (event.lengthComputable) {
          console.log('progress: ' + event.loaded + ' / ' + event.total)
        }
      }
    
      reader.onload = function() { // 读取文采
        switch (type) {
          case 'image': // 图片
            var img = document.createElement('img')
            img.src = reader.result
            document.body.appendChild(img) // 显示到 DOM 中
            break;
          case 'text': // 文本
            console.log(reader.result) // 日志 输出
            break;
        }
      }
    }
    

    如果想中断读取过程,可以 调用 abort() 方法,这样就会触发 abort 事件。在触发 load、error、abort 事件后,会触发另外一个 loadend事件
    loadend事件发生就意味着已经读取完整个文件,或者读取文件时发生了错误,获取读取中断。

    4.2、读取部分内容

    File 对象还支持一个 slice() 方法,支持读取文件的一部分而不是全部内容,低版本浏览器需要添加前缀

    slice() 方法接受两个参数:

    • 起始字节
    • 要读取的字节数。

    这个方法返回一个 Blob实例,Blob是File 类型的父类型

    下面是一个通用的函数:

    function blobSlice(blob, startByte, length) {
      if (blod.slice) {
        return blob.slice(startByte, length)
      } else if(blob.webkitSlice) {
        return blob.webkitSlice(startByte, length)
      } else if(blod.mozSlice) {
        return blob.mozSlice(startByte, length)
      } else {
        return null
      }
    }
    

    Blob 类型有一个 size 属性 和一个 type 属性,而且它也支持 slice() 方法,以便进一步切割数据。
    通过 FileReader 也可以从 Blob 中获取数据

    var fileList = document.getElementById('files-list')
    fileList.onchange = function(event) {
      var files = event.target.files,
          reader = new FileReader(),
          blob = blobSlice(files[0], 0, 32) // 只读取文件的 32B 内容
    
      if (blob) {
        reader.readAsText(blob)
    
        reader.onerror = function() {
          console.log('error code: ' + reader.error.code)
        }
    
        reader.onload = function() {
          console.log(reader.result)
        }
      } else {
        console.log('browser doesn\'t support slice().')
      }
    }
    

    4.3、对象 URL

    对象URL也被称为 blob URL, 指的是引用保存在 File 或 Blob 中数据的URL。使用对象 URL 的好处是可以不必把文件内容读取到 JavaScript 中而直接使用文件内容。为此,只要在需要文件内容的地方提供对象URL即可。

    要创建对象URL,可以使用 window.URL.createObjectURL() 方法
    通过以下函数来消除命名差异:

    function createObjectURL(blob) {
      if (window.URL) {
        return window.URL.createObjectURL(blob)
      } else if (window.webkitURL) {
        return window.webkitURL.createObjectURL(blob)
      } else {
        return null
      }
    }
    

    这个函数的返回值是一个字符串,指向一块内存的地址。因为这个字符串是 URL,所以在 DOM中也能使用。

    function createObjectURL(blob) {
      if (window.URL) {
        return window.URL.createObjectURL(blob)
      } else if (window.webkitURL) {
        return window.webkitURL.createObjectURL(blob)
      } else {
        return null
      }
    }
    
    var filesList = document.getElementById('files-list')
    filesList.onchange = function(event) {
      var files = event.target.files,
          reader = new FileReader(),
          url = createObjectURL(files[0])
    
      if (url) {
        if (/image/.test(files[0].type)) {
          var img = document.createElement('img')
          img.src = url
          document.body.appendChild(img)
        } else {
          console.log('not an image')
        }
      } else {
        console.log('doesn\'t support object URLs')
      }
    }
    

    当不再需要相应的数据,最好释放它占用的内容。但只要有代码在引用对象 URL,内存就不会释放。要手工释放,可以把对象 URL 传给 window.URL.revokeObjectURL()
    兼容命名:

    function revokeObjectURL(url) {
      if (window.URL) {
        window.URL.revokeObjectURL(url)
      } else if (window.webkitURL) {
        window.webkitURL.revokeObjectURL(url)
      }
    }
    

    为了确保尽可能少地占用内存,最好在不需要某个对象 URL 时,就马上手工释放其占用

    4.4、读取拖放文件

    围绕读取文件信息,结合使用 HTML5 拖放 API 和 文件API,能够创建出令人瞩目的用户界面;在页面上创建了自定义的放置目标之后,你可以从桌面上把文件拖放到该目标。从左面上把文件拖放到浏览器中也会触发 drop 事件。而且可以在 event.dataTransfer.files 中读取到被放置的文件,当然此时它是一个 File 对象,与通过文件输入字段取得的 File 对象一样。

      var droptarget = document.getElementById('droptarget')
      function handleEvent(event) {
        var info = '',
            output = document.getElementById('output'),
            files, i, len
        event.preventDefault()
        console.log(event.type)
        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.ondragenter = handleEvent
      droptarget.ondragover = handleEvent
      droptarget.ondrop = handleEvent
    

    与之前展示的拖放示例一样,这里也必须取消 dragenter、dragover 和 drop 的默认行为。在 drop 事件中,可以通过 event.dataTransfer.files 读取文件信息。

    4.5、使用 XHR 上传文件

    通过 File API 能够访问到文件内容,利用这一点就可以通过 XHR 直接把文件上传到服务器。把文件内容放到 send() 方法中,再通过 POST 请求,的确很容易就能实现上传

    使用 FormData 类型就很容易做到了。首先,要创建一个 FormData 对象,通过它调用 append() 方法并传入相应的 File 对象作为参数。然后,再把 FormData 对象传递给 XHR 的 send() 方法,结果与通过 表单上传 一模一样。

      var droptarget = document.getElementById('droptarget')
      function handleEvent(event) {
        var info = '',
            output = document.getElementById('output'),
            data, xhr,
            files, i, len
        
        event.preventDefault()
        console.log(event.type)
        if (event.type == 'drop') {
          data = new FormData() // 创建 FormData 实例
          files = event.dataTransfer.files // 获取文件
          i = 0
          len = files.length
    
          while (i < len) { // 在 FormData 上添加数据
            data.append('file' + i, files[i])
            i++
          }
    
          // Ajax 操作
          xhr = new XMLHttpRequest()
          xhr.open('post', 'test.php', true)
          xhr.onreadystatechange = function() {
            if (xhr.readyState == 4) console.log(xhr.responseText)
          }
          xhr.send(data)
        }
      }
    
      droptarget.ondragenter = handleEvent
      droptarget.ondragover = handleEvent
      droptarget.ondrop = handleEvent
    

    五、Web 计时

    Web 计时机制的核心是 window.performance 对象。 对页面的所有度量信息,包括那些规范中已经定义的和将来才能确定的,都包含在这个对象里面。Web Timing 规范一开始就为 performance 对象定义了两个属性。

    performance.navigation 属性也是一个对象,包含着与页面导航有关的多个属性,如下所示

    • redirectCount:页面加载前的重定向次数。
    • type:数值常量,表示刚刚发生的导航类型。
      • performance.navigation.TYPE_NAVIGATE(0):页面第一次加载
      • performance.navigation.TYPE_RELOAD(1):页面重载过
      • performance.navigation.TYPE_BACK_FORWARD(2):页面是通过“后退” 或 “前进”按钮打开的

    performance.timing 属性也是一个对象,但这个对象的属性都是时间戳(从软件纪元开始经过的毫秒数),不同的事件会产生不同的时间值。

    • navigationStart:开始导航到当前页面的时间
    • unloadEventStart:前一个页面的 unload 事件开始的时间。但只有在前一个页面与当前页面来自同一个域时这个属性才会有值;否则,值为0
    • unloadEventEnd:前一个页面的 unload 事件结束的时间。但只有在前一个页面与当前页面来自同一个域时这个属性才会有值;否则,值为0
    • redirectStart:到当前页面的重定向开始的时间。但只有在重定向的页面来自同一个域时这个属性才会有值;否则。值为0
    • redirectEnd:到当前页面的重定向结束的时间。但只有在重定向的页面来自同一个域时这个属性才会有值;否则,值为0
    • fetchStart:开始通过 HTTP GET 取得页面的时间
    • dimainLookupStart:开始查询当前页面 DNS 的时间
    • dimainLookupEnd:查询当前页面 DNS 结束的时间
    • connectStart:浏览器尝试连接服务器的时间
    • connectEnd:浏览器成功连接服务器的时间
    • secureConnectionStart:浏览器尝试以 SSL 方式连接服务器的时间。不使用 SSL 方式连接时,这个属性的值为 0
    • requestStart:浏览器开始请求页面的时间
    • responseStart:浏览器接收到页面第一字节的时间
    • responseEnd:浏览器接收到页面所有内容的时间
    • domLoading:document.readyState 变为 "loading" 的时间
    • domInteractive:document.readyState 变为 "interactive"的时间
    • domContentLoadedEventStart:发生 DOMContentLoaded 时间的时间
    • domContentLoadedEventEnd:DOMContentLoaded 事件已经发生且执行完所有事件处理程序的时间
    • domComplete:document.readyState 变为 "complete" 的时间
    • loadEventStart:发生 load 事件的时间。
    • loadEventEnd:load 事件已经发生且执行完毕所有事件处理程序的时间

    通过这些时间值,可以全面了解页面在被加载到浏览器的过程中都经历了哪些阶段,而哪些阶段可能是影响性能的瓶颈。

    六、Web Workers

    长时间运行的 JavaScript 进程会导致浏览器冻结用户界面,让人感觉屏幕“冻结了”。Web Workers 规范通过让 JavaScript 在后台运行解决了这个问题。浏览器实现了 Web Workers 规范的方式有很多种,可以使用线程、后台进程或者运行在其他处理器核心上的进程,等等。

    6.1、使用 Worker

    实例化 Worker 对象并传入要执行的 JavaScript 文件名就可以创建一个新的 Web Worker。
    例如:

    var worker = new Worker('stufftodo.js')
    

    这行代码会导致浏览器下载 stufftodo.js,但只有 Worker 接受到消息才会实际执行文件中的代码。

    要给 Worker 传递消息,可以使用 postMessage() 方法

    worker.postMessage('start!')
    

    消息内容可以是任何能被序列化的值,包括对象参数。因此,可以随便传递任何形式的对象数据。
    如下所示:

    worker.postMessage({
      name: 'Lxxx',
      age: 19
    })
    

    Worker 是通过 message 和 error 事件与页面通信的。来自 Worker 的数据保存在 event.data 中。Worker 返回的数据也可以是任何能够被序列化的值

    worker.onmessage = function(event) {
      var data = event.data
    
      // todo
    }
    

    Worker 不能完成给定任务的时候就会触发 error 事件。具体来说,Worker 内部的 JavaScript 在执行过程中只要遇到错误,就会触发 error 事件
    发生 error 事件时,事件对象包含三个 属性:

    • filename:发生错误的文件名
    • lineno:代码行号
    • message:完整的错误消息
    worker.onerror = function(event) {
      console.log('ERROR: ' + event.filename + ' (' + event.lineno + '): ' + event.message) 
    }
    

    建议使用 Web Worker 时,始终都要使用 onerror 事件处理程序,否则,worker 就会在错误发生时,悄无声息地失败。

    terminate() 方法可以停止 Worker 的工作,而且,Worker 中的代码会立即停止执行,后续的所有过程都不会再发生。

    worker.terminate() //立即停止 Worker 的工作
    

    6.2、Worker 全局作用域

    关于 Web Worker,最重要的是要知道它所执行的JavaScript 代码完全在另一个作用域中,与当前网页中的代码不共享作用域。在 Web Worker 中,同样有一个全局对象和其他对象以及方法。但是 Web Worker 中的代码不能访问 DOM,也无法通过任何方式影响页面的外观。

    Web Worker中的全局对象是 worker 对象本身,也就是说,在这个特殊的全局作用域中,this 和 self 的引用都是 worker 对象为了便于处理数据,Web Worker 本身也是一个最小化的运行环境

    • 最小化的 navigator 对象,包括 onLine、appName、appVersion、userAgent、platform属性;
    • 只读的 location 对象
    • setTimeout()、setInterval()、clearTimeout()、clearInterval() 方法
    • XMLHttpRequest 构造函数

    当页面在 worker 对象上调用 postMessage() 时,数据会以异步方式传递给 worker,进而触发 worker 中的 message 事件。
    为了处理来自页面的数据,同时也需要创建一个 onmessage 事件处理程序。

    // Web Worker  内部的代码
    self.onmessage = function(event) {
      console.log(event.data)
    
    }
    

    调用 postMessage() 可以把数据再发回页面

    self.onmessage = function(event) {
      var data = event.data
    
      data.sort(function(a, b) {
        return a - b
      })
      // 回发消息
      self.postMessage(data)
    }
    

    在页面中使用这个 worker

      var worker = new Worker('webWorkers.js')
    
      // 发送给 Web Worker
      worker.postMessage([1, 6, 4643, 464635, 11, 66, 33, 88, 99])
    
      // 接受 Web Worker 的消息
      worker.onmessage = function(event) { 
        console.log('收到了哟!!')
        console.log(event.data)
      }
    

    排序是比较消耗时间的操作,因此转交给 Worker 做就不会阻塞用户界面了


    在 worker 内部,调用 close() 方法也可以停止工作。与在 页面上调用 terminate() 方法一样

    // 这里是 Web Worker
    self.close()
    

    6.3、包含其他脚本

    既然无法在 Worker 中动态创建新的 <script> 元素,那是不是就不能向 Worker 中添加其他脚本了?非也!Worker 的全局作用域提供了这个功能,即我们可以调用 importScripts() 方法。这个方法接收一个或多个指向 JavaScript 文件的 URL。每个加载过程都是异步进行的,因此所有脚本加载并执行之后,importScripts() 才会执行。

    // 这里是 Web Worker
    importScripts('file1.js', 'file2.js')
    

    6.4、Web Workers 的未来

    Web Workers 规范还在继续制定和改进之中。该规范的另外一个 概念是 "共享 Worker"(shared worker),这种 Worker 可以在浏览器的多个标签中打开的同一个页面间共享。许多人都希望 Worker 中能够支持更多的内容,因此未来的 Worker 全局作用域中可能会有更大的空间。

    七、小结

    • requestAnimationFrame():是一个着眼于优化 JavaScript 动画的API,能够在动画运行期间发出信号。通过这种机制,浏览器就能够自动优化屏幕重绘操作
    • Page Visibility API:让开发人员知道用户什么时候正在看着页面,而什么时候页面是隐藏的。
    • Geolocation API:在得到许可的情况下,可以确定用户所在的位置。在移动 Web 应用中,这个API 非常重要而且常用。
    • File API:可以读取文件内容,用于显示、处理、上传。与 HTML5 的拖放功能结合,很容易就能创造出拖放上传的功能。
    • Web Timing:给出了页面加载和渲染过程的很多信息。对性能优化非常有价值
    • Web Workers:可以运行异步 JavaScript 代码,避免阻塞用户界面。在执行复杂计算和数据处理的时候,这个 API 非常管用;要不然,这些任务则会占用很长时间,重则会导致用户无法与页面交互。

    相关文章

      网友评论

          本文标题:新兴的 API

          本文链接:https://www.haomeiwen.com/subject/xontectx.html