事件
DOM0
级和 DOM2
级方法指定的事件处理程序都被认为是元素的方法。所以事件处理程序是在元素的作用域中运行,即程序中的 this
引用当前元素
// DOM0
var btn1 = document.getElementById('btn1');
btn1.onclick = function () {
console.log(this.id); // btn1
}
// DOM2
var btn2 = document.getElementById('btn2');
btn2.addEventListener('click', function () {
console.log(this.id); // btn2
}, false);
// 第三个参数为 true,表示捕获阶段调用事件处理程序
// 如果为 false,表示在冒泡阶段调用事件处理程序
使用 DOM2
级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。
通用的事件监听函数
/**
* 通用的事件监听函数
*
* @param {Node} elem 绑定事件的元素
* @param {String} type 绑定事件的类型
* @param {String} selector 代理的标签的字符串
* @param {Function} fn 绑定的回调函数
*/
function bindEvent(elem, type, selector, fn) {
if (fn == null) {
fn = selector;
selector = null;
}
elem.addEventListener(type, function (e) {
var target;
if (selector) {
target = e.target;
if (target.matches(selector)) {
fn.call(target, e);
}
} else {
fn(e);
}
})
}
// 使用代理
var box = document.getElementById('box');
bindEvent(box, 'click', 'a', function (e) {
alert(this.innerHTML);
})
// 不使用代理
var a1 = document.getElementById('a1');
bindEvent(a1, 'click', function (e) {
alert(e.target.innerHTML);
e.stopPropagation();
})
事件流
事件冒泡
IE
的事件流叫做事件冒泡。即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。所有现代浏览器都支持事件冒泡,并且会将事件一直冒泡到window
对象。
- 冒泡的应用:使用代理(例如给包含多个元素的容器绑定事件处理程序)
事件捕获
事件捕获的思想是不太具体的节点应该更早的接收到事件,而在最具体的节点应该最后接收到事件。事件捕获的用以在于事件到达预定目标之前捕获它。
DOM 事件流
“DOM2级事件”规定事件流包括三个阶段,事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的事件捕获,为截获事件提供了机会。然后是实际的目标接收了事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。
- 阻止冒泡:
stopPropagation()
- 取消事件的默认行为:
preventDefault()
一个有趣的例子
原文链接:你真的理解事件冒泡和事件捕获吗?
<div id="a">
<div id="b">
<div id="c"></div>
</div>
</div>
#a{
width: 300px;
height: 300px;
background: pink;
}
#b{
width: 200px;
height: 200px;
background: blue;
}
#c{
width: 100px;
height: 100px;
background: yellow;
}
var a = document.getElementById("a"),
b = document.getElementById("b"),
c = document.getElementById("c");
c.addEventListener("click", function (event) {
console.log("c1");
// 注意第三个参数没有传进 false , 因为默认传进来的是 false
//,代表冒泡阶段调用,个人认为处于目标阶段也会调用的
});
c.addEventListener("click", function (event) {
console.log("c2");
}, true);
b.addEventListener("click", function (event) {
console.log("b");
}, true);
a.addEventListener("click", function (event) {
console.log("a1");
}, true);
a.addEventListener("click", function (event) {
console.log("a2")
});
a.addEventListener("click", function (event) {
console.log("a3");
event.stopImmediatePropagation();
}, true);
a.addEventListener("click", function (event) {
console.log("a4");
}, true);
那么现在有三个问题:
-
如果点击c或者b,输出什么?(答案是
a1、a3
)stopImmediatePropagation
包含了stopPropagation
的功能,即阻止事件传播(捕获或冒泡),但同时也阻止该元素上后来绑定的事件处理程序被调用,所以不输出a4
。因为事件捕获被拦截了,自然不会触发b、c
上的事件,所以不输出b、c1、c2
,冒泡更谈不上了,所以不输出a2
。 -
如果点击a,输出什么?(答案是
a1、a2、a3
)虽然这三个事件处理程序注册时指定了
true、false
,但现在事件流是处于目标阶段,不是冒泡阶段、也不是捕获阶段,事件处理程序被调用的顺序是注册的顺序。不论你指定的是true
还是false
。换句话来说就是现在点击的是a
这个盒子本身,它处于事件流的目标状态,而既非冒泡,又非捕获。(需要注意的是,此时的eventPhase
为2,说明事件流处于目标阶段。当点击a
的时候,先从document
捕获,然后一步步往下找,找到a
这个元素的时候,此时的target
和currentTarget
是一致的,所以认定到底了,不需要再捕获了,此时就按顺序执行已经预定的事件处理函数,执行完毕后再继续往上冒泡…) -
如果注释掉
event.stopImmediatePropagation
,点击c,会输出什么?(答案是a1、a3、a4、b、c1、c2、a2
)如果同一个事件处理程序(指针相同,比如用
handler
保存的事件处理程序),用addEventListener
或attachEvent
绑定多次,如果第三个参数是相同的话,也只会被调用一次。当然,如果第三个参数一个设置为true
,另一个设置为false
,那么会被调用两次。
Ajax
是 Asynchronous Javascript And XML
的缩写,即异步 JavaScript 和 XML。
不考虑async等语法的前提下:
- 异步本来就没返回值(或者说返回值不是想要的结果)。但异步操作一般都是和回调函数绑定使用的,所有依赖返回结果的方法都要在回调函数中进行。
- 监听对象属性,本身也是个异步操作,只不过是将原本函数回调中的一些职责转移到了监听的回调里,因为你本来就可以在原本函数的回调中进行这些依赖返回值得函数的调用。
- 同步ajax请求用的比较少。例如在做访问记录时,页面关闭前确保请求发送成功。
// 基础版
var xhr = new XMLHttpRequest();
xhr.open('get', '/api', false);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
}
}
}
xhr.send(null);
/**
* 封装一个get请求的ajax操作
*
* @param {String} url 请求的地址
* @returns Promise
*/
function getJSON(url) {
return new Promise((resolve, reject) => {
var XHR = new XMLHttpRequest();
XHR.open('get', url, true);
XHR.send();
XHR.onreadystatechange = function () {
if (XHR.readyState === 4) {
if (XHR.status === 200) {
// 成功
try {
var result = JSON.parse(XHR.responseText);
resolve(result);
} catch (e) {
reject(e);
}
} else {
// 失败
reject(new Error(XHR.statusText));
}
}
}
})
}
// 使用
var url = './api.json';
getJSON(url).then((value) => {
console.log(value);
}).catch((err) => {
console.log(err);
})
readyState
readyState
是XHR对象的一个属性,该属性表示请求/响应过程的当前活动阶段。该属性的取值如下:
- 0:未初始化 尚未调用open()方法
- 1:启动 已经调用open()方法,但尚未调用send()方法
- 2:发送 已经调用send()方法,但尚未接收到响应
- 3:接收 已经接收到部分响应数据
- 4:完成 已经接收到全部响应数据,而且已经可以在客户端上使用了
只要readyState属性的值由一个值变成另一个值,都会触发一次readystatechange事件。
跨域
跨域是指一个域下的文档或者脚本视图去请求另一个域下的资源
-
浏览器有同源策略,不允许ajax访问其他域的接口。
- 同源策略限制一下的几种行为
- Cookie,LocalStorage,IndexDB无法获取
- DOM 和 JS 对象无法获得
- AJAX请求不能发送
- 可以跨域的三个标签
<img src=xxx>
<link href=xxx>
<script src=xxx>
- 使用场景
<img>
用于打点统计,统计网站可能是其他域<link> <script>
可以使用CDN,CDN也可能是其他域<script>
可以用于JSONP
- 跨域注意事项
- 所有的跨域请求都必须经过信息提供方允许
- 如果未经允许即可获取,那是浏览器同源策略出现漏洞
- 同源策略限制一下的几种行为
-
跨域条件:协议、域名、端口(有一个不同就算是跨域)
- http的默认端口为80,https的默认端口为443
跨域常用的解决方案
-
通过JSONP跨域(只能实现get请求):利用
<script>
可以跨域的特性// demo-1 <script> window.callback = function (data) { // 这是跨域得到的信息 console.log(data) } </script> <script src="http://imooc.com/api.js"></script> <!-- 以上将返回callback({x:100, y:200}) --> // demo-2 <script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参并指定回调执行函数为onBack script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回调执行函数 function onBack(res) { alert(JSON.stringify(res)); } </script> onBack({"status": true, "user": "admin"})
-
跨域资源共享(CORS —— Cross-Origin Resource Sharing)
- 前端需要设置请求头信息
- 设置
Access-Control-Request-Method
:必写,用来列出浏览器CORS请求可以使用的HTTP方法 - 设置
Access-Control-Request-Headers
:用来指定浏览器可额外发送的请求头
- 设置
- 后端需要设置 http header
- 设置
Access-Control-Allow-Origin
:必写,请求域的domin或者(*) ,*代表着允许所有域名的跨域请求 - 设置
Access-Control-Allow-Headers
:在CORS请求时浏览器的XMLHttpRequest
对象的getResponseHeader()
方法只可以拿到六个基本字段,如果还想拿到其他字段就必须在Access-Control-Expose-Header
中指定。 - 设置
Access-Control-Allow-Credentials
:可写,该值是一个布尔值,表示是否允许发送Cookie
,默认情况下,Cookie
不包括CORS
请求中。
- 设置
- 前端需要设置请求头信息
-
nginx反向代理
-
nodejs中间件代理跨域(vue开发环境下的proxy就是使用这种方式)
-
websocket协议跨域
存储
cookie
- 本身用于客户端和服务端通信,但是它有本地存储的功能,所以就被“借用”。
- 使用
document.cookie = ...
获取和修改 - cookie用于存储的缺点
- 存储量太小。只有4KB
- 所有http请求都带着,会影响获取资源的效率
- API简单,需要封装才能用
localStorage 和 sessionStorage
-
HTML5专门为存储而设计的,最大容量为5M
-
http请求不会携带
-
API简单易用
localStorage.setItem(key, value); localStorage.getItem(key); sessionStorage.setItem(key, value); sessionStorage.getItem(key);
-
使用的一些坑
- IOS safari 隐藏模式下,使用
localStorage.getItem(key)
会报错 - 建议统一使用
try-catch
封装
- IOS safari 隐藏模式下,使用
cookie和sessionStorage,localStorage的区别
- 容量
- cookie只有4KB
- sessionStorage,localStorage最大容量为5M
- 是否会携带到http请求中
- 所有http请求都会携带cookie
- http请求不会携带sessionStorage,localStorage
- API易用性
- cookie的过于简单,需要进行封装才可以使用
- sessionStorage,localStorage的API简单易用,无需封装
1. localStorage 是否可以代替 Cookie —— 为了防止通过 XSS
获取 Cookie
数据,浏览器支持了使用 HttpOnly
来保护 Cookie
不被 XSS
攻击获取到。而 localStorage
存储没有对 XSS
攻击有任何的抵御机制。一旦出现 XSS
漏洞,那么存储在 localStorage
里的数据就极易被获取到。
2. 容易遭受跨目录攻击 —— localStorage
存储方式不会像 Cookie
存储一样可以指定域中的路径,在 localStorage
存储方式中没有域路径的概念。也就是说,如果一个域下的任意路径存在 XSS
漏洞,整个域下存储的数据,在知道存储名称的情况下,都可以被获取到。
3. 容易遭受DNS欺骗攻击