Skip to content

10-2-同源策略

第十章 BOM

统计信息:字数 29680 阅读60分钟

2021-11-24 2023-04-11

10.7 同源策略

Same-origin 介绍

起源:不同源的网页不能打开cookie。

同源:协议域名端口都相同。

目的:保证用户数据的安全,防止恶意网站窃取用户数据。

现在:无法获取非同源网页的cookie,localStorage,IndexedDB;DOM;无法发送AJAX请求。

非同源网页可以调用下面的属性和方法。

window.closed
window.frames
window.length
window.location
window.opener
window.parent
window.self
window.top
window.window
window.blur()
window.close()
window.focus()
window.postMessage()

两个网页的一级域名相同(xxx.com)次级域名不同(docs.demo)可以设置 document.domain 相同来共享cookie,设置是会重置端口为null。两个页面需要同时设置 document.domain = 'xxx.com'; 才能达到同源的目的。设置后两个界面可以通过 document.cookie 获取相同的信息。

iframe多窗口通信

一个界面中嵌入多个窗口,一个窗口可以获取父窗口和子窗口的window,如果不同源,不能操作DOM(document.getElementByID('myIframe')..contentWindow.document 会报错)。

如果两个窗口的一级域名相同,次级域名不同,可以类似cookie 设置 document.domain。

如果l两个窗口完全不同源,需要使用下面的两个方案(开发中未使用):

片段识别符

片段标识符(fragment identifier)指的是,URL 的#号后面的部分,比如http://example.com/x.html#fragment#fragment。如果只是改变片段标识符,页面不会重新刷新。

父窗口可以把信息,写入子窗口的片段标识符。

var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;

上面代码中,父窗口把所要传递的信息,写入 iframe 窗口的片段标识符。

子窗口通过监听hashchange事件得到通知。

window.onhashchange = checkMessage;

function checkMessage() {
  var message = window.location.hash;
  // ...
}

同样的,子窗口也可以改变父窗口的片段标识符。

parent.location.href = target + '#' + hash;

window.postMessage()

上面的这种方法属于破解,HTML5 为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。

这个 API 为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。举例来说,父窗口aaa.com向子窗口bbb.com发消息,调用postMessage方法就可以了。

// 父窗口打开一个子窗口
var popup = window.open('http://bbb.com', 'title');
// 父窗口向子窗口发消息
popup.postMessage('Hello World!', 'http://bbb.com');

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即“协议 + 域名 + 端口”。也可以设为*,表示不限制域名,向所有窗口发送。

子窗口向父窗口发送消息的写法类似。

// 子窗口向父窗口发消息
window.opener.postMessage('Nice to see you', 'http://aaa.com');

父窗口和子窗口都可以通过message事件,监听对方的消息。

// 父窗口和子窗口都可以用下面的代码,
// 监听 message 消息
window.addEventListener('message', function (e) {
  console.log(e.data);
},false);

message事件的参数是事件对象event,提供以下三个属性。

  • event.source:发送消息的窗口
  • event.origin: 消息发向的网址
  • event.data: 消息内容

下面的例子是,子窗口通过event.source属性引用父窗口,然后发送消息。

window.addEventListener('message', receiveMessage);

function receiveMessage(event) {
  event.source.postMessage('Nice to see you!', '*');
}

上面代码有几个地方需要注意。首先,receiveMessage函数里面没有过滤信息的来源,任意网址发来的信息都会被处理。其次,postMessage方法中指定的目标窗口的网址是一个星号,表示该信息可以向任意网址发送。通常来说,这两种做法是不推荐的,因为不够安全,可能会被恶意利用。

event.origin属性可以过滤不是发给本窗口的消息。

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  if (event.origin !== 'http://aaa.com') return;
  if (event.data === 'Hello World') {
    event.source.postMessage('Hello', event.origin);
  } else {
    console.log(event.data);
  }
}

LocalStorage

通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。

下面是一个例子,主窗口写入 iframe 子窗口的localStorage

window.onmessage = function(e) {
  if (e.origin !== 'http://bbb.com') {
    return;
  }
  var payload = JSON.parse(e.data);
  localStorage.setItem(payload.key, JSON.stringify(payload.data));
};

上面代码中,子窗口将父窗口发来的消息,写入自己的 LocalStorage。

父窗口发送消息的代码如下。

var win = document.getElementsByTagName('iframe')[0].contentWindow;
var obj = { name: 'Jack' };
win.postMessage(
  JSON.stringify({key: 'storage', data: obj}),
  'http://bbb.com'
);

加强版的子窗口接收消息的代码如下。

window.onmessage = function(e) {
  if (e.origin !== 'http://bbb.com') return;
  var payload = JSON.parse(e.data);
  switch (payload.method) {
    case 'set':
      localStorage.setItem(payload.key, JSON.stringify(payload.data));
      break;
    case 'get':
      var parent = window.parent;
      var data = localStorage.getItem(payload.key);
      parent.postMessage(data, 'http://aaa.com');
      break;
    case 'remove':
      localStorage.removeItem(payload.key);
      break;
  }
};

加强版的父窗口发送消息代码如下。

var win = document.getElementsByTagName('iframe')[0].contentWindow;
var obj = { name: 'Jack' };
// 存入对象
win.postMessage(
  JSON.stringify({key: 'storage', method: 'set', data: obj}),
  'http://bbb.com'
);
// 读取对象
win.postMessage(
  JSON.stringify({key: 'storage', method: "get"}),
  "*"
);
window.onmessage = function(e) {
  if (e.origin != 'http://aaa.com') return;
  console.log(JSON.parse(e.data).name);
};

Ajax 处理跨域

服务器端处理:架设代理服务器:浏览器访问同源服务器,服务器请求外部服务。还有三种方法:JSONP,websocket, CORS。

1、JSONP

JSONP 简单易用,没有兼容性问题,老式浏览器全部支持,服务端改造非常小。

它的做法如下。

第一步,网页添加一个<script>元素,向服务器请求一个脚本,这不受同源政策限制,可以跨域请求。

<script src="http://api.foo.com?callback=bar"></script>

注意,请求的脚本网址有一个callback参数(?callback=bar),用来告诉服务器,客户端的回调函数名称(bar)。

第二步,服务器收到请求后,拼接一个字符串,将 JSON 数据放在函数名里面,作为字符串返回(bar({...}))。

第三步,客户端会将服务器返回的字符串,作为代码解析,因为浏览器认为,这是<script>标签请求的脚本内容。这时,客户端只要定义了bar()函数,就能在该函数体内,拿到服务器返回的 JSON 数据。

下面看一个实例。首先,网页动态插入<script>元素,由它向跨域网址发出请求。

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于 JSONP 是必需的。

服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。

foo({
  'ip': '8.8.8.8'
});

由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此避免了使用JSON.parse的步骤。

2、WebSocket

WebSocket 是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

下面是一个例子,浏览器发出的 WebSocket 请求的头信息。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

Origin: http://example.com

上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

正是因为有了Origin这个字段,所以 WebSocket 才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
3、CORS

10.8 CORS

CORS概述

CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写,它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发GET请求,CORS 允许任何类型的请求。JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。

兼容:CORS 需要浏览器和服务器同时支持,目前浏览器都支持。对于前端开发,CORS和AJAX差不多,如果发出的请求是跨域的,那么在请求头更改信息,用户体验良好。后端开发需要在服务器实现CORS API。

分类:简单请求和非简单请求

简单请求:请求方法(get-post-head)和请求头信息简单,请求头只能是下面的属性。简单请求类似于表单请求,浏览器允许表单请求跨域。

Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

简单请求

浏览器发现请求跨域,需要在请求头中加入 Origin: https://demo.xxx.com 字段,包括当前请求的协议域名端口号;服务器判断这个origin在许可的范围内,在相应头中添加 Access-Control-Allow-Origin 字段,实现跨域;如果这个origin不在许可范围内,就返回正常的HTTP回应,不包含 Access-Control-Allow-Origin 字段,此时前端xhr.onerror 会捕获到这个错误。下面是正确的字符说明

// 服务器的响应头
Access-Control-Allow-Origin: http://api.bob.com // 必选参数,返回值是origin的参数,或者是*,表示接受任意域名的请求
Access-Control-Allow-Credentials: true // 可选参数:这个值表示是否发送cookie,只能设置为true,不设置就表示false(默认浏览器cookie不包括在CORS请求中)
Access-Control-Expose-Headers: FooBar // 可选参数:JS脚本默认可以获取部分响应头信息,这里加上这个属性,表示可以获取这个信息:getResponseHeader('FooBar') 
Content-Type: text/html; charset=utf-8

默认CORS请求不包含 cookie,为了避免CSRF风险。如果需要发送cookie,前端需要设置 xhr.withCredentials = true; 服务器会返回 Access-Control-Allow-Credentials: true,实现跨域。

如果服务器要求浏览器发送 Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。

非简单请求

非简单请求:请求的方法特殊(PUT DELETE)请求头的字段特殊(Content-Type: 'application/json')

第一步 预检请求(preflight)

在通信开始前,发送一个预检请求,浏览器询问服务器当前网页所在域名是否在服务器的许可列表中,哪些请求方法和请求头字段是允许的。服务器响应同意后,浏览器可以继续发送请求。如果不同意就不能发送;这样避免了服务器收到大量跨域的PUT delete请求。下面是一个预检请求的请求头:

OPTIONS /cors HTTP/1.1 预检请求的方法是 OPTIONS
Origin: http://api.bob.com 源是网页的网址
Access-Control-Request-Method: PUT 这是可以接受的请求方法
Access-Control-Request-Headers: X-Custom-Header 这里是自定义的请求头字段
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

第二步 服务器响应

服务器收到预检请求,允许跨域,做出回应,响应头如下

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com 这里表示当前网页可以跨域
Access-Control-Allow-Methods: GET, POST, PUT 这里是可以跨域的方法
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

如果不允许跨域,onerror 会捕获错误

XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

第三步 浏览器正常的请求

浏览器自动添加Origin字段,发送正常的请求

10.9 Storage

Storage API 的实例对象有两个:window.localStorage and window.sessionStorage。他们的区别是,关闭当前窗口(对话)后存储是否继续保留。保存的数据以键值对保存,实际上是一个字符串(数值参数会转化成字符串存储)。每个域名的存储不同,最小的chrome是2.5MB,也受同域限制,跨域操作会报错。

属性和方法

1window.localStorage.length

2window.localStorage.setItem('key', 'value'); // 这里设置的key-value都是字符串,其他数据类型会强制转换成字符串,方法没有返回值。如果存储满了,这个方法会报错。或者直接改变属性也可以。如果已有这个key, 重新设置会覆盖原始值。
window.localStorage['key'] = value;

3window.sessionStorage.getItem('foo') 如果不存在就返回 null

4window.sessionStorage.removeItem('foo') 如果key不存在方法不会报错
5window.sessionStorage.clear(); // return undefined
6window.sessionStorage.key 返回一个key的伪数组结合length可以遍历

存储事件:当存储内容变化时,会触发storage事件。这个事件很特殊:如果一个窗口存储变化,那么当前窗口不会触发,同一个域名的其他窗口会触发这个事件(可以使用这个属性进行不同窗口的通信)

window.addEventListener('storage', onChange);

onChange = (e) => {
  console.log(e.key); 表示变动的键名如果是clear事件返回null
  e.newValue
  e.oldValue
  e.url
  e.storageArea
}

localStorage 的可以参考自己总结的博客。

https://blog.csdn.net/weixin_41697143/article/details/98479748

10.9 History

浏览器的历史对象。JS脚本不允许获取历史地址,但是可以跳转导航。对历史对象的操作,和浏览器的前进后退按钮相同。

属性:history.length 当前历史的条数;history.state 历史堆栈顶层的属性(默认undefined)

方法:

  • history.back(); history.forward(); 这里会从缓存中加载页面。
  • history.go(0) 默认是0,表示刷新界面;使用加减1表示网页前进后退。
  • History.pushState(state, title, url) state 表示状态对象,当popstate事件触发后,这个对象会传入回调函数。title 表示新页面标题,url 表示新的网址。这个方法会把当前的地址改成URL,history中会增加一个历史信息,但是界面不会跳转(不会刷新)这个网址不能跨域,否则报错。
  • History.replaceState(state, title, url) 替换当前的历史记录为新的URL(原始历史被替换)

事件:popstate

当history对象变化时会触发这个事件:pushState() and replaceState() 不会触发这个事件,其他三个事件或者点击前进后退按钮就会触发这个事件。可以给这个事件指定回调函数。注意,页面第一次加载的时候,浏览器不会触发popstate事件。

10.10 Location, URL, URLSearchParems 对象

Location

Location 对象可以操作 URL,使用 window.location or document.location 可以获取Location对象。下面是常用的属性:

location.href 整个URL (如果更改,浏览器会立刻跳转到新地址,即使URL与原始相同,界面仍然会刷新)
location.origin 协议、主机名、端口 http://www.baidu.com:8080(这是只读属性)
location.protocol 协议(包括冒号) http:
location.host 主机名和端口号 www.baidu.com:8080(默认80and443端口会省略)
hostname 主机名
port 端口号

pathname 路径:从根路径开始(包括根路径)
search 查询部分:问号开始(不包括问号)
hash 哈希部分:井号开始(不包括#)
username
password 域名前面的用户名和密码 测试是undefined

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

方法:

  • location.assign(newURL) 浏览器立刻跳转到新的URL,如果不是URL会报错
  • location.replace(newURL) 直接跳转到新的网页,当前的历史不会保存(可以判断是否是移动端,然后界面进行跳转);
  • location.reload(true) 重载当前窗口(刷新界面)如果传入true,表示想服务器重新请求,请求新界面不滚动;如果是false或者默认不传值,直接从缓存中加载,界面停留在当前滚动的位置。
  • location.toString() 等价于 let a = location.href;

URL

1、URL的编码和解码

URL中的汉字和特殊字符需要转码成为UTF-8编码(例如%82)请求中路径文件名需要转码

  • encodeURI('http://www.example.com/q=春节') (注意是URI,不是URL)传入一个字符串,会将元字符(冒号斜杠)和语义字符之外的字符,都进行转义,这里可以传入一个完整的URL
  • encodeURIComponent(filepath + name)会转码除了语义字符之外的所有字符,即元字符也会被转码。如果斜杠和冒号被转义就会出错。
  • decodeURI
  • decodeURIComponent 是上面两个方法的逆运算
2、URL对象

URL对象是一个原生对象,a标签继承了URL对象的属性和方法(href)

var url = new URL('http://www.example.com/index.html');
console.log(url.href);
如果参数是另一个 URL 实例构造函数会自动读取该实例的href属性作为实际参数如果 URL 字符串是一个相对路径那么需要表示绝对路径的第二个参数作为计算基准
var url2 = new URL('page2.html', 'http://example.com/page1.html');
// "http://example.com/page2.html"

属性:URL实例对象的属性和Location的属性基本一致(origin只读,其他都可以修改)

方法

  • URL.createObjectURL()为上传/下载的文件、流媒体文件生成一个 URL 字符串。这个字符串代表了File对象或Blob对象的 URL。
  • URL.revokeObjectURL()用来释放URL.createObjectURL方法生成的 URL 实例。它的参数就是URL.createObjectURL方法返回的 URL 字符串。
3、URLSearchParams

URLSearchParams 用来构造、解析、处理 URL 查询部分(?后面的部分)参数可以使字符串对象数组,会对查询字符串自动编码(encode)。

// 方法一:传入字符串
var params = new URLSearchParams('?foo=1&bar=2');
// 方法二:传入数组
var params = new URLSearchParams([['foo', 1], ['bar', 2]]);
// 方法三:传入对象
var params = new URLSearchParams({'foo' : 1 , 'bar' : 2});

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

fetch('https://example.com/api', {
  method: 'POST',
  body: params
}).then(...)

var params = new URLSearchParams({version: 2.0});
window.location.href = location.pathname + '?' + params;
  • URLSearchParams.toString() 把实例对象转化成字符串
  • URLSearchParams.append('baz', 3) 追加一个查询参数。第一个为键名,第二个为键值,没有返回值。
  • URLSearchParams.delete() 删除一个查询参数。
  • URLSearchParams.has() 查询一个参数 返回布尔值
  • URLSearchParams.set('foo', 5) 设置一个查询参数;如果没有这个参数就增加
  • URLSearchParams.get() 获取某个value
  • URLSearchParams.getAll() 返回全部的value数组
  • URLSearchParams.sort() 按照Unicode进行排序
  • URLSearchParams.keys()、URLSearchParams.values()、URLSearchParams.entries() 返回一个遍历器对象,让for...of... 循环遍历。keys方法返回的是键名的遍历器,values方法返回的是键值的遍历器,entries返回的是键值对的遍历器。

10.11 ArrayBuffer, Blob

ArrayBuffer 对象

可以获取操作二进制数据(操作内存),主要在ES6中介绍。

let buffer = new ArrayBuffer(8); // 构造函数创建实例对象,表示这段二进制数据占用多少个字节
buffer.byteLength; 当前对象占用的内存长度
let buffer2 = buffer.slice(0,4); 复制一部分内存

Blob 对象

表示二进制文件的数据内容(图片)Binary Large Object 二进制大型对象,可以用来操作二进制文件

构造函数
let blob = newBlob([array], options);
// 第一个参数是二进制数组,数组成员是二进制数据或者字符串,第二个可选参数是配置,type,表示数据类型

let htmlFragment = ['<span>Michael</span>'];
let blob = new Blob(htmlFragemtn, {
  type: 'text/html'
});

let obj = { foo: 'foo' };
let blob2 = new Blob([JSON.stringify(obj)], {
  type: 'application/json'
});

属性和方法
size 返回二进制文件的长度
type 返回二进制文件的类型 
blob.slice(start, end, type) 截取一部分二进制文件可以设置类型

下面使用Blob读取上传的文件

获取文件

input type=file 的上传组件,浏览器不允许脚本直接控制value,必须用户上传。用户选好文件后,即可读取文件。文件输入input和拖拽对象dataTransfer 返回的是一个类数组,FileList,每一个item是一个File对象(File对象是Blob构造函数的特殊实例,具有 name,lastModifiedData 的属性)

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

function fileinfo(files) {
  for (let i = 0; i < files.length; i++) {
    console.log(file);
  }
}
下载文件

在AJAX请求中,如果设置responseType = blob 下载的结果就是Blob对象

let xhr = new XMLHttpRequest();
xhr.open('GET', null);
xhr.responseType = 'blob';
xhr.onload = () => {
  console.log(xhr.response);
}
xhr.send(null);
生成URL

浏览器允许使用URL.createObjectURL()方法,针对 Blob 对象生成一个临时 URL,以便于某些 API 使用。

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

取得 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' onchange='readfile(this.files[0])'></input>
// <pre id='output'></pre>

function readfile(file) {
  var reader = new FileReader();
  reader.readAsText(file);
  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" onchange="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);
  };
}  

10.12 File, FileList, FileReader

File 对象:File对象是一个特殊的Blob实例,文件上传后,即可从input元素中拿到这个对象

let file = document.getElementById('file-update').files[0];

构造函数
let file = new File(array, name, options);
array 是文件数组可以使arraybuffer或者stringname是文件路径或者文件名options是配置文件type="application/json", lastModified: 时间戳

属性和方法
file.name
file.size
file.type
file.lastModified
file.slice() 这是继承自Blob对象的方法

FileList 伪数组,每一项是一个File对象:文件输入选择或者拖拽元素节点返回的就是FileList对象。FileList.length 返回的长度表示包含文件的个数。

FileReader 对象用于读取文件对象的内容。

let reader = new FileReader();

属性和方法
reader.error
reader.readyState(表示加载的过程0-1-2)未加载-正在加载-加载完成
reader.result 读取完成后的文件内容可能是字符串或者arraybuffer实例

reader.onabort
reader.onerror
reader.onload
reader.onloadstart
reader.onloadend
reader.onprogress

eg

// HTML 代码如下
// <input type="file" onchange="onChange(event)">
function onChange = (e) => {
  let file = e.target.files[0];
  let reader = new FileReader();
  reader.readAsText(file);
  reader.onload = (event) => {
    console.log(event.target.result);
  };
}

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" onchange="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属性,从而把图片展示出来。


Last update: November 9, 2024