浏览器对象模型(BOM) BOM包含5个东西:
location 管理 URL
navigator 管理浏览器
history 管理历史记录
screen 管理屏幕
window 管理浏览器所有的东西
location相关操作 1 2 3 4 5 6 7 location.href = 'www.baidu.com' location.href = '#shabi' location.reload() location.replace(location.href, 'www.baidu.com' )
navigator相关操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var u = navigator.userAgent;var isAndroid = u.indexOf('Android' ) > -1 || u.indexOf('Adr' ) > -1 ; var isiOS = !!u.match(/(i[^;]+;( U;)? CPU.+Mac OS X/ ); var browser={ versions: function ( ) { var u = navigator.userAgent, app = navigator.appVersion; return { trident: u.indexOf('Trident' ) > -1 , presto: u.indexOf('Presto' ) > -1 , 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/ ), android: u.indexOf('Android' ) > -1 || u.indexOf('Adr' ) > -1 , iPhone: u.indexOf('iPhone' ) > -1 , iPad: u.indexOf('iPad' ) > -1 , webApp: u.indexOf('Safari' ) == -1 , weixin: u.indexOf('MicroMessenger' ) > -1 , qq: u.match(/sQQ/i ) == " qq" }; }(), language:(navigator.browserLanguage || navigator.language).toLowerCase() } if (browser.versions.trident){ alert("is IE" ); }if (browser.versions.webKit){ alert("is webKit" ); }if (browser.versions.mobile||browser.versions.android||browser.versions.ios){ alert("移动端" ); }
1 2 3 4 5 6 7 8 9 10 11 let timing = performance.getEntriesByType('navigation' )[0 ]let tti = timing.domInteractive - timing.fetchStartlet connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;let type = connection.effectiveType;function updateConnectionStatus ( ) { console .log("connection type of change from" + type + "to" + connection.effectiveType); } connection.addEventListener('change' ,updateConnectionStatus,false );
history相关操作 history对象是用来处理历史记录的, 在 HTML5 它增加了一些 API 使得它也可以做单页应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 history.pushState(null , 'title' , "/profile" ) window .addEventListener("popstate" , function (e ) { var state = e.state; console .log('pop state' , state) }) history.replaceState(state1, state2) history.length
spa的原理机制 这里有两种实现方式, 一种是哈希路由的方式,另一种是history的机制. 所谓单页Web应用, 就是只有一张Web页面的应用. 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序. 不像以前所有的页面跳转都要新开窗口,用户体验不好.另外最重要的一点是不会刷新页面(也就是说减少了不必要的刷新操作). 另外为啥不用ajax实现spa因为ajax的 实现方式没有记住页面状态的能力
哈希路由的方式, hash值得改变不会导致浏览器向服务器发送请求,并且改变的时候会触发hashchange事件, hashchange可以捕捉url的变化从而实现spa.
1 2 3 4 5 6 7 8 9 10 11 12 <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 >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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) let { path, query } = parsedUrl(location.hash.slice(1 )) let r = responseForPath(path) renderHtml(r) } const bindEventHashChange = () => { window .addEventListener('hashchange' , (event ) => { log('event url' , event.oldURL, event.newURL) render() }) } const bindEvents = () => { bindEventHashChange() } const __main = () => { bindEvents() render() } document .addEventListener('DOMContentLoaded' , () => { __main() })
history spa
1 2 3 4 5 6 7 8 9 10 <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 >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const bindEventPopState = () => { window .addEventListener('popstate' , (event ) => { 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相关操作 1 2 screen.availWidth; screen.availHeight
回流与重绘
url输入到渲染过程
DNS域名解析,根据url找到对应的服务地址
三次握手构建TCP连接
发送HTTP请求
服务端响应请求,返回相应的资源文件
解析文档
构建 DOM 树和 CSSOM(css object modal)
生成渲染树(render tree):从DOM树的根节点开始遍历每个可见节点,对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们,根据每个可见节点以及其对应的样式,组合生成渲染树
Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的集合信息
Painting(重绘):根据渲染树及其回流得到的集合信息,得到节点的绝对像素
绘制,在页面上展示,这一步还涉及到绘制层级、GPU相关的知识点
加载js脚本,加载完成解析js脚本
浏览器的多进程 一般情况下在浏览器中每个tab页面可以算作一个进程。可以通过打开chorme浏览器的更多工具->任务管理器 查看当前浏览器中正在运行哪些进程
chorme 浏览器的主要进程和职责
Browser Process 浏览器的主进程(负责协调、主控)
负责包括地址栏,书签栏,前进后退按钮等部分的工作
负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问
负责各个页面的管理,创建和销毁其他进程
Renderer Process 负责一个 tab 内关于网页呈现的所有事情,页面渲染,脚本执行,事件处理等
Plugin Process 负责控制一个网页用到的所有插件,如 flash 每种类型的插件对应一个进程,仅当使用该插件时才创建
GPU Process 负责处理 GPU 相关的任务
Renderer Process进程里的多个线程
GUI渲染线程
负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行,在Javascript引擎运行脚本期间,GUI渲染线程都是处于就绪状态的
javaScript引擎线程
JS内核,负责处理Javascript脚本程序。 一直等待着任务队列中任务的到来,然后解析Javascript脚本,运行代码。GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞
定时触发器线程
定时器setInterval与setTimeout所在线程。浏览器定时计数器并不是由JavaScript引擎计数的 因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。
事件触发线程
用来控制事件轮询,JS引擎自己忙不过来,需要浏览器另开线程协助 当JS引擎执行代码块如鼠标点击、AJAX异步请求等,会将对应任务添加到事件触发线程中 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理任务队列的队尾,等待JS引擎的处理 由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
异步http请求线程
在XMLHttpRequest在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理。
关于js单线程 上面所说的线程是不能并行的, 是一个单线程,原因如下:
假设存在两个线程同时操作一个DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果。如果没懂可以看看阮一峰的博客 学过操作系统的进程调度知道可以用锁的机制解决,但是会使得javascript的实现变得复杂,想想当初javascript是一天就搞出来的