浏览器对象模型(BOM)
BOM包含5个东西:
- location 管理 URL
- navigator 管理浏览器
- history 管理历史记录
- screen 管理屏幕
- window 管理浏览器所有的东西
location相关操作
// 改变 href 属性就可以跳转页面, 相对路径就是放在原url的后面拼接,绝对路径就是替换
location.href = 'www.baidu.com'
location.href = '#shabi'
// reload 刷新页面
location.reload()
// replace 替换当前页面
location.replace(location.href, 'www.baidu.com')
复制代码
navigator相关操作
// 比较需要关注的是userAgent这个属性
// 通过对navigator.userAgent的处理可以判断当前的平台环境(andoriod, ios, web)
var u = navigator.userAgent;
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android终端
var isiOS = !!u.match(/(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
var browser={
versions: function(){
var u = navigator.userAgent, app = navigator.appVersion;
return {
trident: u.indexOf('Trident') > -1, //IE内核
presto: u.indexOf('Presto') > -1, //opera内核
webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,//火狐内核
mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
ios: !!u.match(/(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1, //android终端
iPhone: u.indexOf('iPhone') > -1 , //是否为iPhone或者QQHD浏览器
iPad: u.indexOf('iPad') > -1, //是否iPad
webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部
weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增)
qq: u.match(/sQQ/i) == " qq" //是否QQ
};
}(),
language:(navigator.browserLanguage || navigator.language).toLowerCase()
}
//判断是否IE内核
if(browser.versions.trident){ alert("is IE"); }
//判断是否webKit内核
if(browser.versions.webKit){ alert("is webKit"); }
//判断是否移动端
if(browser.versions.mobile||browser.versions.android||browser.versions.ios){ alert("移动端"); }
复制代码
history相关操作
history对象是用来处理历史记录的, 在 HTML5 它增加了一些 API 使得它也可以做单页应用
/*
pushState的 三个参数分别是:
自定义对象, 新页面的标题, 新页面的地址
跳转到新页面地址的时候,浏览器不会刷新页面
history.pushState 并不会触发popstate事件, 除了将数据push进入历史栈
它还会改变loaction当前的属性
*/
history.pushState(null, 'title', "/profile")
// 用户点击前进和后退按钮的时候, 会触发window的popstate事件
// 前进:history.forward(), history.go()
// 后退:history.back()
window.addEventListener("popstate", function(e) {
// state 就是 pushState 的第一个参数
var state = e.state;
console.log('pop state', state)
})
// replaceState的作用和pushState一样, 只是不生成一条历史纪录
history.replaceState(state1, state2)
// 表示会话历史中元素的数目
history.length
复制代码
spa的原理机制
这里有两种实现方式, 一种是哈希路由的方式,另一种是history的机制.
所谓单页Web应用, 就是只有一张Web页面的应用. 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序. 不像以前所有的页面跳转都要新开窗口,用户体验不好.另外最重要的一点是不会刷新页面(也就是说减少了不必要的刷新操作). 另外为啥不用ajax实现spa因为ajax的
实现方式没有记住页面状态的能力
哈希路由的方式, hash值得改变不会导致浏览器向服务器发送请求,并且改变的时候会触发hashchange事件, hashchange可以捕捉url的变化从而实现spa.
<body>
<h1>spa hash demo</h1>
<ul>
<li>
<a href="#/">home</a>
<a href="#/arena">arena</a>
<a href="#/questions?name=woyao">questions</a>
<a href="#/classroom?name=woyao&height=171">classroom</a>
</li>
</ul>
<div class="route"></div>
</body>
复制代码
const log = console.log.bind(console)
const renderHtml = (text) => {
let element = document.querySelector('.route')
element.innerHTML = text
}
const responseForPath = (path) => {
let mapper = {
'/': 'home page',
'/arena': 'arena page',
'/questions': 'question page',
'/classroom': 'classroom page',
}
if (path in mapper) {
return mapper[path]
} else {
return 'not found'
}
}
const argsFromQuery = (query) => {
let o = {}
let qs = query.split('&')
qs.forEach(e => {
let [k, v] = e.split('=')
o[k] = v
})
return o
}
const parsedUrl = (url) => {
let path = ''
let query = {}
let index = url.indexOf('?')
if (index > -1) {
path = url.slice(0, index)
let q = url.slice(index + 1)
query = argsFromQuery(q)
} else {
path = url
}
return {
path,
query,
}
}
const render = () => {
log('location.hash', location.hash)
// 再用 parsedUrl 解析这个地址, 得到 path 和 query
let { path, query } = parsedUrl(location.hash.slice(1))
let r = responseForPath(path)
renderHtml(r)
}
const bindEventHashChange = () => {
window.addEventListener('hashchange', (event) => {
// event.oldURL 表示变化之前的 URL, event.newURL 表示变化之后(也就是现在)的 URL
log('event url', event.oldURL, event.newURL)
render()
})
}
const bindEvents = () => {
bindEventHashChange()
}
const __main = () => {
bindEvents()
// 初始化渲染
render()
}
// DOMContentLoaded 事件表示 HTML 已经加载(渲染)到页面中, 这个时候操作 DOM 元素就没有问题
document.addEventListener('DOMContentLoaded', () => {
__main()
})
复制代码
history spa
<h1>spa history demo</h1>
<ul>
<li>
<a href="/">home</a>
<a href="/arena">arena</a>
<a href="/questions?name=gua">questions</a>
<a href="/classroom?name=gua&height=169">classroom</a>
</li>
</ul>
<div class="route"></div>
复制代码
// 代码几乎和hash的那段js代码一样,只是事件机制这边处理不一样, 我就不再重复写一次了
const bindEventPopState = () => {
// 注意: 调用 history.pushState 并不会触发 popstate 事件
// 当使用浏览器的前进、后退功能时会触发这个事件
window.addEventListener('popstate', (event) => {
// event.state 的值是 history.pushState 调用时传入的第一个参数
log('pop state', event.state)
})
}
const bindEventLink = () => {
let links = document.querySelectorAll('a')
for (let l of links) {
l.addEventListener('click', (event) => {
event.preventDefault()
let self = event.target
let path = self.href
let state = {
'path': path
}
history.pushState(state, '', path)
render()
})
}
}
const bindEvents = () => {
bindEventPopState()
bindEventLink()
}
复制代码
screen相关操作
// 可用的屏幕的宽度和高度
screen.availWidth; screen.availHeight
复制代码
回流与重绘
-
回流
renderTree: 简单的理解就是DOM Tree和我们写的CSS结合在一起之后,渲染出了render tree
回流: 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。
-
导致回流的操作
- 页面首次渲染
- 浏览器窗口大小发生改变
- 元素尺寸或位置发生改变
- 元素内容变化(文字数量或图片大小等等)
- 元素字体大小变化
- 添加或者删除可见的DOM元素
- 激活CSS伪类(例如::hover)
- 查询某些属性或调用某些方法
-
如何避免
-
避免使用table布局。
-
尽可能在DOM树的最末端改变class。
-
避免设置多层内联样式。
-
将动画效果应用到position属性为absolute或fixed的元素上。
-
避免使用CSS表达式(例如:calc())
-
避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
-
避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
-
也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
-
避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
-
-
-
重绘
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格.
而不会影响布局的,比如background-color。浏览器会将新样式赋予给元素并重新绘制它
这个过程称为重绘- 导致重绘的操作
-
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等)
-
- 导致重绘的操作
url输入到渲染过程
- DNS域名解析,根据url找到对应的服务地址
- 三次握手构建TCP连接
- 发送HTTP请求
- 服务端响应请求,返回相应的资源文件
- 解析文档
- 构建 DOM 树和 CSSOM(css object modal)
- 生成渲染树(render tree):从DOM树的根节点开始遍历每个可见节点,对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们,根据每个可见节点以及其对应的样式,组合生成渲染树
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的集合信息
- Painting(重绘):根据渲染树及其回流得到的集合信息,得到节点的绝对像素
- 绘制,在页面上展示,这一步还涉及到绘制层级、GPU相关的知识点
- 加载js脚本,加载完成解析js脚本