目标
- 浏览器内置JS对象详解
- 浏览器事件模型详解
- 浏览器请求相关内容详解
知识要点
BOM 浏览器对象模型
Browser Object Model(浏览器对象模型),浏览器模型提供了独立于内容的、可以与浏览器窗口进行互动的对象结构,就是浏览器提供的 API 其主要对象有:
- window 对象——BOM 的核心,是 js 访问浏览器的接口,也是 ES 规定的 Global 对象
- location 对象:提供当前窗口中的加载的文档有关的信息和一些导航功能。既是 window 对象属 性,也是 document 的对象属性
- navigation 对象:获取浏览器的系统信息
- screen 对象:用来表示浏览器窗口外部的显示器的信息等
- history 对象:保存用户上网的历史信息
window对象
整个浏览器对象模型的核心,其扮演着既是接口又是全局对象的角色
窗口位置
screen对象
大小
定时器
setTimeout
setInterval
Location 对象
提供当前窗口中的加载的文档有关的信息和一些导航功能。既是 window 对象属性,也是 document 的对象属性
location 对象的主要属性:
Navigation 对象
navigation 接口表示用户代理的状态和标识,允许脚本查询它和注册自己进行一些活动
History 对象
history 对象保存着用户上网的历史记录,从窗口被打开的那一刻算起,history 对象是用窗口的浏览历史用文档和文档状态列表的形式表示。
浏览器事件捕获,冒泡
- 捕获阶段
- 目标阶段
- 冒泡阶段
addEventListener(eventName:string,callback:Function,true|false)
第三个参数, 如果为true,就是代表在捕获阶段执行。如果为false,就是在冒泡阶段进行
阻止事件传播
- e.stopPropagation()
大家经常听到的可能是阻止冒泡,实际上这个方法不只能阻止冒泡,还能阻止捕获阶段的传播。
- stopImmediatePropagation() 如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation() 方法,则当前元素剩下的监听函数将不会被执行
阻止默认行为
e.preventDefault()
e.preventDefault()可以阻止事件的默认行为发生,默认行为是指:点击a标签就转跳到其他页面、拖拽一个图片到浏览器会自动打开、点击表单的提交按钮会提交表单等等,因为有的时候我们并不希望发生这些事情,所以需要阻止默认行为
兼容性
attachEvent——兼容:IE7、IE8; 不支持第三个参数来控制在哪个阶段发生,默认是绑定在冒泡阶段 addEventListener——兼容:firefox、chrome、IE、safari、opera;
绑定事件的运用,以及封装一个多浏览器兼容的绑定事件函数
class BomEvent {
constructor(element) {
this.element = element;
}
addEvent(type, handler) {
if (this.element.addEventListener) {
//事件类型、需要执行的函数、是否捕捉
this.element.addEventListener(type, handler, false);
} else if (this.element.attachEvent) {
this.element.attachEvent('on' + type, function () {
handler.call(element);
});
} else {
this.element['on' + type] = handler;
}
}
removeEvent(type, handler) {
if (this.element.removeEnentListener) {
this.element.removeEnentListener(type, handler, false);
} else if (element.datachEvent) {
this.element.detachEvent('on' + type, handler);
} else {
this.element['on' + type] = null;
}
}
}
// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
function stopPropagation(ev) {
if (ev.stopPropagation) {
ev.stopPropagation(); // 标准w3c
} else {
ev.cancelBubble = true; // IE
}
}
// 取消事件的默认行为
function preventDefault(event) {
if (event.preventDefault) {
event.preventDefault(); // 标准w3c
} else {
event.returnValue = false; // IE
}
}
大家常见的一个面试题可能是ul + li,点击每个li alert对应的索引,这里就给大家来写一下看看
- 先来给每个li绑定事件
- 再来写一个事件委托的方式
冒泡实例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="parent" class="flex-center">
parent
<p id="child" class="flex-center">
child
<span id='son' class="flex-center">
son
<a href="https://www.baidu.com" id="a-baidu">点我啊</a>
</span>
</p>
</div>
</body>
<script type="text/javascript" src="index.js"></script>
<style>
#parent {
background-color: bisque;
width: 700px;
height: 700px;
}
#child {
background-color: chocolate;
width: 500px;
height: 500px;
}
#son {
background-color: crimson;
width: 300px;
height: 300px;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
}
</style>
</html>
index.js
const parent = document.getElementById("parent");
const child = document.getElementById("child");
const son = document.getElementById("son");
// const baidu = document.getElementById("a-baidu");
// baidu.addEventListener('click', function (e) {
// e.preventDefault();
// })
window.addEventListener("click", function (e) {
// e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);//capture: 默认值为false(即 使用事件冒泡). 是否使用事件捕获;或者在捕获时执行,还是在冒泡时执行
parent.addEventListener("click", function (e) {
// e.stopPropagation();
// e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
console.log("parent 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
child.addEventListener("click", function (e) {
console.log("child 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
son.addEventListener("click", function (e) {
console.log("son 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);
window.addEventListener("click", function (e) {
// e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
parent.addEventListener("click", function (e) {
console.log("parent 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
child.addEventListener("click", function (e) {
console.log("child 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
son.addEventListener("click", function (e) {
console.log("son 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);
ajax 及 fetch API 详解
XMLHTTPRequest
ajax使用XMLHttpRequest实现demo例子
let xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain/service');
// request state change event
xhr.onreadystatechange = function () {
// request completed?
if (xhr.readyState !== 4) return;
if (xhr.status === 200) {
// request successful - show response
console.log(xhr.responseText);
} else {
// request error
console.log('HTTP error', xhr.status, xhr.statusText);
}
};
// xhr.timeout = 3000; // 3 seconds
// xhr.ontimeout = () => console.log('timeout', xhr.responseURL);
// progress事件可以报告长时间运行的文件上传
// xhr.upload.onprogress = p => {
// console.log(Math.round((p.loaded / p.total) * 100) + '%');
// }
// start request
xhr.send();
使用ts 手写一个ajax
使用的 ts, XMLHttpRequest||ActiveXObject (microsoft的xmlHTTP)(兼容), promise, encodeURIComponent来编码url query, xhr.onreadystatechange来接收response, xhr.timeout设置超时,设置xhr.ontimeout来reject
interface IOptions {
url: string;
type?: string;
data: any;
timeout?: number;
}
function formatUrl(json) {
let dataArr = [];
json.t = Math.random();
for (let key in json) {
dataArr.push(`${key}=${encodeURIComponent(json[key])}`)
}
return dataArr.join('&');
}
export function ajax(options: IOptions) {
return new Promise((resolve, reject) => {
if (!options.url) return;
options.type = options.type || 'GET';
options.data = options.data || {};
options.timeout = options.timeout || 10000;
let dataToUrlstr = formatUrl(options.data);
let timer;
// 1.创建
let xhr;
if ((window as any).XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
if (options.type.toUpperCase() === 'GET') {
// 2.连接
xhr.open('get', `${options.url}?${dataToUrlstr}`, true);
// 3.发送
xhr.send();
} else if (options.type.toUpperCase() === 'POST') {
// 2.连接
xhr.open('post', options.url, true);
xhr.setRequestHeader('ContentType', 'application/x-www-form-urlencoded');
// 3.发送
xhr.send(options.data);
}
// 4.接收
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
clearTimeout(timer);
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(xhr.status);
}
}
}
if (options.timeout) {
timer = setTimeout(() => {
xhr.abort();
reject('超时');
}, options.timeout)
}
// xhr.timeout = options.timeout;
// xhr.ontimeout = () => {
// reject('超时');
// }
});
}
fetch
- 默认不带cookie
- 错误不会reject
- 不支持超时设置
- 需要借用AbortController中止fetch
//默认不带cookie
fetch('http://domain/service',{
method:'GET',
credentials:'same-origin'
}).then(response=>{
if(response.ok){
return response.json();
}
throw new Error('Network response was not ok.');
})
.then(json=>console.log(json))
.catch(error=>console.error('error',error))
/ 错误不会reject
// HTTP错误(例如404 Page Not Found 或 500 Internal Server Error)不会导致Fetch返回的Promise标记为reject;.catch()也不会被执行。
// 想要精确的判断 fetch是否成功,需要包含 promise resolved 的情况,此时再判断 response.ok是不是为 true
//不支持直接设置超时,可以用promise
function fetchTimeOut(url,init,timeout=3000){
return new Promise((resolve,reject)=>{
fetch(url,init)
.then(resolve)
.catch(reject);
setTimeout(reject,timeout)
})
}
// 中止fetch
const controller = new AbortController();
fetch(
'http://domain/service', {
method: 'GET',
signal: controller.signal
})
.then(response => response.json())
.then(json => console.log(json))
.catch(error => console.error('Error:', error));
controller.abort();
常见的浏览器请求/响应头/错误码解析
request header
:method: GET :path: /solar-comment/api/comment/tutor-primary-activity/senior-recommend/users/self?tagSource=&productId=351& appId=0 :scheme: https accept: application/json, text/plain, / accept-encoding: gzip, deflate, br cache-control: no-cache cookie: deviceId=c122305d338525616baea870cc76dd5b; abSeed=843447469b71b0978db580220c952c10; userid=172270653; persistent=3411agNdImBJd8GjTW6bWT9Vg0U2yoaka3Lp8sSCiv9B6MDvr27fL4o50ha+Pfuhi1y4/Gg8aRN3FEP+VV4jWA==; sid=5530384168693043754; sess=QvrAQ0Cq+EcDQQPTer2XHlv4fhIRaW/YCb/e4pz/I+vzKp85mI2ukPUBIuGweXj5sq8HhuYQtf03DxK4dphwkOyBKovyUyC5I8t9exQw6Aw= origin: m.yuanfudao.biz referer: m.yuanfudao.biz/primary/mar… 来自那个页面 user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1
response header
access-control-allow-credentials: true access-control-allow-origin: m.yuanfudao.biz content-encoding: gzip content-type: application/json;charset=UTF-8 date: Thu, 06 Aug 2020 08:15:05 GMT set-cookie: sess=QvrAQ0Cq+EcDQQPTer2XHlv4fhIRaW/YCb/e4pz/I+uSfZtum4dPp9q4HJL5o+GWuDXHXQLQF2JrIgwzZPaZHWal4qYZy/cfW0Sle/fyB/w=;domain=.yuanfudao.biz;path=/;HttpOnly set-cookie: userid=172270653;domain=.yuanfudao.biz;path=/;HttpOnly status: 200
status
200 get 成功 201 post 成功 301 永久重定向 302 临时重定向 304 协商缓存 服务器文件未修改 400 客户端请求有语法错误,不能被服务器识别 403 服务器受到请求,但是拒绝提供服务,可能是跨域 404 请求的资源不存在 405 请求的method不允许 500 服务器发生不可预期的错误
补充知识点
Bom
-
BOM 浏览器对象模型 (用于访问和操作浏览器窗口)
- 弹出新浏览器窗口
- 移动,缩放,关闭浏览器窗口
- 提供浏览器详细信息 navigator对象
- 加载页面的详细信息 location对象
- 用户显示器分辨率详细信息
- cookies支持
- 自定义对象 如 XMLHttpRequest
- 核心对象是window, 表示浏览器的一个实例,
- 访问浏览器的一个接口
- Global对象
- 任何一个对象,变量和函数,都以window作为Global对象
窗口关系及框架
-
每个框架都有自己的window对象, 保存在frames集合中
<html> <frameset>//弃用 <frame src='frame.html'> </frameset> </html>窗口位置
-
screenLeft属性,窗口相对于屏幕左边的位置(火狐 screenX)
-
screenTop属性,窗口相对于屏幕上边的位置(screenY)
窗口大小
-
innerWidth 页面视图区的大小,减去边框的宽度 移动上是屏幕上可见页面区域的大小
-
innerHeight
-
outerWidth
-
outerHeight
导航和打开窗口
window.open(url)
location对象
- window.location , document.localtion引用的是同一个对象
- host 'www.wrox.com:80'
- hostname 'www.wrox.com'
- href ‘http:/ww.wrox.com’
- pathname '/abd/'
- port
- protocol http
- search '?q=javascript' //使用多 解析查询字符串参数
function getQueryStringArgs() {
var qs = location.search.length > 0 ? location.search.substring(1) : '',
args = {},
items = qs.length ? qs.split('&') : [],
item = null,
name = null,
i = 0,
len = items.length;
for (i = 0; i < len; i++) {
item = items[i].split('=');
//查询字符串被编译过,要解码
name = decodeURIComponent(item[0]);
value = decodeURIComponent(item[1]);
if (name.length) {
args[name] = value;
}
}
return args;
}
console.log(getQueryStringArgs())
位置操作,打开新的地址
localtion.assign('www.wrox.com') //localtion.href, window.location 其实调用的都是assign方法 window.location= 'www.wrox.com'; localtion.href = 'www.wrox.com'
重新加载
localtion.reload();
navigator对象
识别客户端浏览器
检测插件
navigator.plugins 是一个插件数组,每个元素有name
客户端检测
引擎
识别平台 windows, Max Unix
识别移动设备
识别游戏系统
##Ajax 与Comet, XMLHttpREquest
##XMLHttpREquest 用于访问请求api
xhr.open('get','example.php',fasle)
xhr.send(null);
if(xhr.status>=200&&xhr.status<300||xhr.status==304){
}
- 启动一个请求,准备发送,xhr.open 三个参数, 1.请求类型,2.请求的url,3.是否异步发送请求
- xhr.send(body) 发送请求,如果不需要发送数据, body参数可以传null
同步发送时,JavaScript代码会等到服务器相依之后继续执行
返回的数据
- responseText 作为响应主体返回的文本
- responseXML 如果响应的内容是‘text/xml’ 或者‘application/xml’ ,属性保存返回的结果
- status 响应的HTTP状态
- statusText Http状态的说明
发送异步请求
readyState属性,表示请求/响应过程的当前活动阶段
- 0 未初始化。未调用open
- 1 启动,调用open,未调用send
- 2 发送,调用send,未收到响应
- 3 接收,接收到部分响应数据
- 4 完成,已经接收到全部响应数据,而且已经在客户端使用
var xhr = createXHR();
//利用dom0级方法为xhr对象添加事件处理程序
xhr.onreadystatechange = function(){
if(xhr.status >=200 && xhr.status <300||xhr.status==304){
}
}
xhr.open('get','test/a/b',true)
xhr.setRequestHeader('MyHeader','MyValue');
xhr.send(null)
GET请求
xhr.open('get','example.php?name=value&name2=value2',true)
url +="?"+encodeURIComponent(name)+"="+encodeURIComponent(value);
return url;
POST 请求
var xhr = createXHR();
//利用dom0级方法为xhr对象添加事件处理程序
xhr.onreadystatechange = function(){
if(xhr.status >=200 && xhr.status <300||xhr.status==304){
}
}
xhr.open('post','test/a/b',true)
xhr.setRequestHeader('MyHeader','MyValue');
xhr.send(serialize({'d':'ab'}))
XMLHttpRequest2
FormData
var xhr = createXHR();
//利用dom0级方法为xhr对象添加事件处理程序
xhr.onreadystatechange = function(){
if(xhr.status >=200 && xhr.status <300||xhr.status==304){
}
}
xhr.open('post','test/a/b',true)
xhr.setRequestHeader('MyHeader','MyValue');
var data = new FormData();
data.append('name','Nic')
xhr.send(data)
超时限定
var xhr = createXHR();
//利用dom0级方法为xhr对象添加事件处理程序
xhr.onreadystatechange = function(){
if(xhr.status >=200 && xhr.status <300||xhr.status==304){
}
}
xhr.open('post','test/a/b',true)
xhr.setRequestHeader('MyHeader','MyValue');
var data = new FormData();
xhr.timeout=1000;//1秒
xhr.ontimeout = function(){
console.log('d')
}
data.append('name','Nic')
xhr.send(data)
进度事件
Progress Events定义了与客户端服务器通信有关的事件
- loadStart 接收响应数据的第一个字节触发
- progress 接收响应期间持续不断触发
- error 请求发生错误触发
- abort abort()方法而终止连接时触发
- load 接收完整响应数据时触发
- loadend 在通信完成或者触发error, abort, load事件后触发
progress事件
xhr.onprogress 在浏览器接收新的数据据期间周期性的触发
#服务器向页面推送数据的技术
Comet
一种更高级的Ajax技术
也称服务器推送
一种服务器向页面推送数据的技术
能够让信息实时地推送到页面。适合处理体育比赛,股票报价
两种实现方式
- 长轮询
- 流
长轮询
浏览器定时向服务器发送请求,看有咩有更新数据
过程和短轮询(请求,马上响应)不一样
过程:
- 页面发送一个服务器请求
- 服务器一直保持连接打开,直到有数据可发送
- 发送完数据之后,浏览器关闭连接
- 重复
使用XHR对象,和setTimeout就能实现
HTTP流
整个生命周期内只使用一个HTTP连接
浏览器向服务器发送一个请求,服务器保持连接打开,然后周期性地向浏览器发送数据
- readyState值,会周期性地变为3,responseText属性中就会保存接收到所有的数据
function createStreamingClient(url,progress,fininsed){
var xhr = new XMLHttpRequest(),received =0;
xhr.open('get',url,true);
xhr.onreadystatechange = function(){
var result;
if(xhr.readyState ===3){
//取得最新数据并调整计数器
result = xhr.responseText.substring(received);
received = += result.length;
//调用progress回调
progress(result);
}else if (xhr.readyState===4){
fininshed(xhr.responseText)
}
}
}
var client = createStreamingClient('streaming.php',function(data){
console.log(data)
}),function(data){
console.log(data)
}
浏览器设区简化Comet技术,创建两个新的接口
SSE(server sent events, 服务器发送事件)
围绕只读的Comet交互推出的API或者模式
- 用于创建服务器的单向连接,服务器可以通过一个连接发送任意数量的数据。
- 服务器响应的MIME类型必须是text/event-stream
- 支持短轮询,长轮询,HTTP流
- 断开连接时,决定何时重新连接
- 适合长轮询,和http流
- 断开不重连,使用close()方法
//传入一个入口点, 需要与创建对象的页面同源, 相同的url模式,域,端口
var source = new EventSource('myevents.php')
//- open 在建立连接时触发
//- message: 从服务器接收到新事件时触发
//- error: 在无法建立连接时触发
source.onmessage = function(event){
var data = event.data;
//处理数据
}
事件流,响应格式是纯文本
Web Sockets (重点)
- 全双工
- 双向通信
流程:
-
javascript创建了 Web Socket之后,会有一个HTTP请求发送到(浏览器 有疑惑)以发送连接
-
取得服务器响应后,建立的连接后从HTTP 升级到WebSocket协议
-
未加密 ws://, 加密: wss://
-
适合移动应用
-
readyState属性,表示当前状态
- WebSocket.OPENING(0) 正在建立连接
- OPEN(1):已经建立连接
- CLOSING(2):正在关闭连接
- CLOSE(3) 已经关闭连接
-
关闭,socket.close()
-
其他事件
-
open
-
error
-
close
-
var socket = new WebSocket('ws://www.example.com/server.php');
//发送数据,纯文本
socket.send('hello world')
socket.send(JSON.stringify({'a':'b'}))
//接收
socket.onmessage = function(event){
var data = event.data;
//处理数据
}
socket.onopen = function(){}
socket.onerror = function(){
}
socket.onclose = function(){
}
安全
确保XHR,访问url 安全, 验证发送者是否有权限访问相应的资源
- ssl 连接访问
- 签名
- cookie验证
- 检测url是否可信
跨域源资源共享
通过XHR 实现Ajax通信的一个主要限制,跨域安全策略。 XHR对象只能访问与包含它的页面位于同一个域中的资源
CORS 是w3c的一个工作草案,定义了浏览器与服务器应该如何沟通。cors的背后基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败
-
http get /post 请求
-
添加Origin: www.nczoline.net
-
服务器认为这个请求可以接受,添加Access-Control-Allow-Origin头部,内容 www.nczoline.net
IE对CORS的实现
xdr 类型,这个对象与XHR类似
- cookie不会随请求发送,也不会随响应返回
- 只能设置请求头部信息中的Content-Type字段
- 不能访问响应头部的信息
- 只支持get. post请求
- 请求都是异步执行的,请求返回触发load事件,响应数据保存在reponseText
- 无法确定响应的状态码
- onerror事件处理请求失败
缓解:
csrf cross-site request forgery 跨站点请求伪造
xss cross-siet scripting 跨站点脚本
Prefighted Requests
CORS 通过一种叫做Preflighted Requests的透明服务器验证机制,支持开发人员自定义头部
-
Origin : www.ncz.net
-
Access-Control-Allow-Methods:POST,运行的方法
-
Access-Control-Allow-Headers:NCZ
-
Access-Control-Max-Age:172800缓存的时间
-
Access-Control-Allow-Credentials: true ,默认跨域请求不提供凭据,来设置这个来,要求提供凭据
实现跨浏览器的cors
1.检查是否存在withCredentials属性
2.检测XDomainRequest对象是否存在
function createCORSRequest(method,url){
var xhr = new XHLHttpRequest();
if('withCredentias' in xhr){
xhr.open(method,url,true)
}else if(typeof XDomainRequest !="undefined"){
xhr=new XDomainRequest();
xhr.open(method,url)
}else{
xhr = null;
}
return xhr;
}
var request = createCORSRequest('get','http://www.somewhere-else.com/page/');
if(request){
request.onload = function(){
//对request.responseText进行处理
};
request.send();
}
其他跨域技术
图像Ping 常用于跟踪用户点击页面或动态广告曝光次数
- 只能发送get
- 无法访问服务器响应脚本
- 浏览器向服务器的单向通信
var img = new Image();
img.onload = imge.onerror = function(){
...
}
img.src='http://www.example.com/test?name=N'
JSONP
-
能直接访问响应脚本
-
双向通信
-
其他域不安全,会夹带恶意代码
JSON width padding 填充式JSON 或参数式JSON
callback({'name':'Nicholas'})
由回调函数和数据组成
回调函数是响应到来时页面应该调用的函数
JSONP请求:freegeip.net/json/?callb…
function handleReponse(response){
console.log('ddd')
}