浏览器体系
一、 认识在浏览器运行态下的JS
- 包含:BOM、DOM、ECMAScript
(function(context, undefined){
const _class = ['js', 'browser', 'vue']
// 向全局中挂载
window.classArr = _class.map(item => item)
// 获取当前页面地址
const _url = location.href
// 设置tab标题
document.title = 'zhaowa class'
// 获取渲染节点
document.getElementById('app')
})(this)
追问:了解浏览器JS的执行态
简述: ECMAScript - 基础逻辑、数据处理
DOM - 对于浏览器视窗内,文本的相应操作
BOM - 对于浏览器本身区域能力的处理
二、BOM
1. location
属性 | 值 | 说明 |
---|---|---|
location.href | “https://www.zhaowa.com/search?class=browser#comments” | 路径栏所有,当前加载页面完整的URL |
location.origin | “https://www.zhaowa.com” | URL的原地址,只读 |
location.protocol | “https:” | 页面使用的协议 |
location.host | “www.zhaowa.com” | 服务器名及端口号 |
location.port | “端口号” || " " | 请求的端口,如果URL中没有端口,则返回空字符串 |
location.pathname | “/search/” | URL中的路径和(或)文件名 |
location.search | “?class=browser&id=2” | URL的查询字符串.这个字符串以问号开头 |
location.hash | “#comments” || " " | URL散列值(#号后跟零或多个字符),如果没有则为空字符串 |
location.assign('') // 跳转到指定path => 替换pathname
location.replace('') // 同上,同时替换浏览历史
location.reload()
location.toString() // 产出当前地址字符串
location面试方向
-
location本身api操作
-
提取相关信息
-
api间对比 => assign vs replace
使用
window.location.assign("url")
只会导致加载新文档。使用
window.location.replace("url")
将替换当前文档并用该 URL 替换当前历史记录,这样就无法返回上一个加载的文档。
-
-
路由相关: 跳转、参数、操作 => 场景:可返回(history)、是否刷新(hash)=> replace替换assign、携带参数
- hash:根据#以及后面的字符对页面进行定位,使对应的id元素在可视区域显示,hash改变浏览器不会向服务器发送请求
- history:可以在url里放传参,会使浏览器向服务器发送请求
-
url处理 - 正则 or 手写js处理
-
URI(统一资源标志符,uniform resource identifier) & URL(统一资源定位符,uniform resource locator)’
- URI:资源标识符,当前页面的resource的id,相当于一个人的身份证,通过身份证号可以对应唯一一个资源
- URL:资源定位符,当前页面存放的路径,当前定位的地址信息,相当于通过资源的地址来对应唯一一个资源
2. history
history.state => 存储当前页面的状态
history.pushState()
history.replaceState()
history面试方向
-
路由方向 history和hash的模式
hash优缺点
优点:
a. 不需要后端配合,只需要前端配置路由表
b. 兼容性好
c. hash值改变不会发送请求
缺点:hash需要加#,不符合url规范,不美观history优缺点:
优点:符合url规范,美观
缺点:
a. 在输入地址或刷新页面时会重新请求,后端需要配置index.html页面用户匹配不到静态资源的情况,否则会出现404
b. 兼容性差,利用了pushState和replaceState不同点:
-
hash有#,监听浏览器地址hash值变化,执行相应的js切换网页;
-
history 无#,利用history API实现url地址改变,网页内容改变;
-
它们的区别最明显的就是hash会在浏览器地址后面增加#号,而history可以自定义地址
-
回车刷新: hash 可以加载到hash值对应页面 ; history需要设置默认页面否则会404
-
支持版本: hash支持低版本浏览器和IE浏览器 ; historyHTML5新推出的API
-
3. navigator
- 浏览器系统信息大集合
navigator.userAgent // 获取当前用户的环境信息
- 面试方向
- userAgent 读取信息 => 浏览器兼容性、上报信息
- 能够识别用户使用的操作系统及版本,CPU类型,浏览器及版本,浏览器渲染引擎,浏览器语言,浏览器插件等
- 剪切板、键盘
4. screen
表征显示区域 - 荧幕
面试方向 - 判断区域大小
-
window 视窗判断:
全局入口处:
window.innerHeight
window.innerWidth
文本处获取:
document.documentElement.clientHeight
document.documentElement.clientWidth
document.body.clientHeight
document.body.clientWidth
-
网页视图的size -> offsetHeight = clientHeight + 滚动条 + 边框
document.documentElement.offsetHeight
document.documentElement.offsetWidth
document.body.offsetHeight
document.body.offsetWidth
-
动态定位:
scrollLeft / scrollTop - 距离常规左 / 上滚动距离
offsetLeft / offsetTop - 距离常规左 / 上距离 -
返回元素的大小及其相对于视口的位置
element.getBoundingClientRect()
element.getBoundingClientRect().top
element.getBoundingClientRect().left
element.getBoundingClientRect().bottom
element.getBoundingClientRect().right
- 兼容性 - IE是会多出来2像素
三、 Event事件模型
<div id="app">
<p id="dom"></p>
</div>
冒泡 - ms: p => div => body => HTML => document
捕获 - ns: document => HTML => body => div => p
1. addEventListener
el.addEventListener(event, function, useCapture[是否捕获,默认值false])
2. 相关问题
1. 如何阻止事件的传播?
-
event.stopPropgation() => 既能阻止冒泡,也能阻止捕获
注意:阻止任何传递行为但无法阻止默认事件
-
event.preventDefault() => 阻止默认事件,例如a标签跳转等等自带的事件能力
-
event.stopImmediatePropagation() => 相同节点绑定多个同类事件,如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了event.stopImmediatePropagation()方法,则当前元素剩下的监听函数将不会被执行
2. 引申型面试核心: 兼容性 & 性能
-
attachEvent——兼容:IE7、IE8; 不支持第三个参数来控制在哪个阶段发生,默认是绑定在冒泡阶段 addEventListener——兼容:firefox、chrome、IE、safari、opera
- 区别:
a. 传参:attachEvent 对于事件名需要加上’on’
b. 执行顺序:attachEvent - 后绑定先执行; addEventListener - 先绑定先执行
c. 解绑:detachEvent VS removeEventListener
d. 阻断:event.cancelBubble = true VS event.stopPropgation()
e. 默认事件拦截:event.returnValue = false VS event.preventDefault()
- 区别:
-
手写兼容性事件绑定
class bindEvent { constructor(element) { this.element = element; } // 绑定 addEventListener = (type, handler) => { if(this.element.addEventListener) { this.element.addEventListener(type, handler, false) } else if(this.element.attachEvent) { this.element.attachEvent('on' + type, () => { handler.call(element); }) } else { this.element['on' + type] = handler; } } // 解绑 removeEventListener = (type, handler) => { if(this.element.removeEventListener) { this.element.removeEventListener(type, handler, false) } else if(this.element.detachEvent) { this.element.detachEvent('on' + type, () => { handler.call(element); }) } else { this.element['on' + type] = null; } } // 全局的用static 阻断 static stopPropgation(e) { if (e.stopPropagation) { e.stopPropagation() } else { e.cancelBubble = true; } } // 默认拦截 static preventDefault(e) { if(e.preventDefault) { e.preventDefault() } else { e.returnValue = false; } } }
3. 性能优化 - 事件代理
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<div class="content"></div>
var list = document.querySelector('list');
var li = list.getElementsByTagName('li');
// 代理前 - 硬碰硬
for(var n = 0; n < li.length; n++) {
li[n].addEventListener('click', function() {
// 业务逻辑
})
}
// 代理后 - 利用事件传递
function onClick(e) {
var e = e || window.event;
if(e.target.nodeName.toLowCase() === 'li') {
// 业务逻辑
var liList = this.querySelectorAll('li');
// ……
}
}
list.addEventListener('click', onClick, false)
四、网络层
1. Ajax原理
// 实例化
const xhr = new XMLHttpRequest();
//初始化建立
xhr.open(method, url, async) // get/post; 请求的地址; 是否为异步请求
// 方法的发送请求 - send
xhr.send(data) // get - 可以不传或传入null,post - encodeURIComponent编码拼接
// 接收
// xhr.readyStatus - 0 - 尚未建立open;1 - 已经调用open; 2 - 已经调用send; 3 - 已经收到请求返回; 4- 请求已经完成
xhr.onreadystatuschange = () => {
if(xhr.readyStatus === 4) {
// 判断http状态码
if(xhr.status >= 200 &&
xhr.status < 300 ||
xhr.status == 304) {
// xhr.responseText
}
}
}
// 超时时间
xhr.timeout = 30000
xhr.ontimeout = () => {
// 超时后
}
2. 面试方向
- TCP => HTTP/HTTPs
- 状态码 => 2xx 4xx 5xx | 3xx => 浏览器缓存 => 强缓存(Expires + cache-control) / 协商缓存(last-modified + Etag)
3. 封装ajax手写
ajax({
url: 'reqUrl',
method: 'get',
async: true,
timeout: 30000,
data: {
payload: 'text'
}
}).then(
res => {}
err => {}
).catch(err => {})
// 实现:
function ajax(options) {
const {
url,
method,
async,
data,
timeout
} = options;
const xhr = new XMLHttpRequest()
// 配置超时事件
if (timeout) {
xhr.timeout = timeout;
}
return new Promise((resolve, reject) => {
// 成功
xhr.onreadystatuschange = () => {
if(xhr.readyStatus === 4) {
// 判断http状态码
if(xhr.status >= 200 &&
xhr.status < 300 ||
xhr.status == 304) {
// 返回拦截器
resolve(xhr.responseText)
} else {
//后端返回错误数据
reject()
}
}
}
// 失败
xhr.onerror = err => reject(err)
xhr.ontimeout = () => reject('timeout')
// 传参处理
let _params = []
let encodeData = ''
if (data instanceof Object) {
for(let key in data) {
// 参数编码
_params.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
encodeData = _params.join('&')
}
// method判断连接
if (method === 'get') {
const index = url.indexOf('?')
//没有'?'
if(index === -1) {
url += '?'
} else if(index !== url.length -1) {
url += '&'
}
url += encodeData
}
// 建立连接
xhr.open(method, url, async)
// 请求拦截器……
// 发送请求
if (method === 'get') {
xhr.send(null)
} else {
// post
xhr.setRequestHeader(
//传参方法
'content-type': 'application/x-www-form-urlencoded'
)
xhr.send(encodeData)
}
})
}
// 面试点: content-type => 内容类型 => 浏览器 => ff chrome
五、浏览器原理
面试题: 从url输入到页面展示发生了什么?
-
获取到资源 => 渲染出页面
-
DOM - 生成文本树
-
CSSOM - CSS解析成树形数据结构
-
Render Tree: DOM + CSSOM生成树
-
Layout module:计算Render Tree每个节点具体的状态和位置
-
Painting:呈现到屏幕上
流程总结:Url => HTML解析 - JS + DOM + CSSOM => render tree / JS + css执行 => layout => painting
-
DOM和CSSOM生成的纵向切分
bytes字节串 48 65 2C……) => <characters结构化语言 > => Tokens(tag tree) => Nodes(html|head|body) => DOM | CSSOM