ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

JavaScript(八)(Storage/History/URL/ArrayBuffer/File/FormDate)

2021-07-29 21:02:14  阅读:190  来源: 互联网

标签:URL ArrayBuffer JavaScript 表单 对象 实例 var 属性


JavaScript(八)(Storage/History/URL/ArrayBuffer/File/FormData

文章目录

61. Storage 接口

61.1 概述

Storage 接口用于脚本在浏览器保存数据。两个对象部署了这个接口:window.sessionStoragewindow.localStorage

sessionStorage保存的数据用于浏览器的一次会话(session),当会话结束(通常是窗口关闭),数据被清空;localStorage保存的数据长期存在,下一次访问该网站的时候,网页可以直接读取以前保存的数据。除了保存期限的长短不同,这两个对象的其他方面都一致。

保存的数据都以**“键值对”**的形式存在。也就是说,每一项数据都有一个键名和对应的值。所有的数据都是以文本格式保存。

这个接口很像 Cookie 的强化版,能够使用大得多的存储空间。目前,每个域名的存储上限视浏览器而定,Chrome 是 2.5MB,Firefox 和 Opera 是 5MB,IE 是 10MB。其中,Firefox 的存储空间由一级域名决定,而其他浏览器没有这个限制。也就是说,Firefox 中,a.example.comb.example.com共享 5MB 的存储空间。另外,与 Cookie 一样,它们也受同域限制。某个网页存入的数据,只有同域下的网页才能读取,如果跨域操作会报错。

61.2 属性和方法

Storage 接口只有一个属性。

  • Storage.length:返回保存的数据项个数。
window.localStorage.setItem('foo', 'a');
window.localStorage.setItem('bar', 'b');
window.localStorage.setItem('baz', 'c');

window.localStorage.length // 3

该接口提供5个方法。

61.2.1 Storage.setItem()

Storage.setItem()方法用于存入数据。它接受两个参数,第一个是键名,第二个是保存的数据。如果键名已经存在,该方法会更新已有的键值。该方法没有返回值。

window.sessionStorage.setItem('key', 'value');
window.localStorage.setItem('key', 'value');

注意,Storage.setItem()两个参数都是字符串。如果不是字符串,会自动转成字符串,再存入浏览器。

window.sessionStorage.setItem(3, { foo: 1 });
window.sessionStorage.getItem('3') // "[object Object]"

上面代码中,setItem方法的两个参数都不是字符串,但是存入的值都是字符串。

如果储存空间已满,该方法会抛错。

写入不一定要用这个方法,直接赋值也是可以的。

// 下面三种写法等价
window.localStorage.foo = '123';
window.localStorage['foo'] = '123';
window.localStorage.setItem('foo', '123');

61.2.2 Storage.getItem()

Storage.getItem()方法用于读取数据。它只有一个参数,就是键名。如果键名不存在,该方法返回null

window.sessionStorage.getItem('key')
window.localStorage.getItem('key')

键名应该是一个字符串,否则会被自动转为字符串。

61.2.3 Storage.removeItem()

Storage.removeItem()方法用于清除某个键名对应的键值。它接受键名作为参数,如果键名不存在,该方法不会做任何事情。

sessionStorage.removeItem('key');
localStorage.removeItem('key');

61.2.4 Storage.clear()

Storage.clear()方法用于清除所有保存的数据。该方法的返回值是undefined

window.sessionStorage.clear()
window.localStorage.clear()

61.2.5 Storage.key()

Storage.key()方法接受一个整数作为参数(从零开始),返回该位置对应的键名。

window.sessionStorage.setItem('key', 'value');
window.sessionStorage.key(0) // "key"

结合使用Storage.length属性和Storage.key()方法,可以遍历所有的键。

for (var i = 0; i < window.localStorage.length; i++) {
  console.log(localStorage.key(i));
}

61.3 storage 事件

Storage 接口储存的数据发生变化时,会触发 storage 事件,可以指定这个事件的监听函数。

window.addEventListener('storage', onStorageChange);

监听函数接受一个event实例对象作为参数。这个实例对象继承了 StorageEvent 接口,有几个特有的属性,都是只读属性。

  • StorageEvent.key:字符串,**表示发生变动的键名。**如果 storage 事件是由clear()方法引起,该属性返回null
  • StorageEvent.newValue:字符串,表示新的键值。如果 storage 事件是由clear()方法或删除该键值对引发的,该属性返回null
  • StorageEvent.oldValue:字符串,表示旧的键值。如果该键值对是新增的,该属性返回null
  • StorageEvent.storageArea:对象,返回键值对所在的整个对象。也说是说,可以从这个属性上面拿到当前域名储存的所有键值对。
  • StorageEvent.url:字符串,表示原始触发 storage 事件的那个网页的网址。

下面是StorageEvent.key属性的例子。

function onStorageChange(e) {
  console.log(e.key);
}

window.addEventListener('storage', onStorageChange);

注意,该事件有一个很特别的地方,就是它不在导致数据变化的当前页面触发,而是在同一个域名的其他窗口触发。也就是说,如果浏览器只打开一个窗口,可能观察不到这个事件。比如同时打开多个窗口,当其中的一个窗口导致储存的数据发生改变时,只有在其他窗口才能观察到监听函数的执行。可以通过这种机制,实现多个窗口之间的通信。

62. History 对象

62.1 概述

window.history属性指向 History 对象,它表示当前窗口的浏览历史。

History 对象保存了当前窗口访问过的所有页面网址。下面代码表示当前窗口一共访问过3个网址。

window.history.length // 3

由于安全原因,浏览器不允许脚本读取这些地址,但是允许在地址之间导航。

// 后退到前一个网址
history.back()

// 等同于
history.go(-1)

浏览器工具栏的“前进”和“后退”按钮,其实就是对 History 对象进行操作。

62.2 属性

History 对象主要有两个属性。

  • History.length:当前窗口访问过的网址数量(包括当前网页)
  • History.state:History 堆栈最上层的状态值(详见下文)
// 当前窗口访问过多少个网页
window.history.length // 1

// History 对象的当前状态
// 通常是 undefined,即未设置
window.history.state // undefined

62.3 方法

62.3.1 History.back()、History.forward()、History.go()

这三个方法用于在历史之中移动。

  • History.back():移动到上一个网址,等同于点击浏览器的后退键。对于第一个访问的网址,该方法无效果。
  • History.forward():移动到下一个网址,等同于点击浏览器的前进键。对于最后一个访问的网址,该方法无效果。
  • History.go():接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址,比如go(1)相当于forward()go(-1)相当于back()。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为0,相当于刷新当前页面。
history.back();
history.forward();
history.go(-2);

history.go(0)相当于刷新当前页面。

history.go(0); // 刷新当前页面

注意,移动到以前访问过的页面时,页面通常是从浏览器缓存之中加载,而不是重新要求服务器发送新的网页。

62.3.2 History.pushState(),

History.pushState()方法用于在历史中添加一条记录。

window.history.pushState(state, title, url)

该方法接受三个参数,依次为:

  • state一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填null
  • title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。
  • url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。

假定当前网址是example.com/1.html,使用pushState()方法在浏览记录(History 对象)中添加一个新记录。

var stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2', '2.html');

添加新记录后,浏览器地址栏立刻显示example.com/2.html,但并不会跳转到2.html,甚至也不会检查2.html是否存在,它只是成为浏览历史中的最新记录。这时,在地址栏输入一个新的地址(比如访问google.com),然后点击了倒退按钮,页面的 URL 将显示2.html;你再点击一次倒退按钮,URL 将显示1.html

总之,pushState()方法不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有反应。

使用该方法之后,就可以用History.state属性读出状态对象。

var stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2', '2.html');
history.state // {foo: "bar"}

如果pushState的 URL 参数设置了一个新的锚点值(即hash),并不会触发hashchange事件。反过来,如果 URL 的锚点值变了,则会在 History 对象创建一条浏览记录。

如果pushState()方法设置了一个跨域网址,则会报错。****

// 报错
// 当前网址为 http://example.com
history.pushState(null, '', 'https://twitter.com/hello');

上面代码中,pushState想要插入一个跨域的网址,导致报错。这样设计的目的是,防止恶意代码让用户以为他们是在另一个网站上,因为这个方法不会导致页面跳转。

62.3.3 History.replaceState()

History.replaceState()方法用来修改 History 对象的当前记录,其他都与pushState()方法一模一样。

假定当前网页是example.com/example.html

history.pushState({page: 1}, 'title 1', '?page=1')
// URL 显示为 http://example.com/example.html?page=1

history.pushState({page: 2}, 'title 2', '?page=2');
// URL 显示为 http://example.com/example.html?page=2

history.replaceState({page: 3}, 'title 3', '?page=3');
// URL 显示为 http://example.com/example.html?page=3

history.back()
// URL 显示为 http://example.com/example.html?page=1

history.back()
// URL 显示为 http://example.com/example.html

history.go(2)
// URL 显示为 http://example.com/example.html?page=3

62.4 popstate 事件

每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。

注意,仅仅调用pushState()方法或replaceState()方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用History.back()History.forward()History.go()方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。

使用的时候,可以为popstate事件指定回调函数。

window.onpopstate = function (event) {
  console.log('location: ' + document.location);
  console.log('state: ' + JSON.stringify(event.state));
};

// 或者
window.addEventListener('popstate', function(event) {
  console.log('location: ' + document.location);
  console.log('state: ' + JSON.stringify(event.state));
});

回调函数的参数是一个event事件对象,它的state属性指向pushStatereplaceState方法为当前 URL 所提供的状态对象(即这两个方法的第一个参数)。上面代码中的event.state,就是通过pushStatereplaceState方法,为当前 URL 绑定的state对象。

这个state对象也可以直接通过history对象读取。

var currentState = history.state;

注意,页面第一次加载的时候,浏览器不会触发popstate事件。

63. Location 对象,URL 对象,URLSearchParams 对象

URL 是互联网的基础设施之一。浏览器提供了一些原生对象,用来管理 URL。

63.1 Location 对象

Location对象是浏览器提供的原生对象,提供 URL 相关的信息和操作方法。通过window.locationdocument.location属性,可以拿到这个对象。

63.1.1 属性

Location对象提供以下属性。

  • Location.href:整个 URL。
  • Location.protocol:当前 URL 的协议,包括冒号(:)。
  • Location.host:主机。如果端口不是协议默认的80433,则还会包括冒号(:)和端口。
  • Location.hostname:主机名,不包括端口。
  • Location.port:端口号。
  • Location.pathname:URL 的路径部分,从根路径/开始。
  • Location.search:查询字符串部分,从问号?开始。
  • Location.hash:片段字符串部分,从#开始。
  • Location.username:域名前面的用户名。
  • Location.password:域名前面的密码。
  • Location.origin:URL 的协议、主机名和端口。
// 当前网址为
// http://user:passwd@www.example.com:4097/path/a.html?x=111#part1
document.location.href
// "http://user:passwd@www.example.com:4097/path/a.html?x=111#part1"
document.location.protocol
// "http:"
document.location.host
// "www.example.com:4097"
document.location.hostname
// "www.example.com"
document.location.port
// "4097"
document.location.pathname
// "/path/a.html"
document.location.search
// "?x=111"
document.location.hash
// "#part1"
document.location.username
// "user"
document.location.password
// "passwd"
document.location.origin
// "http://user:passwd@www.example.com:4097"

这些属性里面,只有origin属性是只读的,其他属性都可写。

注意,如果对Location.href写入新的 URL 地址,浏览器会立刻跳转到这个新地址。

// 跳转到新网址
document.location.href = 'http://www.example.com';

这个特性常常用于让网页自动滚动到新的锚点。

document.location.href = '#top';
// 等同于
document.location.hash = '#top';

直接改写location,相当于写入href属性。

document.location = 'http://www.example.com';
// 等同于
document.location.href = 'http://www.example.com';

另外,Location.href属性是浏览器唯一允许跨域写入的属性,即非同源的窗口可以改写另一个窗口(比如子窗口与父窗口)的Location.href属性,导致后者的网址跳转。Location的其他属性都不允许跨域写入。

63.1.2 方法

(1)Location.assign()

assign方法接受一个 URL 字符串作为参数,使得浏览器立刻跳转到新的 URL。如果参数不是有效的 URL 字符串,则会报错。

// 跳转到新的网址
document.location.assign('http://www.example.com')
(2)Location.replace()

replace方法接受一个 URL 字符串作为参数,使得浏览器立刻跳转到新的 URL。如果参数不是有效的 URL 字符串,则会报错。

它与assign方法的差异在于,replace会在浏览器的浏览历史History里面删除当前网址,也就是说,一旦使用了该方法,后退按钮就无法回到当前网页了,相当于在浏览历史里面,使用新的 URL 替换了老的 URL它的一个应用是,当脚本发现当前是移动设备时,就立刻跳转到移动版网页。

// 跳转到新的网址
document.location.replace('http://www.example.com')
(3)Location.reload()

reload方法使得浏览器重新加载当前网址,相当于按下浏览器的刷新按钮。

它接受一个布尔值作为参数。如果参数为true,浏览器将向服务器重新请求这个网页,并且重新加载后,网页将滚动到头部(即scrollTop === 0)。如果参数是false或为空,浏览器将从本地缓存重新加载该网页,并且重新加载后,网页的视口位置是重新加载前的位置。

// 向服务器重新请求当前网址
window.location.reload(true);
(4)Location.toString()

toString方法返回整个 URL 字符串,相当于读取Location.href属性。

63.2 URL 的编码和解码

网页的 URL 只能包含合法的字符。合法字符分成两类。

  • URL 元字符:分号(;),逗号(,),斜杠(/),问号(?),冒号(:),at(@),&,等号(=),加号(+),美元符号($),井号(#
  • 语义字符:a-zA-Z0-9,连词号(-),下划线(_),点(.),感叹号(!),波浪线(~),星号(*),单引号('),圆括号(()

除了以上字符,其他字符出现在 URL 之中都必须转义,规则是根据操作系统的默认编码,将每个字节转为百分号(%)加上两个大写的十六进制字母。

比如,UTF-8 的操作系统上,http://www.example.com/q=春节这个 URL 之中,汉字“春节”不是 URL 的合法字符,所以被浏览器自动转成http://www.example.com/q=%E6%98%A5%E8%8A%82。其中,“春”转成了%E6%98%A5,“节”转成了%E8%8A%82。这是因为“春”和“节”的 UTF-8 编码分别是E6 98 A5E8 8A 82,将每个字节前面加上百分号,就构成了 URL 编码。

JavaScript 提供四个 URL 的编码/解码方法。

  • encodeURI()
  • encodeURIComponent()
  • decodeURI()
  • decodeURIComponent()

63.2.1 encodeURI()

encodeURI()方法用于转码整个 URL。它的参数是一个字符串,代表整个 URL。它会将元字符和语义字符之外的字符,都进行转义。

encodeURI('http://www.example.com/q=春节')
// "http://www.example.com/q=%E6%98%A5%E8%8A%82"

63.2.2 encodeURIComponent()

encodeURIComponent()方法用于转码 URL 的组成部分,会转码除了语义字符之外的所有字符,即元字符也会被转码。所以,它不能用于转码整个 URL。它接受一个参数,就是 URL 的片段。

encodeURIComponent('春节')
// "%E6%98%A5%E8%8A%82"
encodeURIComponent('http://www.example.com/q=春节')
// "http%3A%2F%2Fwww.example.com%2Fq%3D%E6%98%A5%E8%8A%82"

上面代码中,encodeURIComponent()会连 URL 元字符一起转义,所以如果转码整个 URL 就会出错。

63.2.3 decodeURI()

decodeURI()方法用于整个 URL 的解码。它是encodeURI()方法的逆运算。它接受一个参数,就是转码后的 URL。

decodeURI('http://www.example.com/q=%E6%98%A5%E8%8A%82')
// "http://www.example.com/q=春节"

63.2.4 decodeURIComponent()

decodeURIComponent()用于URL 片段的解码。它是encodeURIComponent()方法的逆运算。它接受一个参数,就是转码后的 URL 片段。

decodeURIComponent('%E6%98%A5%E8%8A%82')
// "春节"

63.3 URL 接口

浏览器原生提供URL()接口,它是一个构造函数,用来构造、解析和编码 URL。一般情况下,通过window.URL可以拿到这个构造函数。

63.3.1 构造函数

URL()作为构造函数,可以生成 URL 实例。它接受一个表示 URL 的字符串作为参数。如果参数不是合法的 URL,会报错。

var url = new URL('http://www.example.com/index.html');
url.href
// "http://www.example.com/index.html"

上面示例生成了一个 URL 实例,用来代表指定的网址。

除了字符串,URL()的参数也可以是另一个 URL 实例。这时,URL()会自动读取该实例的href属性,作为实际参数。

如果 URL 字符串是一个相对路径,那么需要表示绝对路径的第二个参数,作为计算基准。

var url1 = new URL('index.html', 'http://example.com');
url1.href
// "http://example.com/index.html"

var url2 = new URL('page2.html', 'http://example.com/page1.html');
url2.href
// "http://example.com/page2.html"

var url3 = new URL('..', 'http://example.com/a/b.html')
url3.href
// "http://example.com/"

上面代码中,返回的 URL 实例的路径都是在第二个参数的基础上,切换到第一个参数得到的。最后一个例子里面,第一个参数是..,表示上层路径。

63.3.2 实例属性

URL 实例的属性与Location对象的属性基本一致,返回当前 URL 的信息。

  • URL.href:返回整个 URL
  • URL.protocol:返回协议,以冒号:结尾
  • URL.hostname:返回域名
  • URL.host:返回域名与端口,包含:号,默认的80和443端口会省略
  • URL.port:返回端口
  • URL.origin:返回协议、域名和端口
  • URL.pathname:返回路径,以斜杠/开头
  • URL.search:返回查询字符串,以问号?开头
  • URL.searchParams:返回一个URLSearchParams实例,该属性是Location对象没有的
  • URL.hash:返回片段识别符,以井号#开头
  • URL.password:返回域名前面的密码
  • URL.username:返回域名前面的用户名
var url = new URL('http://user:passwd@www.example.com:4097/path/a.html?x=111#part1');

url.href
// "http://user:passwd@www.example.com:4097/path/a.html?x=111#part1"
url.protocol
// "http:"
url.hostname
// "www.example.com"
url.host
// "www.example.com:4097"
url.port
// "4097"
url.origin
// "http://www.example.com:4097"
url.pathname
// "/path/a.html"
url.search
// "?x=111"
url.searchParams
// URLSearchParams {}
url.hash
// "#part1"
url.password
// "passwd"
url.username
// "user"

这些属性里面,只有origin属性是只读的,其他属性都可写,并且会立即生效。

var url = new URL('http://example.com/index.html#part1');

url.pathname = 'index2.html';
url.href // "http://example.com/index2.html#part1"

url.hash = '#part2';
url.href // "http://example.com/index2.html#part2"

上面代码中,改变 URL 实例的pathname属性和hash属性,都会实时反映在 URL 实例当中。

63.3.3 静态方法

(1)URL.createObjectURL()

URL.createObjectURL()方法用来为上传/下载的文件、流媒体文件生成一个 URL 字符串。这个字符串代表了File对象或Blob对象的 URL。

// HTML 代码如下
// <div id="display"/>
// <input
//   type="file"
//   id="fileElem"
//   multiple
//   accept="image/*"
//   οnchange="handleFiles(this.files)"
//  >
var div = document.getElementById('display');

function handleFiles(files) {
  for (var i = 0; i < files.length; i++) {
    var img = document.createElement('img');
    img.src = window.URL.createObjectURL(files[i]);
    div.appendChild(img);
  }
}

上面代码中,URL.createObjectURL()方法用来为上传的文件生成一个 URL 字符串,作为<img>元素的图片来源。

该方法生成的 URL 就像下面的样子。

blob:http://localhost/c745ef73-ece9-46da-8f66-ebes574789b1

注意,每次使用URL.createObjectURL()方法,都会在内存里面生成一个 URL 实例。如果不再需要该方法生成的 URL 字符串,为了节省内存,可以使用URL.revokeObjectURL()方法释放这个实例。

(2)URL.revokeObjectURL()

URL.revokeObjectURL()方法用来释放URL.createObjectURL()方法生成的 URL 实例。它的参数就是URL.createObjectURL()方法返回的 URL 字符串。

下面为上一段的示例加上URL.revokeObjectURL()

var div = document.getElementById('display');

function handleFiles(files) {
  for (var i = 0; i < files.length; i++) {
    var img = document.createElement('img');
    img.src = window.URL.createObjectURL(files[i]);
    div.appendChild(img);
    img.onload = function() {
      window.URL.revokeObjectURL(this.src);
    }
  }
}

上面代码中,一旦图片加载成功以后,为本地文件生成的 URL 字符串就没用了,于是可以在img.onload回调函数里面,通过URL.revokeObjectURL()方法卸载这个 URL 实例。

63.4 URLSearchParams 对象(处理URL 问号后面的部分

63.4.1 概述

URLSearchParams对象是浏览器的原生对象,用来构造、解析和处理 URL 的查询字符串(即 URL 问号后面的部分)。

它本身也是一个构造函数,可以生成实例。参数可以为查询字符串,起首的问号?有没有都行,也可以是对应查询字符串的数组或对象。

// 方法一:传入字符串
var params = new URLSearchParams('?foo=1&bar=2');
// 等同于
var params = new URLSearchParams(document.location.search);

// 方法二:传入数组
var params = new URLSearchParams([['foo', 1], ['bar', 2]]);

// 方法三:传入对象
var params = new URLSearchParams({'foo' : 1 , 'bar' : 2});

URLSearchParams会对查询字符串自动编码。

var params = new URLSearchParams({'foo': '你好'});
params.toString() // "foo=%E4%BD%A0%E5%A5%BD"

上面代码中,foo的值是汉字,URLSearchParams对其自动进行 URL 编码。

浏览器向服务器发送表单数据时,可以直接使用URLSearchParams实例作为表单数据。

const params = new URLSearchParams({foo: 1, bar: 2});
fetch('https://example.com/api', {
  method: 'POST',
  body: params
}).then(...)

上面代码中,fetch命令向服务器发送命令时,可以直接使用URLSearchParams实例。

URLSearchParams可以与URL()接口结合使用。

var url = new URL(window.location);
var foo = url.searchParams.get('foo') || 'somedefault';

上面代码中,URL 实例的searchParams属性就是一个URLSearchParams实例,所以可以使用URLSearchParams接口的get方法。

URLSearchParams实例有遍历器接口,可以用for...of循环遍历(详见《ES6 标准入门》的《Iterator》一章)。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});

for (var p of params) {
  console.log(p[0] + ': ' + p[1]);
}
// foo: 1
// bar: 2

URLSearchParams没有实例属性,只有实例方法。

63.4.2 URLSearchParams.toString()

toString方法返回实例的字符串形式。

var url = new URL('https://example.com?foo=1&bar=2');
var params = new URLSearchParams(url.search);

params.toString() // "foo=1&bar=2'

那么需要字符串的场合,会自动调用toString方法。

var params = new URLSearchParams({version: 2.0});
window.location.href = location.pathname + '?' + params;

上面代码中,location.href赋值时,可以直接使用params对象。这时就会自动调用toString方法。

63.4.3 URLSearchParams.append()

append()方法用来追加一个查询参数。它接受两个参数,第一个为键名,第二个为键值,没有返回值。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.append('baz', 3);
params.toString() // "foo=1&bar=2&baz=3"

append()方法不会识别是否键名已经存在

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.append('foo', 3);
params.toString() // "foo=1&bar=2&foo=3"

上面代码中,查询字符串里面foo已经存在了,但是append依然会追加一个同名键。

63.4.4 URLSearchParams.delete()

delete()方法用来删除指定的查询参数。它接受键名作为参数。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.delete('bar');
params.toString() // "foo=1"

63.4.5 URLSearchParams.has()

has()方法返回一个布尔值,表示查询字符串是否包含指定的键名。

var params = new URLSearchParams({'foo': 1 , 'bar': 2});
params.has('bar') // true
params.has('baz') // false

63.4.6 URLSearchParams.set()

set()方法用来设置查询字符串的键值。

它接受两个参数,第一个是键名,第二个是键值。如果是已经存在的键,键值会被改写,否则会被追加。

var params = new URLSearchParams('?foo=1');
params.set('foo', 2);
params.toString() // "foo=2"
params.set('bar', 3);
params.toString() // "foo=2&bar=3"

上面代码中,foo是已经存在的键,bar是还不存在的键。

如果有多个的同名键,set会移除现存所有的键。

var params = new URLSearchParams('?foo=1&foo=2');
params.set('foo', 3);
params.toString() // "foo=3"

下面是一个替换当前 URL 的例子。

// URL: https://example.com?version=1.0
var params = new URLSearchParams(location.search.slice(1));
params.set('version', '2.0');

window.history.replaceState({}, '', location.pathname + `?` + params);
// URL: https://example.com?version=2.0

63.4.7 URLSearchParams.get(),URLSearchParams.getAll()

get()方法用来读取查询字符串里面的指定键。它接受键名作为参数。

var params = new URLSearchParams('?foo=1');
params.get('foo') // "1"
params.get('bar') // null

两个地方需要注意。第一,它返回的是字符串,如果原始值是数值,需要转一下类型;第二,如果指定的键名不存在,返回值是null

如果有多个的同名键,get返回位置最前面的那个键值。

var params = new URLSearchParams('?foo=3&foo=2&foo=1');
params.get('foo') // "3"

上面代码中,查询字符串有三个foo键,get方法返回最前面的键值3

getAll()方法返回一个数组,成员是指定键的所有键值。它接受键名作为参数。

var params = new URLSearchParams('?foo=1&foo=2');
params.getAll('foo') // ["1", "2"]

上面代码中,查询字符串有两个foo键,getAll返回的数组就有两个成员。

63.4.8 URLSearchParams.sort()

sort()方法对查询字符串里面的键进行排序,规则是按照 Unicode 码点从小到大排列。

该方法没有返回值,或者说返回值是undefined

var params = new URLSearchParams('c=4&a=2&b=3&a=1');
params.sort();
params.toString() // "a=2&a=1&b=3&c=4"

上面代码中,如果有两个同名的键a,它们之间不会排序,而是保留原始的顺序。

63.4.9 URLSearchParams.keys(),URLSearchParams.values(),URLSearchParams.entries()

这三个方法都返回一个遍历器对象,供for...of循环遍历。它们的区别在于,keys方法返回的是键名的遍历器,values方法返回的是键值的遍历器entries返回的是键值对的遍历器。

var params = new URLSearchParams('a=1&b=2');

for(var p of params.keys()) {
  console.log(p);
}
// a
// b

for(var p of params.values()) {
  console.log(p);
}
// 1
// 2

for(var p of params.entries()) {
  console.log(p);
}
// ["a", "1"]
// ["b", "2"]

如果直接对URLSearchParams进行遍历,其实内部调用的就是entries接口。

for (var p of params) {}
// 等同于
for (var p of params.entries()) {}

64. ArrayBuffer 对象,Blob 对象

64.1 ArrayBuffer 对象(内存数据

ArrayBuffer 对象表示一段二进制数据,用来模拟内存里面的数据。通过这个对象,JavaScript 可以读写二进制数据。这个对象可以看作内存数据的表达

这个对象是 ES6 才写入标准的,普通的网页编程用不到它,为了教程体系的完整,下面只提供一个简略的介绍,详细介绍请看《ES6 标准入门》里面的章节。

浏览器原生提供ArrayBuffer()构造函数,用来生成实例。它接受一个整数作为参数,表示这段二进制数据占用多少个字节。

var buffer = new ArrayBuffer(8);

上面代码中,实例对象buffer占用8个字节。

ArrayBuffer 对象有实例属性byteLength,表示当前实例占用的内存长度(单位字节)。

var buffer = new ArrayBuffer(8);
buffer.byteLength // 8

ArrayBuffer 对象有实例方法slice(),用来复制一部分内存。它接受两个整数参数,分别表示复制的开始位置(从0开始)和结束位置(复制时不包括结束位置),如果省略第二个参数,则表示一直复制到结束。

var buf1 = new ArrayBuffer(8);
var buf2 = buf1.slice(0);

上面代码表示复制原来的实例。

64.2 Blob 对象(二进制大型对象

64.2.1 简介

Blob 对象表示一个二进制文件的数据内容,比如一个图片文件的内容就可以通过 Blob 对象读写。它通常用来读写文件,它的名字是 ==Binary Large Object (二进制大型对象)==的缩写。它与 ArrayBuffer 的区别在于,它用于操作二进制文件,而 ArrayBuffer 用于操作内存。

浏览器原生提供Blob()构造函数,用来生成实例对象。

new Blob(array [, options])

Blob构造函数接受两个参数。第一个参数是数组,成员是字符串或二进制对象,表示新生成的Blob实例对象的内容;第二个参数是可选的,是一个配置对象,目前只有一个属性type,它的值是一个字符串,表示数据的 MIME 类型,默认是空字符串。

var htmlFragment = ['<a id="a"><b id="b">hey!</b></a>'];
var myBlob = new Blob(htmlFragment, {type : 'text/html'});

上面代码中,实例对象myBlob包含的是字符串。生成实例的时候,数据类型指定为text/html

下面是另一个例子,Blob 保存 JSON 数据。

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

64.2.2 实例属性和实例方法

Blob具有两个实例属性sizetype,分别返回数据的大小和类型。

var htmlFragment = ['<a id="a"><b id="b">hey!</b></a>'];
var myBlob = new Blob(htmlFragment, {type : 'text/html'});

myBlob.size // 32
myBlob.type // "text/html"

Blob具有一个实例方法slice,用来拷贝原来的数据,返回的也是一个Blob实例。

myBlob.slice(start, end, contentType)

slice方法有三个参数,都是可选的。它们依次是起始的字节位置(默认为0)、结束的字节位置(默认为size属性的值,该位置本身将不包含在拷贝的数据之中)、新实例的数据类型(默认为空字符串)。

64.2.3 获取文件信息

文件选择器<input type="file">用来让用户选取文件。出于安全考虑,浏览器不允许脚本自行设置这个控件的value属性,即文件必须是用户手动选取的,不能是脚本指定的。一旦用户选好了文件,脚本就可以读取这个文件。

文件选择器返回一个 FileList 对象,该对象是一个类似数组的成员,每个成员都是一个 File 实例对象。File 实例对象是一个特殊的 Blob 实例,增加了namelastModifiedDate属性。

// HTML 代码如下
// <input type="file" accept="image/*" multiple οnchange="fileinfo(this.files)"/>

function fileinfo(files) {
  for (var i = 0; i < files.length; i++) {
    var f = files[i];
    console.log(
      f.name, // 文件名,不含路径
      f.size, // 文件大小,Blob 实例属性
      f.type, // 文件类型,Blob 实例属性
      f.lastModifiedDate // 文件的最后修改时间
    );
  }
}

除了文件选择器,拖放 API 的dataTransfer.files返回的也是一个FileList 对象,它的成员因此也是 File 实例对象。

64.2.4 下载文件

AJAX 请求时,如果指定responseType属性为blob,下载下来的就是一个 Blob 对象。

function getBlob(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.responseType = 'blob';
  xhr.onload = function () {
    callback(xhr.response);
  }
  xhr.send(null);
}

上面代码中,xhr.response拿到的就是一个 Blob 对象。

64.2.5 生成 URL

浏览器允许使用URL.createObjectURL()方法,针对 Blob 对象生成一个临时 URL,以便于某些 API 使用。这个 URL 以blob://开头,表明对应一个 Blob 对象,协议头后面是一个识别符,用来唯一对应内存里面的 Blob 对象。这一点与data://URL(URL 包含实际数据)和file://URL(本地文件系统里面的文件)都不一样。

var droptarget = document.getElementById('droptarget');

droptarget.ondrop = function (e) {
  var files = e.dataTransfer.files;
  for (var i = 0; i < files.length; i++) {
    var type = files[i].type;
    if (type.substring(0,6) !== 'image/')
      continue;
    var img = document.createElement('img');
    img.src = URL.createObjectURL(files[i]);
    img.onload = function () {
      this.width = 100;
      document.body.appendChild(this);
      URL.revokeObjectURL(this.src);
    }
  }
}

上面代码通过为拖放的图片文件生成一个 URL,产生它们的缩略图,从而使得用户可以预览选择的文件。

浏览器处理 Blob URL 就跟普通的 URL 一样,如果 Blob 对象不存在,返回404状态码;如果跨域请求,返回403状态码。Blob URL 只对 GET 请求有效,如果请求成功,返回200状态码。由于 Blob URL 就是普通 URL,因此可以下载。

64.2.6 读取文件

取得 Blob 对象以后,可以通过FileReader对象,读取 Blob 对象的内容,即文件内容。

FileReader 对象提供四个方法,处理 Blob 对象。Blob 对象作为参数传入这些方法,然后以指定的格式返回。

  • FileReader.readAsText():返回文本,需要指定文本编码,默认为 UTF-8。
  • FileReader.readAsArrayBuffer():返回 ArrayBuffer 对象。
  • FileReader.readAsDataURL():返回 Data URL。
  • FileReader.readAsBinaryString():返回原始的二进制字符串。

下面是FileReader.readAsText()方法的例子,用来读取文本文件。

// HTML 代码如下
// <input type="file" οnchange="readfile(this.files[0])"></input>
// <pre id="output"></pre>
function readfile(f) {
  var reader = new FileReader();
  reader.readAsText(f);
  reader.onload = function () {
    var text = reader.result;
    var out = document.getElementById('output');
    out.innerHTML = '';
    out.appendChild(document.createTextNode(text));
  }
  reader.onerror = function(e) {
    console.log('Error', e);
  };
}

上面代码中,通过指定 FileReader 实例对象的onload监听函数,在实例的result属性上拿到文件内容。

下面是FileReader.readAsArrayBuffer()方法的例子,用于读取二进制文件。

// HTML 代码如下
// <input type="file" οnchange="typefile(this.files[0])"></input>
function typefile(file) {
  // 文件开头的四个字节,生成一个 Blob 对象
  var slice = file.slice(0, 4);
  var reader = new FileReader();
  // 读取这四个字节
  reader.readAsArrayBuffer(slice);
  reader.onload = function (e) {
    var buffer = reader.result;
    // 将这四个字节的内容,视作一个32位整数
    var view = new DataView(buffer);
    var magic = view.getUint32(0, false);
    // 根据文件的前四个字节,判断它的类型
    switch(magic) {
      case 0x89504E47: file.verified_type = 'image/png'; break;
      case 0x47494638: file.verified_type = 'image/gif'; break;
      case 0x25504446: file.verified_type = 'application/pdf'; break;
      case 0x504b0304: file.verified_type = 'application/zip'; break;
    }
    console.log(file.name, file.verified_type);
  };
}

65. File 对象(继承Blob),FileList 对象,FileReader 对象

65.1 File 对象

File 对象代表一个文件,用来读写文件信息。它继承了 Blob 对象,或者说是一种特殊的 Blob 对象,所有可以使用 Blob 对象的场合都可以使用它。

最常见的使用场合是表单的文件上传控件(<input type="file">),用户选中文件以后,浏览器就会生成一个数组,里面是每一个用户选中的文件,它们都是 File 实例对象。

// HTML 代码如下
// <input id="fileItem" type="file">
var file = document.getElementById('fileItem').files[0];
file instanceof File // true

上面代码中,file是用户选中的第一个文件,它是 File 的实例。

65.1.1 构造函数

浏览器原生提供一个File()构造函数,用来生成 File 实例对象。

new File(array, name [, options])

File()构造函数接受三个参数。

  • array:一个数组,成员可以是二进制对象或字符串,表示文件的内容。
  • name:字符串,表示文件名或文件路径。
  • options:配置对象,设置实例的属性。该参数可选。

第三个参数配置对象,可以设置两个属性。

  • type:字符串,表示实例对象的 MIME 类型,默认值为空字符串。
  • **lastModified:时间戳,**表示上次修改的时间,默认为Date.now()

下面是一个例子。

var file = new File(
  ['foo'],
  'foo.txt',
  {
    type: 'text/plain',
  }
);

65.1.2 实例属性和实例方法

File 对象有以下实例属性。

  • File.lastModified:最后修改时间
  • File.name:文件名或文件路径
  • File.size:文件大小(单位字节)
  • File.type:文件的 MIME 类型
var myFile = new File([], 'file.bin', {
  lastModified: new Date(2018, 1, 1),
});
myFile.lastModified // 1517414400000
myFile.name // "file.bin"
myFile.size // 0
myFile.type // ""

上面代码中,由于myFile的内容为空,也没有设置 MIME 类型,所以size属性等于0,type属性等于空字符串。

File 对象没有自己的实例方法,由于继承了 Blob 对象,因此可以使用 Blob 的实例方法slice()

65.2 FileList 对象

FileList对象是一个类似数组的对象,代表一组选中的文件,每个成员都是一个 File 实例。它主要出现在两个场合。

  • 文件控件节点(<input type="file">)的files属性,返回一个 FileList 实例。
  • 拖拉一组文件时,目标区的DataTransfer.files属性,返回一个 FileList 实例。
// HTML 代码如下
// <input id="fileItem" type="file">
var files = document.getElementById('fileItem').files;
files instanceof FileList // true

上面代码中,文件控件的files属性是一个 FileList 实例。

FileList 的实例属性主要是length,表示包含多少个文件。

FileList 的实例方法主要是item(),用来返回指定位置的实例。它接受一个整数作为参数,表示位置的序号(从零开始)。但是,由于 FileList 的实例是一个类似数组的对象,可以直接用方括号运算符,即myFileList[0]等同于myFileList.item(0),所以一般用不到item()方法。

65.3 FileReader 对象

FileReader 对象用于读取 File 对象或 Blob 对象所包含的文件内容。

浏览器原生提供一个FileReader构造函数,用来生成 FileReader 实例。

var reader = new FileReader();

FileReader 有以下的实例属性。

  • FileReader.error:读取文件时产生的错误对象
  • FileReader.readyState:整数,表示读取文件时的当前状态。一共有三种可能的状态,0表示尚未加载任何数据,1表示数据正在加载,2表示加载完成。
  • FileReader.result:读取完成后的文件内容,有可能是字符串,也可能是一个 ArrayBuffer 实例。
  • FileReader.onabort:abort事件(用户终止读取操作)的监听函数。
  • FileReader.onerror:error事件(读取错误)的监听函数。
  • FileReader.onload:load事件(读取操作完成)的监听函数,通常在这个函数里面使用result属性,拿到文件内容。
  • FileReader.onloadstart:loadstart事件(读取操作开始)的监听函数。
  • FileReader.onloadend:loadend事件(读取操作结束)的监听函数。
  • FileReader.onprogress:progress事件(读取操作进行中)的监听函数。

下面是监听load事件的一个例子。

// HTML 代码如下
// <input type="file" οnchange="onChange(event)">

function onChange(event) {
  var file = event.target.files[0];
  var reader = new FileReader();
  reader.onload = function (event) {
    console.log(event.target.result)
  };

  reader.readAsText(file);
}

上面代码中,每当文件控件发生变化,就尝试读取第一个文件。如果读取成功(load事件发生),就打印出文件内容。

FileReader 有以下实例方法。

  • FileReader.abort():终止读取操作,readyState属性将变成2
  • FileReader.readAsArrayBuffer():以 ArrayBuffer 的格式读取文件,读取完成后result属性将返回一个 ArrayBuffer 实例。
  • FileReader.readAsBinaryString():读取完成后,result属性将返回原始的二进制字符串。
  • FileReader.readAsDataURL():读取完成后,result属性将返回一个 Data URL 格式(Base64 编码)的字符串,代表文件内容。对于图片文件,这个字符串可以用于<img>元素的src属性。注意,这个字符串不能直接进行 Base64 解码,必须把前缀data:*/*;base64,从字符串里删除以后,再进行解码。
  • FileReader.readAsText():读取完成后,result属性将返回文件内容的文本字符串。该方法的第一个参数是代表文件的 Blob 实例,第二个参数是可选的,表示文本编码,默认为 UTF-8。

下面是一个例子。

/* HTML 代码如下
  <input type="file" οnchange="previewFile()">
  <img src="" height="200">
*/

function previewFile() {
  var preview = document.querySelector('img');
  var file    = document.querySelector('input[type=file]').files[0];
  var reader  = new FileReader();

  reader.addEventListener('load', function () {
    preview.src = reader.result;
  }, false);

  if (file) {
    reader.readAsDataURL(file);
  }
}

上面代码中,用户选中图片文件以后,脚本会自动读取文件内容,然后作为一个 Data URL 赋值给<img>元素的src属性,从而把图片展示出来。

66. 表单,FormData 对象

66.1 表单概述

表单(<form>)用来收集用户提交的数据,发送到服务器。比如,用户提交用户名和密码,让服务器验证,就要通过表单。表单提供多种控件,让开发者使用,具体的控件种类和用法请参考 HTML 语言的教程。本章主要介绍 JavaScript 与表单的交互。

<form action="/handling-page" method="post">
  <div>
    <label for="name">用户名:</label>
    <input type="text" id="name" name="user_name" />
  </div>
  <div>
    <label for="passwd">密码:</label>
    <input type="password" id="passwd" name="user_passwd" />
  </div>
  <div>
    <input type="submit" id="submit" name="submit_button" value="提交" />
  </div>
</form>

上面代码就是一个简单的表单,包含三个控件:用户名输入框、密码输入框和提交按钮。

用户点击“提交”按钮,每一个控件都会生成一个键值对,键名是控件的name属性,键值是控件的value属性,键名和键值之间由等号连接。比如,用户名输入框的name属性是user_namevalue属性是用户输入的值,假定是“张三”,提交到服务器的时候,就会生成一个键值对user_name=张三

所有的键值对都会提交到服务器。但是,提交的数据格式跟<form>元素的method属性有关。该属性指定了提交数据的 HTTP 方法。如果是 GET 方法,所有键值对会以 URL 的查询字符串形式,提交到服务器,比如/handling-page?user_name=张三&user_passwd=123&submit_button=提交。下面就是 GET 请求的 HTTP 头信息。

GET /handling-page?user_name=张三&user_passwd=123&submit_button=提交
Host: example.com

如果是 POST 方法,所有键值对会连接成一行,作为 HTTP 请求的数据体发送到服务器,比如user_name=张三&user_passwd=123&submit_button=提交。下面就是 POST 请求的头信息。

POST /handling-page HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 74

user_name=张三&user_passwd=123&submit_button=提交

注意,实际提交的时候,只要键值不是 URL 的合法字符(比如汉字“张三”和“提交”),浏览器会自动对其进行编码。

点击submit控件,就可以提交表单。

<form>
  <input type="submit" value="提交">
</form>

上面表单就包含一个submit控件,点击这个控件,浏览器就会把表单数据向服务器提交。

注意,表单里面的<button>元素如果没有用type属性指定类型,那么默认就是submit控件。

<form>
  <button>提交</button>
</form>

上面表单的<button>元素,点击以后也会提交表单。

除了点击submit控件提交表单,还可以用表单元素的submit()方法,通过脚本提交表单。

formElement.submit();

表单元素的reset()方法可以重置所有控件的值(重置为默认值)。

formElement.reset()

66.2 FormData 对象

66.2.1 概述

**表单数据以键值对的形式向服务器发送,这个过程是浏览器自动完成的。但是有时候,我们希望通过脚本完成这个过程,构造或编辑表单的键值对,然后通过脚本发送给服务器。**浏览器原生提供了 FormData 对象来完成这项工作。

FormData()首先是一个构造函数,用来生成表单的实例。

var formdata = new FormData(form);

FormData()构造函数的参数是一个 DOM 的表单元素,构造函数会自动处理表单的键值对。这个参数是可选的,如果省略该参数,就表示一个空的表单。

下面是一个表单。

<form id="myForm" name="myForm">
  <div>
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username">
  </div>
  <div>
    <label for="useracc">账号:</label>
    <input type="text" id="useracc" name="useracc">
  </div>
  <div>
    <label for="userfile">上传文件:</label>
    <input type="file" id="userfile" name="userfile">
  </div>
<input type="submit" value="Submit!">
</form>

我们用FormData()处理上面这个表单。

var myForm = document.getElementById('myForm');
var formData = new FormData(myForm);

// 获取某个控件的值
formData.get('username') // ""

// 设置某个控件的值
formData.set('username', '张三');

formData.get('username') // "张三"

66.2.2 实例方法

FormData 提供以下实例方法。

  • FormData.get(key):获取指定键名对应的键值,参数为键名。如果有多个同名的键值对,则返回第一个键值对的键值。
  • FormData.getAll(key):返回一个数组,表示指定键名对应的所有键值。如果有多个同名的键值对,数组会包含所有的键值。
  • FormData.set(key, value):设置指定键名的键值,参数为键名。如果键名不存在,会添加这个键值对,否则会更新指定键名的键值。如果第二个参数是文件,还可以使用第三个参数,表示文件名。
  • FormData.delete(key):删除一个键值对,参数为键名。
  • FormData.append(key, value):添加一个键值对。如果键名重复,则会生成两个相同键名的键值对。如果第二个参数是文件,还可以使用第三个参数,表示文件名。
  • FormData.has(key):返回一个布尔值,表示是否具有该键名的键值对。
  • FormData.keys():返回一个遍历器对象,用于for...of循环遍历所有的键名。
  • FormData.values():返回一个遍历器对象,用于for...of循环遍历所有的键值。
  • FormData.entries():返回一个遍历器对象,用于for...of循环遍历所有的键值对。如果直接用for...of循环遍历 FormData 实例,默认就会调用这个方法。

下面是get()getAll()set()append()方法的例子。

var formData = new FormData();

formData.set('username', '张三');
formData.append('username', '李四');
formData.get('username') // "张三"
formData.getAll('username') // ["张三", "李四"]

formData.append('userpic[]', myFileInput.files[0], 'user1.jpg');
formData.append('userpic[]', myFileInput.files[1], 'user2.jpg');

下面是遍历器的例子。

var formData = new FormData();
formData.append('key1', 'value1');
formData.append('key2', 'value2');

for (var key of formData.keys()) {
  console.log(key);
}
// "key1"
// "key2"

for (var value of formData.values()) {
  console.log(value);
}
// "value1"
// "value2"

for (var pair of formData.entries()) {
  console.log(pair[0] + ': ' + pair[1]);
}
// key1: value1
// key2: value2

// 等同于遍历 formData.entries()
for (var pair of formData) {
  console.log(pair[0] + ': ' + pair[1]);
}
// key1: value1
// key2: value2

66.3 表单的内置验证

66.3.1 自动校验(required/pattern/minlength/type :invalid)

表单提交的时候,浏览器允许开发者指定一些条件,它会自动验证各个表单控件的值是否符合条件。

<!-- 必填 -->
<input required>

<!-- 必须符合正则表达式 -->
<input pattern="banana|cherry">

<!-- 字符串长度必须为6个字符 -->
<input minlength="6" maxlength="6">

<!-- 数值必须在1到10之间 -->
<input type="number" min="1" max="10">

<!-- 必须填入 Email 地址 -->
<input type="email">

<!-- 必须填入 URL -->
<input type="URL">

如果一个控件通过验证,它就会匹配:valid的 CSS 伪类,浏览器会继续进行表单提交的流程。如果没有通过验证,该控件就会匹配:invalid的 CSS 伪类,浏览器会终止表单提交,并显示一个错误信息。

input:invalid {
  border-color: red;
}
input,
input:valid {
  border-color: #ccc;
}

66.3.2 checkValidity()

除了提交表单的时候,浏览器自动校验表单,还可以手动触发表单的校验。表单元素和表单控件都有checkValidity()方法,用于手动触发校验。

// 触发整个表单的校验
form.checkValidity()

// 触发单个表单控件的校验
formControl.checkValidity()

checkValidity()方法返回一个布尔值,true表示通过校验,false表示没有通过校验。因此,提交表单可以封装为下面的函数。

function submitForm(action) {
  var form = document.getElementById('form');
  form.action = action;
  if (form.checkValidity()) {
    form.submit();
  }
}

66.3.3 willValidate 属性

控件元素的willValidate属性是一个布尔值,表示该控件是否会在提交时进行校验。

// HTML 代码如下
// <form novalidate>
//   <input id="name" name="name" required />
// </form>

var input = document.querySelector('#name');
input.willValidate // true

66.3.4 validationMessage 属性

控件元素的validationMessage属性返回一个字符串,表示控件不满足校验条件时,浏览器显示的提示文本。以下两种情况,该属性返回空字符串。

  • 该控件不会在提交时自动校验
  • 该控件满足校验条件
// HTML 代码如下
// <form><input type="text" required></form>
document.querySelector('form input').validationMessage
// "请填写此字段。"

下面是另一个例子。

var myInput = document.getElementById('myinput');
if (!myInput.checkValidity()) {
  document.getElementById('prompt').innerHTML = myInput.validationMessage;
}

66.3.5 setCustomValidity()

控件元素的setCustomValidity()方法用来定制校验失败时的报错信息。它接受一个字符串作为参数,该字符串就是定制的报错信息。如果参数为空字符串,则上次设置的报错信息被清除。

这个方法可以替换浏览器内置的表单验证报错信息,参数就是要显示的报错信息。

<form action="somefile.php">
  <input
    type="text"
    name="username"
    placeholder="Username"
    pattern="[a-z]{1,15}"
    id="username"
  >
  <input type="submit">
</form>

上面的表单输入框,要求只能输入小写字母,且不得超过15个字符。如果输入不符合要求(比如输入“ABC”),提交表单的时候,Chrome 浏览器会弹出报错信息“Please match the requested format.”,禁止表单提交。下面使用setCustomValidity()方法替换掉报错信息。

var input = document.getElementById('username');
input.oninvalid = function (event) {
  event.target.setCustomValidity(
    '用户名必须是小写字母,不能为空,最长不超过15个字符'
  );
}

上面代码中,setCustomValidity()方法是在invalid事件的监听函数里面调用。该方法也可以直接调用,这时如果参数不为空字符串,浏览器就会认为该控件没有通过校验,就会立刻显示该方法设置的报错信息。

/* HTML 代码如下
<form>
  <p><input type="file" id="fs"></p>
  <p><input type="submit"></p>
</form>
*/

document.getElementById('fs').onchange = checkFileSize;

function checkFileSize() {
  var fs = document.getElementById('fs');
  var files = fs.files;
  if (files.length > 0) {
     if (files[0].size > 75 * 1024) {
       fs.setCustomValidity('文件不能大于 75KB');
       return;
     }
  }
  fs.setCustomValidity('');
}

上面代码一旦发现文件大于 75KB,就会设置校验失败,同时给出自定义的报错信息。然后,点击提交按钮时,就会显示报错信息。这种校验失败是不会自动消除的,所以如果所有文件都符合条件,要将报错信息设为空字符串,手动消除校验失败的状态

66.3.6 validity 属性

控件元素的属性validity属性返回一个ValidityState对象,包含当前校验状态的信息。

该对象有以下属性,全部为只读属性。

  • ValidityState.badInput:布尔值,表示浏览器是否不能将用户的输入转换成正确的类型,比如用户在数值框里面输入字符串。
  • ValidityState.customError:布尔值,表示是否已经调用setCustomValidity()方法,将校验信息设置为一个非空字符串。
  • ValidityState.patternMismatch:布尔值,表示用户输入的值是否不满足模式的要求。
  • ValidityState.rangeOverflow:布尔值,表示用户输入的值是否大于最大范围。
  • ValidityState.rangeUnderflow:布尔值,表示用户输入的值是否小于最小范围。
  • ValidityState.stepMismatch:布尔值,表示用户输入的值不符合步长的设置(即不能被步长值整除)。
  • ValidityState.tooLong:布尔值,表示用户输入的字数超出了最长字数。
  • ValidityState.tooShort:布尔值,表示用户输入的字符少于最短字数。
  • ValidityState.typeMismatch:布尔值,表示用户填入的值不符合类型要求(主要是类型为 Email 或 URL 的情况)。
  • ValidityState.valid:布尔值,表示用户是否满足所有校验条件。
  • ValidityState.valueMissing:布尔值,表示用户没有填入必填的值。

下面是一个例子。

var input = document.getElementById('myinput');
if (input.validity.valid) {
  console.log('通过校验');
} else {
  console.log('校验失败');
}

下面是另外一个例子。

var txt = '';
if (document.getElementById('myInput').validity.rangeOverflow) {
  txt = '数值超过上限';
}
document.getElementById('prompt').innerHTML = txt;

如果想禁止浏览器弹出表单验证的报错信息,可以监听invalid事件。

var input = document.getElementById('username');
var form  = document.getElementById('form');

var elem = document.createElement('div');
elem.id  = 'notify';
elem.style.display = 'none';
form.appendChild(elem);

input.addEventListener('invalid', function (event) {
  event.preventDefault();
  if (!event.target.validity.valid) {
    elem.textContent   = '用户名必须是小写字母';
    elem.className     = 'error';
    elem.style.display = 'block';
    input.className    = 'invalid animated shake';
  }
});

input.addEventListener('input', function(event){
  if ( 'block' === elem.style.display ) {
    input.className = '';
    elem.style.display = 'none';
  }
});

上面代码中,一旦发生invalid事件(表单验证失败),event.preventDefault()用来禁止浏览器弹出默认的验证失败提示,然后设置定制的报错提示框。

66.3.7 表单的 novalidate 属性

表单元素的 HTML 属性novalidate,可以关闭浏览器的自动校验。

<form novalidate>
</form>

这个属性也可以在脚本里设置。

form.noValidate = true;

如果表单元素没有设置novalidate属性,那么提交按钮(<button><input>元素)的formnovalidate属性也有同样的作用。

<form>
  <input type="submit" value="submit" formnovalidate>
</form>

66.4 enctype 属性

表单能够用四种编码,向服务器发送数据。编码格式由表单的enctype属性决定。

假定表单有两个字段,分别是foobaz,其中foo字段的值等于barbaz字段的值是一个分为两行的字符串。

The first line.
The second line.

下面四种格式,都可以将这个表单发送到服务器。

(1)GET 方法

如果表单使用GET方法发送数据,enctype属性无效。

<form
  action="register.php"
  method="get"
  onsubmit="AJAXSubmit(this); return false;"
>
</form>

数据将以 URL 的查询字符串发出。

?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.

(2)application/x-www-form-urlencoded

如果表单用POST方法发送数据,并省略enctype属性,那么数据以application/x-www-form-urlencoded格式发送(因为这是默认值)。

<form
  action="register.php"
  method="post"
  onsubmit="AJAXSubmit(this); return false;"
>
</form>

发送的 HTTP 请求如下。

Content-Type: application/x-www-form-urlencoded

foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A

上面代码中,数据体里面的%0D%0A代表换行符(\r\n)。

(3)text/plain

如果表单使用POST方法发送数据,enctype属性为text/plain,那么数据将以纯文本格式发送。

<form
  action="register.php"
  method="post"
  enctype="text/plain"
  onsubmit="AJAXSubmit(this); return false;"
>
</form>

发送的 HTTP 请求如下。

Content-Type: text/plain

foo=bar
baz=The first line.
The second line.

(4)multipart/form-data

如果表单使用POST方法,enctype属性为multipart/form-data,那么数据将以混合的格式发送。

<form
  action="register.php"
  method="post"
  enctype="multipart/form-data"
  onsubmit="AJAXSubmit(this); return false;"
>
</form>

发送的 HTTP 请求如下。

Content-Type: multipart/form-data; boundary=---------------------------314911788813839

-----------------------------314911788813839
Content-Disposition: form-data; name="foo"

bar
-----------------------------314911788813839
Content-Disposition: form-data; name="baz"

The first line.
The second line.

-----------------------------314911788813839--

这种格式也是文件上传的格式。

66.5 文件上传

用户上传文件,也是通过表单。具体来说,就是通过文件输入框选择本地文件,提交表单的时候,浏览器就会把这个文件发送到服务器。

<input type="file" id="file" name="myFile">

此外,还需要将表单<form>元素的method属性设为POSTenctype属性设为multipart/form-data。其中,enctype属性决定了 HTTP 头信息的Content-Type字段的值,默认情况下这个字段的值是application/x-www-form-urlencoded,但是文件上传的时候要改成multipart/form-data

<form method="post" enctype="multipart/form-data">
  <div>
    <label for="file">选择一个文件</label>
    <input type="file" id="file" name="myFile" multiple>
  </div>
  <div>
    <input type="submit" id="submit" name="submit_button" value="上传" />
  </div>
</form>

上面的 HTML 代码中,file 控件的multiple属性,指定可以一次选择多个文件;如果没有这个属性,则一次只能选择一个文件。

var fileSelect = document.getElementById('file');
var files = fileSelect.files;

然后,新建一个 FormData 实例对象,模拟发送到服务器的表单数据,把选中的文件添加到这个对象上面。

var formData = new FormData();

for (var i = 0; i < files.length; i++) {
  var file = files[i];

  // 只上传图片文件
  if (!file.type.match('image.*')) {
    continue;
  }

  formData.append('photos[]', file, file.name);
}

最后,使用 Ajax 向服务器上传文件。

var xhr = new XMLHttpRequest();

xhr.open('POST', 'handler.php', true);

xhr.onload = function () {
  if (xhr.status !== 200) {
    console.log('An error occurred!');
  }
};

xhr.send(formData);

除了发送 FormData 实例,也可以直接 AJAX 发送文件。

var file = document.getElementById('test-input').files[0];
var xhr = new XMLHttpRequest();

xhr.open('POST', 'myserver/uploads');
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);

标签:URL,ArrayBuffer,JavaScript,表单,对象,实例,var,属性
来源: https://blog.csdn.net/qq_43456781/article/details/119220484

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有