参考资料:
一、浏览器内置对象
BOM :Browser Object Model(浏览器对象模型),浏览器模型提供了独立于内容的、可以与浏览器窗口进行滑动的对象结构,就是浏览器提供的 API。其主要对象有:
- window对象:BOM 的核心,是 js 访问浏览器的接口,也是 ES 规定的 Global 对象
- location对象:提供当前窗口中的加载的文档有关的信息和一些导航功能。既是 window 对象属性,也是document的对象属性
- navigation 对象:获取浏览器的系统信息
- screen对象:用来表示浏览器窗口外部的显示器的信息等
- history对象:保存用户上网的历史信息
1. Window 对象
windows 对象是整个浏览器对象模型的核心,其扮演着既是接口又是全局对象的角色
- window 对象的属性和方法
alert(<msg>)/confirm(<msg>)/prompt(<msg>,<val>)
open(url,[target,string,boolean])
/*
url: 要加载的URL,
target: 窗口目标
string: 特定的字符串,以逗号分隔的字符串表示新窗口显示的特性
boolean: 表示新页面是否取代浏览器历史记录中当前加载页面的布尔值
*/
onerror() //多用于前端日志和错误采集
/*
*事件处理程序,当未捕获的异常传播到调用栈上时就会调用它,并把错误消息输出到浏览器的 JavaScript 控制上。
window.onerror(描述错误的一条消息, 字符串--存放引发错误的JavaScript代码所在的文档url, 文档中发生错误的行数)
*/
setTimeout(function(){}, val) //超时调用——在指定的时间过后执行代码
setInterval(function(){}, val) //间歇调用——每隔指定的时间就执行一次
/*
*使用 setInterval() 方法的时候,再不加干涉的情况下,该方法会一直执行到页面的卸载,所以一般情况下
serInterval()比较消耗性能。然后setTimeout()方法可以通过调用自身完成间歇调用的功能。
所以说,在一般情况下使用setTimeout()来完成超时与间歇调用。
*/
/* bug:app里嵌入H5时候,setInterval做的倒计时,10次递归 */
- 窗口位置
screenLeft //窗口相对于屏幕左边的位置,适用于IE、Safari、Chrome
screenTop
screenX //窗口相对于屏幕左边的位置,适用于IE、Safari、Chrome,适用于Firefox
screenY
moveBy(x,y) //全兼容
moveTo(x,y)
//跨浏览器获取窗口左边和上边位置
var leftPos = (typeof window.screenLeft == 'number') ? window.screenLeft : window.screenX
var topPos = (typeof window.screenTop == 'number') ? window.screenTop : window.screenY
- 窗口大小
innerWidth //可见视窗的大小
innerHeight
outerWidth //浏览器窗口本身的尺寸
outerHeight
resizeTo(width, height)
resizeBy(width, height)
/* 移动IE浏览器: 不支持该属性,当移动IE浏览器将布局视口的信息保存至document.body.clientWidth与document.body.clientHeight中*/
//获取浏览器视窗大小,可使用can i use网站查询
window.innerWidth || document.body.clientWidth
window.innerHeight || document.body.clientHeight
2. Location 对象
提供当前窗口中的加载的文档有关的信息和一些导航功能。既是 window 对象属性,也是 document 的对象属性
window.location === document.location //true
/* location 对象的主要属性
hash #host 返回url中的 hash(#后字符>=0)
host juejin.im:80 服务器名称+端口
hostname juejin.im 服务器名称
href 当前加载页面的完整的 url
pathname 返回url的的目录和(或)文件名 /book/5a7bfe595188257a7349b52a
port
protocol
search 返回url的查询字符串,以问号开头 ?name=aha&age=20
*/
location 的应用场景:
- 1、解析 url 查询字符串参数,并将其返回一个对象,可通过循环、正则来实现,方法有很多,实现的大体思路是: 通过
location的search属性来获取当前 url 传递的参数,如果 url 中有查询字符串的话就将其问号截取掉,然后再遍历里面的字符串并以等号为断点,使用decodeURIComponent()方法来解析其参数的具体数值,并将其放在对象容器中,并将其返回 - 2、载入新的文档,也可以说是刷新页面,主要有三个方法:
assign(): location.assign("www.xxx.com") 就可立即打开新 url 并在浏览器是我历史中生成一条新的记录, 在一个生成了 5 条浏览记录的页面中,然后使用 assign()跳转 url 后,history 记录只剩两条,一条是通过 assign 跳转的页面,另一条则是上一个页面(使用 assign()跳转方法的页面),其余的所有页面都被清除掉了。
replace(): location.replace("www.bbb.com") 只接受 url 一个参数,通过跳转到的 url 界面不会在浏览器中生成历史记录,就是 history 的 length 不会+1,但是会替代掉当前的页面。
reload(): 其作用是重新加载当前显示的页面,当不传递参数的时候,如果页面自上次请求以来并没有改变过,页面就会从浏览器中重新加载,如果传递true,则会强制从服务器重新加载。
提供当前窗口中的加载的文档有关的信息和一些导航功能。既是 window 对象属性,也是 document 的对象属性。
3. Navigation 对象
navigation 接口表示用户代理的状态和标识,允许脚本查询它和注册自己进行一些活动。
navigation 应用场景:
- 检测插件
- 注册处理程序
navigator.onLine:浏览器是否链接了因特网
4. History对象
- go()
- back()
- forword()
- length - 保存历史记录的数量,可用于检测当前页面是否是用户历史记录的第一页(history.length === 0)
二、浏览器事件模型
浏览器事件模型中的过程主要分为三个阶段:捕获阶段、目标阶段、冒泡阶段
1. 第三个参数
window.addEventListener('click', function(e){
console.log(e.target.nodeName) //指当前点击的元素
console.log(e.currentTarget.nodeName) //绑定监听事件的元素
}, false) //false为默认为冒泡,true为捕获
2. 阻止事件传播
-
e.stopPropagation()阻止冒泡和捕获阶段的传播。 -
stopImmediatePropagation()如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了event.stopImmediatePropagation() 方法,则当前元素剩下的监听函数将不会被执行。
3. 阻止默认行为
e.preventDefault():可以阻止事件的默认行为发生,默认行为是指:点击a标签就转跳到其他页面、拖拽一个图片到浏览器会自动打开、点击表单的提交按钮会提交表单等等,因为有的时候我们并不希望发生这些事情,所以需要阻止默认行为。
4. 兼容性
- attachEvent——兼容:IE7、IE8; 不支持第三个参数来控制在哪个阶段发生,默认是绑定在冒泡阶段
- addEventListener——兼容:firefox、chrome、IE、safari、opera;
三、浏览器网络请求
1. XMLHTTPRequest对象
-
方法
- open(请求方法:“get/post”, 请求url, 是否异步(默认true))
- send(请求体发送数据,无则传入null)
- abort():收到响应之前取消异步请求
- setRequestHeader('MyHeader', 'MyValue')
- getResponseHeader('MyHeader')|getAllResponseHeader()
-
属性
- responseText
- responseXML
- status(响应HTTP状态)
- statusText(响应HTTP状态描述)
- readyState(响应状态,请求/响应过程的哪个阶段):0未初始化|1已打开|2已发送|3接收中|4完成,从一个值变为一个值,会触发readystatechange事件,readystatechange事件处理程序应该在调用open()之前赋值
- timeout超时时间,对应超时事件ontimeout
- 进度事件:loadstart、progress事件:接收数据时反复触发、error、abort、load、loadend
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(null);
2. Fetch
-
方法:fetch(url,{}init对象),返回Promise对象,
只支持异步 -
响应通过response对象获取:fetch().then((response)=>{}).catch(()=>{}),response对象混入了body,提供了5个方法,将ReadableStream转存到缓冲区的内存里,将缓冲区转换为js对象,通过Promise返回。
- response.text() //转为text
- response.json() //转为json
- response.formData()
- response.arrayBuffer()
- response.blob()
-
默认不带cookie -
错误不会reject:当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject -
不支持超时设置 -
需要借用AbortController终止fetch
fetch('http://domain/service', {
method: 'GET'
}
).then(response => response.json())
.then(json => console.log(json))
.catch(error => console.error('error:', error));
//credentials:omit不发送cookie|same-origin同源发送cookie(默认)|include都发送cookie
fetch('http://domain/service', {
method: 'GET',
credentials: 'same-origin'
}
)
// 错误不会reject
// HTTP错误(例如404 Page Not Found 或 500 Internal Server Error)不会导致Fetch返回的Promise标记为reject;.catch()也不会被执行。
// 想要精确的判断 fetch是否成功,需要包含 promise resolved 的情况,此时再判断 response.ok是不是为 true
fetch('http://domain/service', {
method: 'GET'
}
).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));
// 不支持直接设置超时, 可以用promise
function fetchTimeout(url, init, timeout = 3000) {
return new Promise((resolve, reject) => {
fetch(url, init)
.then(resolve)
.catch(reject);
setTimeout(reject, timeout);
})
}
// 中止fetch
// signal用于支持AbortController中断请求
const controller = new AbortController();
//AbortController接口表示一个控制器对象,允许你根据需要中止一个或多个 Web请求。
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();
3. 常见的浏览器请求/响应头/错误码解析
1. 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
2. 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
3. HTTP状态码
100信息性|200成功|300重定向|400客户端错误|500服务器错误
- 200 get 成功
- 201 post 成功
- 301 永久重定向
- 302 临时重定向
- 307 临时重定向,使用get请求重定向
- 304 协商缓存 服务器文件未修改
- 400 客户端请求有语法错误,不能被服务器识别
- 403 服务器受到请求,但是拒绝提供服务,可能是跨域
- 404 请求的资源不存在
- 405 请求的method不允许
- 500 服务器发生不可预期的错误
4. body格式
- form-data:就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来说明文件类型;content-disposition,用来说明字段的一些信息;由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。
- x-www-form-urlencoded:就是application/x-www-from-urlencoded,会将表单内的数据转换为键值对,比如,name=java&age = 23
- raw:可以上传任意格式的文本,可以上传text、json、xml、html等
- binary:Content-Type:application/octet-stream,从字面意思得知,只可以上传二进制数据,通常用来上传文件,由于没有键值,所以,一次只能上传一个文件。
4、axios
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中,有以下特点:
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http请求
- 支持 PromiseAPI
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
1、常见方法
//全局请求
//1、get请求
axios.get('./user?id=1234').then(function(response){}).catch(function(error){})
axios.get('./user',{params:{id: 1234}}).then(function(response){}).catch(function(error){})
//2、post请求
axios.post('/user',{id: 1234}).then(function(response){}).catch(function(error){})
//实例请求
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
instance#request(config)
instance#get(url[, config])
instance#post(url[, data[, config]])
2、相关配置
在 lib/defaults.js 找到的库的默认值,然后是实例的 defaults 属性,最后是请求的 config 参数。后者将优先于前者。
- 全局配置
- 实例配置
- 请求的config参数
//全局配置
axios.defaults.baseURL = 'https://api.example.com';
//实例配置
var instance = axios.create();
instance.defaults.timeout = 2500;
//请求参数
instance.get('/longRequest', {
timeout: 5000
});
1. 请求配置
{
// `url` 是用于请求的服务器 URL
url: '/user',
// `method` 是创建请求时使用的方法
method: 'get', // default
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `headers` 是即将被发送的自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
timeout: 1000,
// `params` 是即将与请求一起发送的 URL 参数,get
// 必须是一个无格式对象(plain object)或 URLSearchParams 对象
params: {
ID: 12345
},
// `data` 是作为请求主体被发送的数据,post
data: {
firstName: 'Fred'
},
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
// `responseEncoding` indicates encoding to use for decoding responses
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // default
// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
// `onUploadProgress` 允许为上传处理进度事件
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
// 对原生进度事件的处理
},
// `maxContentLength` 定义允许的响应内容的最大尺寸
maxContentLength: 2000,
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `cancelToken` 指定用于取消请求的 cancel token
cancelToken: new CancelToken(function (cancel) {
})
}
2. 响应结构
{
// `data` 由服务器提供的响应
data: {},
// `status` 来自服务器响应的 HTTP 状态码
status: 200,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// `headers` 服务器响应的头
headers: {},
// `config` 是为请求提供的配置信息
config: {},
// 'request'
// `request` is the request that generated this response
// It is the last ClientRequest instance in node.js (in redirects)
// and an XMLHttpRequest instance the browser
request: {}
}
3. 错误处理
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
注意:默认情况下,axios将JavaScript对象序列化为JSON。 要以application / x-www-form-urlencoded格式发送数据,需做处理。
- 使用使用URLSearchParams API
- 使用qs库编码数据
const params = new URLSearchParams();
params.append('param1', 'value1');
params.append('param2', 'value2');
axios.post('/foo', params);
import qs from 'qs';
const data = { 'bar': 123 };
const options = {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(data),
url,
};
axios(options);
3. 取消发送
使用 cancel token 取消请求
- 使用
CancelToken.source工厂方法创建 cancel token - 传递一个 executor 函数到
CancelToken的构造函数来创建 cancel token
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// cancel the request
cancel();
4. 并发处理
- axios.all(iterable)
- axios.spread(callback)
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// 两个请求现在都执行完成
//acct方法1返回值
//perms方法2的返回值
}));
四、面试题
1. e.target和e.currentTarget区别?
e.target是实际点击的元素,e.currentTarget是绑定事件监听的元素
2. 同一个元素上存在捕获与冒泡事件,会先执行哪个?
以前老版本浏览器上是谁书写在前面就先执行谁,现在是先捕获再冒泡事件
3. 为什么常见的 cdn 域名 和业务域名不一样?
- 安全问题 - cookie中携带了大量用户信息,如果两个域名相同的话,会将用户信息传输给厂商
- cdn - request header 会携带cookie
- 并发请求数 - http1.1 限制了同源域名
4. vue/react 单页应用都会存在一个index.html 文件,针对 index.html 文件,如果要做缓存的话,适合做什么缓存?
适合做协商缓存;每次打包/生成html文件的时候,文件中的script标签都会发生改变,浏览器去对比发现前后两个html文件发生改变了,就认为更新了,从而去拉更新的代码。
5. 页面为ul + li结构,点击每个li alert对应的索引
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
</ul>
</body>
<script type="text/javascript">
//第一种:给每个li绑定事件
const liList = document.getElementsByTagName("li");
for(let i = 0; i<liList.length; i++){
liList[i].addEventListener('click', function(e){
alert(`内容为${e.target.innerHTML}, 索引为${i}`);
})
}
//第二种:使用捕获绑定事件
const ul = document.querySelector("ul");
ul.addEventListener('click', function (e) {
const target = e.target;
if (target.tagName.toLowerCase() === "li") {
const liList = this.querySelectorAll("li");
index = Array.prototype.indexOf.call(liList, target); //数组和字符串位置函数indexOf
alert(`内容为${target.innerHTML}, 索引为${index}`);
}
})
</script>
</html>
6. 封装一个多浏览器兼容的绑定事件函数
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
}
}