周元-浏览器相关
# 周元-浏览器相关
# 浏览器事件模型 & 请求
# 事件模型
# DOM事件
DOM(Document Object Model,文档对象模型)是针对HTML文档和XML文档的一个API。
- DOM0: IE 和 Netscape
- DOM1: 1998年,W3C制定规范
- DOM0级事件
事件就是用户或浏览器自身执行的某种操作,如click、load、mouseover等,都是事件的名字,而响应某个事件的函数就被称为事件处理程序。
btn.onclick = function(e){
console.log('this is a click event', e)
// 在 IE 中,在使用 DOM0 级方法添加事件处理程序时,event 是作 window 对象的一个属性而存在的。此时访问事件对象需要通过 window.event。
console.log(window.event);
}
- DOM1级事件
DOM级别1于1998年10月1日成为W3C推荐标准。1级DOM标准中并没有定义事件相关的内容,所以没有所谓的1级DOM事件模型。
- DOM2级事件
W3C 后来将 DOM1 升级为 DOM2,DOM2级规范开始尝试以一种符合逻辑的方式来标准化 DOM事件。DOM0级 可以认为 onclick 是 btn 的一个属性,DOM2级 则将属性升级为队列。
DOM2级 事件定义了两个方法,用于处理指定和删除事件处理程序的操作:
addEventListener()和removeEventListener()
所有的 DOM 节点中都包含这两个方法,它们都接收 3 个参数。
- 事件名;
- 事件处理程序的函数;
- 布尔值,true 代表在捕获阶段调用事件处理程序,false 表示在冒泡阶段调用事件处理程序,默认为 false;
function fn1(){
// do something
}
function fn1(){
// do something else
}
btn.addEventListener('click',fn1)
btn.addEventListener('click',fn2)
click 队列中依次
fn1.call(btn,event),fn2.call(btn,event)。
通过addEventListener()添加的事件只能由removeEventListener()来移除,并且removeEventListener()只能移除具名函数,不能移除匿名函数。
- IE 中 DOM2级事件
attachEvent()和detachEvent()
- 要处理的事件名;
- 作为事件处理程序的函数;
- 兼容处理:
if(typeof btn.addEventListener === 'function'){
btn.addEventListener('click',fn);
}else if(typeof btn.attachEvent === 'function'){
btn.attachEvent('onclick',fn)
}else{
btn.onclick=function(){
// do something
}
}
- 总结:
- DOM2级的好处是可以添加多个事件处理程序;DOM0对每个事件只支持一个事件处理程序;
- 通过DOM2添加的匿名函数无法移除,上面写的例子就移除不了,addEventListener和removeEventListener的handler必须同名;
- 作用域:DOM0的handler会在所属元素的作用域内运行,IE的handler会在全局作用域运行,
this === window; - 触发顺序:添加多个事件时,DOM2会按照添加顺序执行,IE会以相反的顺序执行;
- 跨浏览器的事件处理程序:
var EventUtil = {
// element是当前元素,可以通过getElementById(id)获取
// type 是事件类型,一般是click ,也有可能是鼠标、焦点、滚轮事件等等
// handle 事件处理函数
addHandler: (element, type, handler) => {
// 先检测是否存在DOM2级方法,再检测IE的方法,最后是DOM0级方法(一般不会到这)
if (element.addEventListener) {
// 第三个参数false表示冒泡阶段
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent(`on${type}`, handler)
} else {
element[`on${type}`] = handler;
}
},
removeHandler: (element, type, handler) => {
if (element.removeEventListener) {
// 第三个参数false表示冒泡阶段
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent(`on${type}`, handler)
} else {
element[`on${type}`] = null;
}
}
}
// 获取元素
var btn = document.getElementById('btn');
// 定义handler
var handler = function(e) {
console.log('我被点击了');
}
// 监听事件
EventUtil.addHandler(btn, 'click', handler);
// 移除事件监听
// EventUtil.removeHandler(button1, 'click', clickEvent);
# 事件捕获&冒泡
IE 的事件流是事件冒泡流;而 Netscape Communicator 的事件流是事件捕获流。
DOM2级事件规定的事件流包括三个阶段:
- 事件捕获
- 执行事件
- 事件冒泡
事件执行流程:
document > html > body > div
stopPropagation(): 事件传播时,事件不会被传播给下一个节点;
立即停止事件在DOM层次中的传播,包括捕获和冒泡事件;IE 不支持事件捕获,因而只能取消事件冒泡
stopImmediatePropagation(): 事件传播时,后面同一层级的listener不继续执行preventDefault(): 阻止默认行为,跟JS的事件传播没关系;
比如链接被点击会导航到其href指定的URL,这个就是默认行为
例:
<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
点击<div>元素会以下列顺序触发 click 事件:
事件捕获:
document><html>><body>><div>事件冒泡:
<div>><body>><html>>documentEventUtil
var EventUtil = {
// element是当前元素,可以通过getElementById(id)获取
// type 是事件类型,一般是click ,也有可能是鼠标、焦点、滚轮事件等等
// handle 事件处理函数
addHandler: (element, type, handler) => {
// 先检测是否存在DOM2级方法,再检测IE的方法,最后是DOM0级方法(一般不会到这)
if (element.addEventListener) {
// 第三个参数false表示冒泡阶段
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent(`on${type}`, handler)
} else {
element[`on${type}`] = handler;
}
},
removeHandler: (element, type, handler) => {
if (element.removeEventListener) {
// 第三个参数false表示冒泡阶段
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent(`on${type}`, handler)
} else {
element[`on${type}`] = null;
}
},
// 获取event对象
getEvent: (event) => {
return event ? event : window.event
},
// 获取当前目标
getTarget: (event) => {
return event.target ? event.target : event.srcElement
},
// 阻止默认行为
preventDefault: (event) => {
if (event.preventDefault) {
event.preventDefault()
} else {
event.returnValue = false
}
},
// 停止传播事件
stopPropagation: (event) => {
if (event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
}
}
# 事件委托
使用事件委托,只需在 DOM 树中尽量最高的层次上添加一个事件处理程序,用来解决事件处理程序过多的问题。
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
<script>
var list = document.getElementById("myLinks");
EventUtil.addHandler(list, "click", function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id) {
case "doSomething":
document.title = "I changed the document's title";
break;
case "goSomewhere":
location.href = "http://www.wrox.com";
break;
case "sayHi": 9 alert("hi");
break;
}
}
</script>
# ajax
Asynchronous JavaScript And XML,翻译过来就是“异步的 Javascript 和 XML”。
Ajax 是一种思想,XMLHttpRequest 只是实现 Ajax 的一种方式。其中 XMLHttpRequest 模块就是实现 Ajax 的一种很好的方式。
let xmlHttp;
if (window.XMLHttpRequest) {
// code for IE7+, Firefox, Chrome, Opera, Safari
xmlHttp = new XMLHttpRequest();
} else {
// code for IE6, IE5
xmlHttp = new ActiveXObject('Microsoft.XMLHTTP');
}
通过XMLHttpRequest构造函数创建一个异步对象xmlhttp, IE6, IE5 使用ActiveXObject创建。
- 手写ajax:
const ajax = option => {
//type, url, data, timeout, success, error将所有参数换成一个对象{}
// 0.将对象转换成字符串
//处理obj
const objToString = data => {
data.t = new Date().getTime();
let res = [];
for (let key in data) {
//需要将key和value转成非中文的形式,因为url不能有中文。使用encodeURIComponent();
res.push(encodeURIComponent(key) + ' = ' + encodeURIComponent(data[key]));
}
return res.join('&');
};
let str = objToString(option.data || {});
// 1.创建一个异步对象xmlHttp;
var xmlHttp, timer;
if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();
} else if (xmlHttp) {
// code for IE6, IE5
xmlHttp = new ActiveXObject('Microsoft.xmlHttp');
}
// 2.设置请求方式和请求地址;
// 判断请求的类型是POST还是GET
if (option.type.toLowerCase() === 'get') {
// 设置请求方法是不是异步async,true为异步, false为同步
xmlHttp.open(option.type, option.url + '?t=' + str, true);
// 3.发送请求;
xmlHttp.send();
} else {
xmlHttp.open(option.type, option.url, true);
// 注意:在post请求中,必须在open和send之间添加HTTP请求头:setRequestHeader(header,value);
xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
// 3.发送请求;
xmlHttp.send(str);
}
// 4.监听状态的变化;
xmlHttp.onreadystatechange = function () {
clearInterval(timer);
if (xmlHttp.readyState === 4) {
// 表示请求成功
if ((xmlHttp.status >= 200 && xmlHttp.status < 300) || xmlHttp.status == 304) {
// 5.处理返回的结果;
option.success(xmlHttp.responseText); //成功后回调;
} else {
option.error(xmlHttp.responseText); //失败后回调;
}
}
};
//判断外界是否传入了超时时间
if (option.timeout) {
timer = setInterval(function () {
xmlHttp.abort(); //中断请求
clearInterval(timer);
}, option.timeout);
}
};
// 可选择json-server启动本地服务
// 调用
ajax({
type: 'GET',
url: 'http://localhost:3000/posts',
timeout: 1000,
success: data => {
console.log('success', data);
},
error: err => {
console.log('error', err);
},
});
# fetch
Fetch 是在 ES6 出现的,它使用了 ES6 提出的 Promise 对象。它是 XMLHttpRequest 的替代品。
Fetch 是一个 API,它是真实存在的,它是基于 Promise 的。
- XMLHttpRequest => fetch
fetch()的功能与 XMLHttpRequest 基本相同,但有三个差异:
- fetch使用 Promise,不使用回调函数,写法更简洁;
- fetch采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理一些;
- fetch通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用;
fetch('https://api.github.com/users/ruanyf')
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log('Request Failed', err));
// 用await 改写
async function getJSON() {
let url = 'https://api.github.com/users/ruanyf';
try {
let response = await fetch(url);
return await response.json();
} catch (error) {
console.log('Request Failed', error);
}
}
fetch()接收到的response是一个 Stream 对象,
response.json()是一个异步操作,取出所有内容,并将其转为 JSON 对象。
# axios
axios是一个用于网络请求的第三方库,是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:
- 从浏览器中创建 XMLHttpRequest;
- 从 node.js 发出 http 请求;
- 支持 Promise API;
- 拦截请求和响应;
- 转换请求和响应数据;
- 取消请求;
- 自动转换JSON数据;
- 客户端支持防止CSRF/XSRF;
- 总结
- Ajax 是Async Javascript And Xml的简称,它是原生JavaScript的一种请求方案,利用 XMLHttpRequest 进行异步请求数据,实现无感刷新数据;
- Fetch 是 ES6 新推出的一套异步请求方案,它天生自带 Promise,同时也是原生的,如果在较小项目中为了项目大小着想和兼容性不是那么高的前提下不妨可以用它来进行异步请求也是不错的;
- Axios 是基于 Ajax 和 Promise 封装的一个库,可以利用Promise来更好的管控请求回调嵌套造成的回调地狱;