22. HTML5 API

103 阅读20分钟

HTML5不仅仅指的是最新版的HTML标准,它还指代目前一整套的Web应用技术

1.地理位置API

地理位置API(http://www.w3.org/TR/geolocation-API)允许JavaScript程序向浏览器询问用户真实的地理位置。可以使用它来显示地图、导航和其他一些和用户当前位置相关的信息。

支持地理位置API的浏览器会定义navigator.geolocation。拥有如下这三个方法

  • navigator.geolocation.getCurrentPosition(),获取用户当前位置。
  • navigator.geolocation.watchPosition(),获取当前位置,同时不断地监视当前位置,一旦用户位置发生更改,就会调用指定的回调函数。
  • navigator.geolocation.clearWatch(),停止监视用户位置。传递给此方法的参数应当是调用watchPosition()方法获得的返回值。

地理位置API是异步的:getCurrentPosition()方法和watchPosition()方法需要接受一个** **作为参数,在判断用户的位置信息(或者当位置改变信息)时,浏览器会调用该函数。

image.png

  • 除了第一个回调函数的参数之外,getCurrentPosition()方法和watchPosition()方法还接受第二个可选的回调函数,当获取地理位置信息失败的时候,会调用该回调函数。
  • 除了成功和失败情况下的回调函数这两个参数之外,这两个方法还接受一个配置对象作为可选的第三个参数。该对象的属性指定了是否需要高精度的位置信息,该位置信息的过期时间,以及允许系统在多长时间内获取位置信息。
  • 作为参数传递给成功情况下的回调函数的对象,还包含一个时间戳,也有可能(在某些设备上)包含诸如海拔、速度和航向之类的额外信息。

2. 浏览器历史记录API

Web浏览器会记录在一个窗口中载入的所有文档,同时提供了“后退”和“前进”按钮,允许用户在这些文档之间切换浏览。

HTML5定义了两种用于历史记录管理的机制。

其中比较简单的历史记录管理技术就是利用location.hash和hashchange事件。

  • 在浏览器中设置location.hash属性会更新显示在地址栏中的URL,同时会在浏览器的历史记录中添加一条记录。
  • hash属性设置URL的片段标识符,可以设置成任何的字符串。
  • 可以通过设置window.onhashchange处理程序函数,使得每次由于切换历史记录导致片段标识符hash值变化的时候,都会调用该处理程序函数。当调用该处理程序函数的时候,就可以对location.hash的值进行解析,然后使用该值包含的状态信息来重新显示应用。

HTML5还定义了一个相对更加复杂和强健的历史记录管理方法,该方法包含history.pushState()方法和popstate事件。

  • 当一个Web应用进入一个新的状态的时候,它会调用history.pushState()方法将该状态添加到浏览器的浏览历史记录中。
  • history.pushState()方法的第一个参数是一个对象,该对象包含用于恢复当前文档状态所需的所有信息。该对象可以是任何能够通过JSON.stringify()方法转换成相应字符串形式的对象,也可以是其他类似Date和RegExp这样特定的本地类型
  • 第二个可选参数是一个可选的标题(普通的文本字符串),浏览器可以使用它(比如,在一个<Back>菜单中)来标识浏览历史记录中保存的状态。
  • 该方法的第三个参数是一个可选的URL,表示当前状态的位置。相对的URL都是以文档的当前位置为参照,通常该URL只是简单地指定URL(诸如#state)这样的hash(或者“片段标识符”)部分。
  • pushState()方法接受一个状态对象并为该对象创建一份私有副本。这是对一个对象进行深拷贝或者深复制:它会递归地复制所有嵌套对象或者数组的内容。HTML5标准将这类复制称为“结构性复制”(structured clone)
  • 创建一个结构性复制的过程就好比是将一个对象传递给JSON.stringify()方法,然后再将结果字符串传递给JSON.parse()方法。但是JSON只支持JavaScript的基础类型和对象以及数组。
  • History对象还定义了replaceState()方法,该方法和pushState()方法接受同样的参数,但是不同的是,它不是将新的状态添加到浏览历史记录中,而是用新的状态代替当前的历史状态。
  • 当用户通过“后退”和“前进”按钮浏览保存的历史状态时,浏览器会在Window对象上触发一个popstate事件。与该事件相关联的事件对象有一个state属性,该属性包含传递给pushState()方法的状态对象的副本(另一个结构性复制)

使用pushState()方法进行历史记录管理 image.png

<!DOCTYPE html>
<html>
  <head>
    <title>I'm thinking of a number...</title>
    <script>
      window.onload = newgame; //页面载入的时候就开始一个新的游戏
      window.onpopstate = popState; //处理历史记录相关事件
      var state, ui; //全局变量,在newgame()方法中会对其初始化
      function newgame(playagain) {
        //开始一个新的猜数字游戏
        //初始化一个包含需要的文档元素的对象
        ui = {
          heading: null, //文档最上面的<h1>元素
          prompt: null, //要求用户输入一个猜测数字
          input: null, //用户输入猜测数字的地方
          low: null, //可视化的三个表格单元格
          mid: null, //猜测的数字范围
          high: null
        }; //查询这些元素中每个元素的id
        for (var id in ui) ui[id] = document.getElementById(id); //给input字段定义一个事件处理程序函数
        ui.input.onchange = handleGuess; //生成一个随机的数字并初始化游戏状态
        state = {
          n: Math.floor(99 * Math.random()) + 1, //整数:0<n<100
          low: 0, //可猜测数字范围的下限
          high: 100, //可猜测数字范围的上限
          guessnum: 0, //猜测的次数
          guess: undefined //最后一次猜测
        }; //修改文档内容来显示该初始状态
        display(state); //此函数会作为onload事件处理程序调用,
        //同时当单击显示在游戏最后的"再玩一次"按钮时候,也会调用它
        //在第二种调用情况下,playagain参数值为true
        //如果playagain为true,则保存新的游戏状态
        //但是如果是作为onload事件处理程序调用的情况下,则不保存状态
        //这是因为,当通过浏览器历史记录从其他文档状态回退到当前的游戏状态时,
        //也会触发load事件。如果这种情况下,也保存状态的话,
        //会将真正的游戏历史状态记录覆盖掉
        //在支持pushState()方法的浏览器中,load事件之后总是有一个popstate事件
        //因此,这里的处理方式是,等待popstate事件而不是直接进行状态保存
        //如果该事件提供一个状态对象,则直接使用该对象即可
        //如果该事件没有状态对象,就表示这实际上是一个新的游戏,
        //则使用replaceState来保存最新的游戏状态
        if (playagain === true) save(state);
      }
      //如果支持的话,就使用pushState()方法将游戏状态保存到浏览器历史记录中
      function save(state) {
        if (!history.pushState) return; //如果pushState()方法没有定义的话,则什么也不做
        //这里会将一个保存的状态和URL关联起来
        //该URL显示猜测的数字,但是不对游戏状态进行编码,
        //因此,这对于书签是没有用的
        //不能简单地将游戏状态写到URL中,因为这会将游戏一些机密数字暴露在地址栏中
        var url = "#guess" + state.guessnum; //保存状态对象和URL
        history.pushState(
          state, //要保存的状态对象
          "", //状态标题:当前浏览器会忽略它
          url
        ); //状态URL:对书签是没有用的
      }
      //这是onpopstate的事件处理程序,用于恢复历史状态
      function popState(event) {
        if (event.state) {
          //如果事件有一个状态对象,则恢复该状态
          //要注意的是,event.state是对已保存状态对象的一个深拷贝
          //因此无须改变保存的值就可以修改该对象
          state = event.state; //恢复历史状态
          display(state); //显示恢复的状态
        } else {
          //当第一次载入页面时,会触发一个没有状态的popstate事件
          //用真实的状态将null状态替换掉:参见newgame()方法中的注释
          //这里不需要调用display()方法
          history.replaceState(state, "", "#guess" + state.guessnum);
        }
      } //每次用户猜测一个数字的时候,都会调用此事件处理程序
      //此处理程序用于更新游戏的状态、保存游戏状态并显示游戏状态
      function handleGuess() {
        //从input字段中获取用户猜测的数字
        var g = parseInt(this.value); //如果该值是限定范围中的一个数字
        if (g > state.low && g < state.high) {
          //对应地更新状态对象
          if (g < state.n) state.low = g;
          else if (g > state.n) state.high = g;
          state.guess = g;
          state.guessnum++; //在浏览器历史记录中保存新的状态
          save(state); //根据用户猜测情况来修改文档
          display(state);
        } else {
          //无效的猜测:不保存状态
          alert(
            "Please enter a number greater than" +
              state.low +
              "and less than" +
              state.high
          );
        }
      }
      //修改文档来显示游戏当前状态
      function display(state) {
        //显示文档的导航和标题
        ui.heading.innerHTML = document.title =
          "I'm thinking of a number between " +
          state.low +
          " and " +
          state.high +
          "."; //使用一个表格来显示数字的取值范围
        ui.low.style.width = state.low + "%";
        ui.mid.style.width = state.high - state.low + "%";
        ui.high.style.width = 100 - state.high + "%"; //确保input字段是可见的、空的并且是聚焦的
        ui.input.style.visibility = "visible";
        ui.input.value = "";
        ui.input.focus(); //根据用户最近的猜测,设置提示
        if (state.guess === undefined)
          ui.prompt.innerHTML = "Type your guess and hit Enter:";
        else if (state.guess < state.n)
          ui.prompt.innerHTML = state.guess + " is too low.Guess again:";
        else if (state.guess > state.n)
          ui.prompt.innerHTML = state.guess + " is too high.Guess again:";
        else {
          //当猜对了的时候,就隐藏input字段并显示"再玩一次"按钮
          ui.input.style.visibility = "hidden"; //不需要再猜了
          ui.heading.innerHTML = document.title = state.guess + " is correct!";
          ui.prompt.innerHTML =
            "You Win!<button onclick='newgame(true)'>Play Again</button>";
        }
      }
    </script>

    <style>
      /*通过CSS样式美化游戏界面*/
      #prompt {
        font-size: 16pt;
      }
      table {
        width: 90%;
        margin: 10px;
        margin-left: 5%;
      }
      #low,
      #high {
        background-color: lightgray;
        height: 1em;
      }
      #mid {
        background-color: green;
      }
    </style>
  </head>
  <body>
    <!--下面的HTML元素是游戏的UI-->
    <!--游戏标题和数字猜测范围的文本表示-->
    <h1 id="heading">I'm thinking of a number...</h1>
    <!--用于确保猜测的数字在有效范围内-->
    <table>
      <tr>
        <td id="low"></td>
        <td id="mid"></td>
        <td id="high"></td>
      </tr>
    </table>

    <!--用户输入猜测数字的地方-->
    <label id="prompt"></label><input id="input" type="text" />
  </body>
</html>

3. 跨域消息传递

window的postMessage()方法,是允许来自非同源脚本调用的,该方法允许通过异步消息传递的方式——在来自不同源的脚本之间进行有限的通信。这类通信机制是在HTML5标准中定义的,由于该API是定义在Window对象上的,而不是文档对象上的,因此,它又称为“窗口间消息传递”或者“跨域消息传递”。

  • postMessage()方法接受两个参数。其中第一个参数是要传递的消息。
  • 其中第二个参数是一个字符串,指定目标窗口的源。其中包括协议、主机名以及URL(可选的)端口部分
  • 如果传递的消息不包含任何敏感信息的话,并且愿意将其传递给任何窗口,就可以直接将该第二个参数设置成*通配符即可。如果要指定和当前窗口同源的话,那么也可以简单地使用“/”。

image.png 如果指定的源匹配的话,那么当调用postMessage()方法的时候,在目标窗口的Window对象上就会触发一个message事件。在目标窗口中的脚本则可以定义通知message事件的处理程序函数。调用该事件处理程序的时候传递给它的事件对象拥有如下属性:

  • data 作为第一个参数传递给postMessage()方法的消息内容副本。
  • source 消息源自的Window对象。
  • origin 一个字符串,指定消息来源(URL形式)。

1.gif

<!DOCTYPE html>
<html>
  <body>
    <div>
      <input id="text" type="text" value="测试" />
      <button id="sendMessage">发送消息</button>
    </div>
    <iframe
      id="receiver"
      src="http://127.0.0.1:5500/src/views/Test/dom.html"
      width="300"
      height="360"
    >
    </iframe>
    <script>
      window.onload = function() {
        var receiver = document.getElementById("receiver").contentWindow;
        var btn = document.getElementById("sendMessage");
        btn.addEventListener("click", function(e) {
          e.preventDefault();
          var val = document.getElementById("text").value;
          receiver.postMessage("Hello " + val + "!", "http://127.0.0.1:5500");
        });
      };
    </script>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <title>Sample Document</title>
  </head>
  <body>
    <h1 id="msg"></h1>
  </body>
  <script>
    window.addEventListener("message", function(e) {
      // 监听 message 事件
      console.log(e);
      document.querySelector("#msg").innerHTML = e.data;
    });
  </script>
</html>

4 Web Worker

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许Js主线程创建 Worker 线程,将一些任务分配给后者运行。

  • 在Js主线程运行的同时,Worker 线程在后台运行,两者互不干扰。
  • 等到 Worker 线程完成计算任务,再把结果返回给Js主线程。 -这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,Js主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。

Web Worker 特征

(1)同源限制

分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

(2)DOM 限制

Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent这些对象。但是,Worker 线程可以用navigator对象和location对象。

(3)通信联系

Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

(4)脚本限制

Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。

(5)文件限制

Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络

4.1 Worker对象

  1. 主线程采用new命令,调用Worker()构造函数,新建一个 Worker 线程。
var worker = new Worker('test.js');
  • Worker.onerror:指定 error 事件的监听函数。
  • Worker.onmessage:指定 message 事件的监听函数,发送过来的数据在Event.data属性中。
  • Worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • Worker.postMessage():向 Worker 线程发送消息。
  • Worker.terminate():立即终止 Worker 线程。 image.png

Worker()构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的任务。由于 Worker 不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker 就会默默地失败。

  1. 然后,主线程调用worker.postMessage()方法,向 Worker 发消息。
worker.postMessage('Hello World');
worker.postMessage({method: 'echo', args: ['Work']});

worker.postMessage()方法的参数,就是主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。

  1. 接着,主线程通过worker.onmessage指定监听函数,接收子线程发回来的消息。
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
  doSomething();
}

function doSomething() {
  // 执行任务
  worker.postMessage('Work done!');
}

上面代码中,事件对象的data属性可以获取 Worker 发来的数据。

  1. Worker 完成任务以后,主线程就可以把它关掉。
worker.terminate();

4.2 Worker 线程

Web Worker 有自己的全局对象,不是主线程的window

  • self.name: Worker 的名字。该属性只读,由构造函数指定。
  • self.onmessage:指定message事件的监听函数。
  • self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • self.close():关闭 Worker 线程。
  • self.postMessage():向产生这个 Worker 线程发送消息。
  • self.importScripts():加载 JS 脚本。 Worker 线程内部需要有一个监听函数,监听message事件。
self.addEventListener('message', function (e) {
  self.postMessage('You said: ' + e.data);
}, false);

上面代码中,self代表子线程自身,即子线程的全局对象。因此,等同于下面两种写法。

// 写法一
this.addEventListener('message', function (e) {
  this.postMessage('You said: ' + e.data);
}, false);

// 写法二
addEventListener('message', function (e) {
  postMessage('You said: ' + e.data);
}, false);

除了使用self.addEventListener()指定监听函数,也可以使用self.onmessage指定。监听函数的参数是一个事件对象,它的data属性包含主线程发来的数据。self.postMessage()方法用来向主线程发送消息。

根据主线程发来的数据,Worker 线程可以调用不同的方法,

self.addEventListener('message', function (e) {
  var data = e.data;
  switch (data.cmd) {
    case 'start':
      self.postMessage('WORKER STARTED: ' + data.msg);
      break;
    case 'stop':
      self.postMessage('WORKER STOPPED: ' + data.msg);
      self.close(); // Terminates the worker.
      break;
    default:
      self.postMessage('Unknown command: ' + data.msg);
  };
}, false);

上面代码中,self.close()用于在 Worker 内部关闭自身。

4.3 Worker 加载脚本

Worker 内部有一个专门的方法importScripts()加载其他一个或者多个脚本,。

importScripts('script1.js');
importScripts('script1.js', 'script2.js');

4.4 错误处理

  • 主线程可以监听 Worker 是否发生错误。如果发生错误,Worker 会触发主线程的error事件。
worker.onerror(function (event) {
  console.log([
    'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
  ].join(''));
});

// 或者
worker.addEventListener('error', function (event) {
  // ...
});
  • Worker 内部也可以监听error事件。

4.5 关闭 Worker

使用完毕,为了节省系统资源,必须关闭 Worker。

// 主线程
worker.terminate();

// Worker 线程
self.close();

4.6 例子

Js主线程

image.png worker线程

// test.js worker脚本
self.addEventListener(
  "message",
  function(e) {
    self.postMessage("You said: " + e.data);
    // self.close();
  },
  false
);

5.JavaScript类型化数组和ArrayBuffer

  • Array存储的对象能动态增多和减少,并且可以存储任何 JavaScript 值。JavaScript 引擎会做一些内部优化,以便对数组的操作可以很快。
  • 然而,随着 Web 应用程序变得越来越强大,尤其一些新增加的功能例如:音频视频编辑,访问 WebSockets 的原始数据等,很明显有些时候如果使用 JavaScript 代码可以快速方便地通过类型化数组来操作原始的二进制数据将会非常有帮助
  • 但是,不要把类型化数组与正常数组混淆,因为在类型数组上调用  Array.isArray() 会返回false。此外,并不是所有可用于正常数组的方法都能被类型化数组所支持(如 push 和 pop)

5.1 缓冲和视图:类型数组架构

JavaScript 类型数组(Typed Arrays)将实现拆分为缓冲视图两部分。一个缓冲(由 ArrayBuffer 对象实现)描述的是一个数据块。缓冲没有格式可言,并且不提供机制访问其内容。为了访问在缓冲对象中包含的内存,你需要使用视图。视图提供了上下文 — 即数据类型、起始偏移量和元素数 — 将数据转换为实际有类型的数组。

image.png

ArrayBuffer 是一种数据类型,用来表示一个通用的、固定长度的二进制数据缓冲区。你不能直接操纵一个 ArrayBuffer 中的内容;你需要创建一个类型化数组的视图或一个描述缓冲数据格式的DataView,使用它们来读写缓冲区中的内容。

类型化数组视图具有自描述性的名字和所有常用的数值类型像Int8Uint32Float64 等等。有一种特殊类型的数组Uint8ClampedArray。它仅操作 0 到 255 之间的数值。例如,这对于Canvas 数据处理非常有用。

image.png

  • 类型化数组中的元素都是数字。使用构造函数在创建类型化数组的时候决定了数组中数字(有符号或者无符号整数或者浮点数)的类型和大小(以位为单位)。
  • 类型化数组有固定的长度。
  • 在创建类型化数组的时候,数组中的元素总是默认初始化为0

数据视图DataView 是一种底层接口,它提供有可以操作缓冲区中任意数据的读写接口。这对操作不同类型数据的场景很有帮助,例如:类型化数组视图都是运行在本地字节序模式 (参考 Endianness),可以通过使用 DataView 来控制字节序。

5.2 使用类型数组的 Web API

FileReader.prototype.readAsArrayBuffer()

  • FileReader.prototype.readAsArrayBuffer() 读取对应的Blob 或 File的内容

XMLHttpRequest.prototype.send()

  • XMLHttpRequest 实例的 send() 方法现在使用支持类型化数组和 ArrayBuffer 对象作为参数。

ImageData.data

  • 是一个 Uint8ClampedArray 对象,用来描述包含按照 RGBA 序列的颜色数据的一维数组,其值的范围在0255(包含 255)之间。

5.3 示例

1.首先,我们创建一个 16 字节固定长度的缓冲:

var buffer = new ArrayBuffer(16);

image.png

2.现在有了一段初始化为 0 的内存,目前还做不了什么太多操作。让我们确认一下数据的字节长度

image.png

if (buffer.byteLength === 16) {
  console.log("Yes, it's 16 bytes.");
} else {
  console.log("Oh no, it's the wrong size!");
}
  1. 在实际开始操作这个缓冲之前,我们需要创建一个视图,此视图将把缓冲内的数据格式化为一个 32 位的有符号整数数组(32位说明Int32Array数组每个元素占四个字节):
var int32View = new Int32Array(buffer);

image.png

  1. 现在我们可以像普通数组一样访问该数组中的元素:
for (var i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}

image.png

一共 4 个 4 字节元素,所以总长度为 16 字节

  1. 可以在同一数据上创建多个视图,创建了一个 2 字节(Int16Array每个数组元素2个字节)整数视图,该视图共享上文的 4 字节整数视图的缓冲
var int16View = new Int16Array(buffer);

for (var i = 0; i < int16View.length; i++) {
  console.log("Entry " + i + ": " + int16View[i]);
}

image.png

image.png

  1. 转换为普通数组

可以调用 Array.from实现类型化数组转换为普通数组,以便可以可以像普通数据一种操作访问。

image.png

6. Blob

Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。

  • Blob是对大数据块的不透明引用或者句柄。名字来源于SQL数据库,表示“二进制大对象”(Binary Large Object)。
  • 在JavaScript中,Blob通常表示二进制数据
  • Blob是不透明的:能对它们进行直接操作的就只有获取它们的大小(以字节为单位)、MIME类型以及将它们分割成更小的Blob

image.png

  • Blob 表示的不一定是 JavaScript 原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
  • 要从其他非 blob 对象和数据构造一个 Blob,请使用 Blob() 构造函数。
  • 要创建一个 blob 数据的子集 blob,请使用 slice() 方法。要获取用户文件系统上的文件对应的 Blob 对象,请参阅 File 文档。
  • Blob用于给二进制数据的JavaScript API提供重要的数据交换,如下图

image.png

6.1 文件作为Blob

<input type="file">元素上的files属性则是一个FileList对象。该对象是一个类数组对象,其元素要么是0,要么是用户选择的多个File对象。一个File对象就是一个Blob,除此之外,还多了name和lastModifiedDate属性

<!DOCTYPE html>
<html>
  <body>
    <script>
      //输出选中的文件列表相关的信息
      function fileinfo(files) {
        for (var i = 0; i < files.length; i++) {
          //files是一个类数组对象
          var f = files[i];
          console.log(f);
          console.log(f instanceof Blob); // true
          console.log(
            f.name, //只是名字:没有路径
            f.size,
            f.type, //size和type是Blob的属性
            f.lastModifiedDate
          ); //另外一个File对象的属性
        }
      }
    </script>
    <!--允许选择多个图片文件并将它们传递给fileinfo()方法-->
    <input
      type="file"
      accept="image/*"
      multiple
      onchange="fileinfo(this.files)"
    />
  </body>
</html>

image.png

6.2 下载Blob

XHR2可以将URL指定的内容以Blob的形式下载下来

<!DOCTYPE html>
<html>
  <body>
    <script>
      //以Blob的形式获取URL指定的内容,并将其传递给指定的回调函数
      function getBlob(url, callback) {
        var xhr = new XMLHttpRequest(); //创建一个新的XHR对象
        xhr.open("GET", url); //指定要获取内容的URL
        xhr.responseType = "blob"; //以Blob的形式
        xhr.onload = function() {
          //onload比onreadystatechange更容易
          callback(xhr.response); //将Blob传递给回调函数
        };
        //注意,这里是.response,不是.responseText
        xhr.send(null); //发送请求
      }
      getBlob("./dom.html", console.log);
    </script>
  </body>
</html>

image.png

6.3 构造Blob

Blob() 构造函数允许通过其它对象创建 Blob 对象。比如,用字符串构建一个 blob:

var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug, null, 2)], {type : 'application/json'});

image.png

6.4 Blob 对象的方法

Blob.slice([start[, end[, contentType]]])

  • 返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。

Blob.stream()

Blob.text()

  • 返回一个 promise 且包含 blob 所有内容的 UTF-8 格式的 USVString

Blob.arrayBuffer()

  • 返回一个 promise 且包含 blob 所有内容的二进制格式的 ArrayBuffer

6.5 使用 Blob 创建一个指向类型化数组的 URL

<script>
      var buffer = new ArrayBuffer(16);
      var int32View = new Int32Array(buffer);
      var typedArray = int32View;
      var blob = new Blob([typedArray.buffer], {
        type: "application/octet-stream"
      }); // 传入一个合适的 MIME 类型
      var url = URL.createObjectURL(blob);
      console.log(url);
      // 会产生一个类似 blob:d3958f5c-0777-0845-9dcf-2cb28783acaf 这样的 URL 字符串
      // 你可以像使用普通 URL 那样使用它,比如用在 img.src 上。
    </script>

image.png

6.6 从 Blob 中读取数据

  • 一种从 Blob 中读取内容的方法是使用 FileReader。以下代码将 Blob 的内容作为类型数组读取:
<script>
      var buffer = new ArrayBuffer(16);
      var int32View = new Int32Array(buffer);
      var typedArray = int32View;
      var blob = new Blob([typedArray.buffer], {
        type: "application/octet-stream"
      }); // 传入一个合适的 MIME 类型
      // 会产生一个类似 blob:d3958f5c-0777-0845-9dcf-2cb28783acaf 这样的 URL 字符串
      // 你可以像使用普通 URL 那样使用它,比如用在 img.src 上。
      var reader = new FileReader();
      reader.addEventListener("loadend", function() {
        console.log(reader);
        console.log(reader.result);
        // reader.result 包含被转化为类型数组 typed array 的 blob
      });
      reader.readAsArrayBuffer(blob);
    </script>

image.png

  • 另一种读取 Blob 中内容的方式是使用 Response 对象。下述代码将 Blob 中的内容读取为文本:
<script>
      var debug = { hello: "world" };
      var blob = new Blob([JSON.stringify(debug, null, 2)], {
        type: "application/json"
      });
      async function fn() {
        var res = await new Response(blob);
        console.log(res);
        console.log(res.text());
      }
      fn();
    </script>

image.png

  • 通过使用 FileReader 的其它方法可以把 Blob 读取为字符串或者数据 URL。
  1. FileReader.readAsDataURL()

开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL 格式的 Base64 字符串以表示所读取文件的内容。

  1. FileReader.readAsText()

开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。

7. 文件系统API

window.webkitRequestFileSystem( type, size, successCallback[, errorCallback] )

这个非标准 [WindowrequestFileSystem()  方法是谷歌浏览器内核用来让 web 站点或 app 获得通过沙箱访问文件系统 . 它返回 FileSystem 然后就可以和 file system APIs 一起使用了

image.png

通过上述方法获取到的文件系统对象有一个root属性,该属性指向文件系统的根目录。这是一个DirectoryEntry对象

8 客户端数据库

传统的Web应用架构是客户端包含HTML、CSS和JavaScript,服务器端包含一个数据库。而通过强大的HTML5 API可以实现客户端数据库。这些不是通过网络访问服务器端数据库的客户端API,而是真正存储在用户电脑上的客户端数据库,通过浏览器中的JavaScript代码可以直接访问的。

其中一个叫Web SQL数据库,它是支持基本SQL查询的简单关系数据库。Chrome、Safari和Opera已经实现了该API

image.png

另一种数据库API,叫做:IndexedDB,IndexedDB是一个对象数据库,而不是关系数据库,它比支持SQL查询的数据库简单,但是,它要比Web存储API支持的键/值对存储更强大、更高效、更健壮。

  • 在IndexedDB API中,一个数据库其实就是一个命名对象存储区(object store)的集合。
  • 对象存储区自然存储的是对象(也可以存储任意可以复制的值)。
  • 每个对象都必须有一个键(key),通过该键实现在存储区中进行该对象的存储和获取
  • JavaScript中的字符串、数字和日期对象都可以作为该键。当把一个对象存储到IndexedDB数据库中时,IndexedDB数据库可以为该对象自动生成一个唯一的键。
  • 可以在对象存储区上定义索引。(之所以叫"IndexedDB"就是因为可以在对象存储区上创建索引)。每一个索引就等于是为存储的对象定义了次键。

image.png

9 WebSocket协议

HTTP 协议有一个缺陷:通信只能由客户端发起。

WebSocket API定义了一种安全方案:它允许客户端代码在客户端和支持WebSocket协议的服务器端创建双向的套接字类型的连接

image.png (1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

9.1 客户端示例

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};

9.2 客户端的 API

  • WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。
var ws = new WebSocket('ws://localhost:8080');
// 执行上面语句之后,客户端就会与服务器进行连接。
  • readyState属性返回实例对象的当前状态
  1. CONNECTING:值为0,表示正在连接。
  2. OPEN:值为1,表示连接成功,可以通信了。
  3. CLOSING:值为2,表示连接正在关闭。
  4. CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
  • 实例对象的onopen属性,用于指定连接成功后的回调函数。
ws.onopen = function () {
  ws.send('Hello Server!');
}

如果要指定多个回调函数,可以使用addEventListener方法。

ws.addEventListener('open', function (event) {
  ws.send('Hello Server!');
});
  • 实例对象的onclose属性,用于指定连接关闭后的回调函数。
ws.onclose = function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
};

ws.addEventListener("close", function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
});
  • 实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。
ws.onmessage = function(event) {
  var data = event.data;
  // 处理数据
};

ws.addEventListener("message", function(event) {
  var data = event.data;
  // 处理数据
});

注意,服务器数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)。

ws.onmessage = function(event){
  if(typeof event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

也可以使用binaryType属性,显式指定收到的二进制数据类型。

// 收到的是 blob 数据
ws.binaryType = "blob";
ws.onmessage = function(e) {
  console.log(e.data.size);
};

// 收到的是 ArrayBuffer 数据
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
  console.log(e.data.byteLength);
};
  • 实例对象的send()方法用于向服务器发送数据。
ws.send('your message');

发送 Blob 对象

var file = document
  .querySelector('input[type="file"]')
  .files[0];
ws.send(file);

发送 ArrayBuffer 对象

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);
  • 实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。
var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // 发送完毕
} else {
  // 发送还没结束
}
  • 实例对象的onerror属性,用于指定报错时的回调函数。
socket.onerror = function(event) {
  // handle error event
};

socket.addEventListener("error", function(event) {
  // handle error event
});