BOM:浏览器对象模型之浏览器剖析入门

Posted zheoneandonly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BOM:浏览器对象模型之浏览器剖析入门相关的知识,希望对你有一定的参考价值。

  • BOM简介
  • BOM与DOM的关系
  • BOM对象包含的内容
  • 重新认识浏览器

 一、分裂的BOM和被收服的DOM

BOM定义:是browser object model的缩写,简称浏览器对象模型。

主要处理浏览器窗口(window)和框架(iframe),描述了与浏览器进行交互的方法和接口,可以对浏览器窗口进行访问和操作,不过通常浏览器特性的javascript扩展都被看做是BOM的一部分,比如:弹出新的窗口,移动关闭浏览器窗口或调整浏览窗口大小,提供web浏览器详细信息的定位对象,提供屏幕分辨率详细信息的屏幕对象,对cookie的支持,IE扩展了BOM,加入了ActiveXObject类,可以通过JavaScript实例化ActiveX对象。(来源百度)

说人话来解析一篇:

以DOM为例来说(假设你知道DOM是什么),我们日常编写代码的时候无非就是增删改查DOM节点,而实现这些操作的就是浏览器内核给我的们document对象上的一系列方法,这些方法我们也通常叫做操作文档的接口。DOM这些操作的都是我们自己编写的html文档或者xml文档,那么BOM就是浏览器提供给我们操作浏览器的接口,毕竟浏览器不是我们自己编写的代码,操作不可能像DOM那样想删就删,想改就改,BOM提供的基本上都查询浏览下相关的信息的接口。(BOM的具体操作很多手册都有详细的文档,在第二部分我会列出几个重要的操作和一些兼容方法)

DOM与BOM的关系?W3C才是大哥!

在日常开发映像中,DOM与BOM好像是独立的两个模块,其实不然,DOM的document对象的缩写形式,但是在浏览器接口中没有browser...这个东西,但并不代表它没有存在,相反它是无所不在,BOM给JavaScript语言的浏览器接口就是window(不确定其他语音的接口是否一样,没有用过其他语言来写前端交互操作),就是这个全局对象,document是window的属性,所以这两者的关系就非常清楚了,DOM其实是BOM的一部分,只是因为DOM的重要性,我们通常将其独立出来解释。

那这里管W3C什么事呢?别搞笑,浏览器作为一款基于互联网的应用,你说W3C可不可以来说两句话呢?所以W3C做了一个非常英明的决定,制定了DOM的标准,你印象中的前端开发非常痛苦的一件事莫非就是兼容浏览器了,现如今的最新版浏览器基本上都兼容了W3C的DOM标准了,商业利益在时代的趋势前都是废铜烂铁,最终DOM被收服了。这个标准制定了标准文档对象和属性(HTML5就是产物之一),并制定了访问文档的接口;还包括构建核心DOM的标准模型,XML DOM标准模型,以及HTML DOM标准模型。

但是我们今天要详细了解的BOM,准确的说除了DOM以为的BOM部分,就没有那么幸运了,基本上都不兼容,毕竟这个世界如果每个人都长一样那会多么无趣。所以说它分裂有点悲观,应该是多样性,毕竟这个世界如此灿烂而美好。

 二、BOM包含的内容和常用接口

前面我们介绍了BOM重要的一员DOM,但是通常我们开发时表达的BOM是除了DOM以外的部分,所以这里所说的BOM也是除了DOM以外的部分。

  • window
  • Navigator
  • History
  • Location
  • Screen

 1.window中常用的属性和方法:

 1.1/innerHeight、innerWidth获取窗口的文档显示区域的高度。需要注意的是outerHeight、outerWidth这两个属性也是用来获取浏览器窗口的宽高,但这两个属性的宽高包含了滚动条的宽高。

 1.2/pageXOffset、pageYOffset设置或返回当前页面相对于窗口显示区左上角的X轴和Y轴的位置,通常也被称作是横向滚动条的最左侧的位置和纵向滚动条最顶端的位置。这两个属性出现比较多的应用场景就是懒加载(关于懒加载在网络部分会详细说明,下面有一个模拟demo)。

技术图片
//用定时器模拟懒加载,实践应用pageYOffset/innerHeight属性
//css
body{
    height: 3000px;
}
div{
    position: absolute;
    left: 200px;
    top: 1500px;
    width: 200px;
    height: 200px;
    background: red;
    
}
//html
<div id = "demo" style="opacity: 0.3;"></div>
//js
function check(_id){
    var oDiv = document.getElementById(_id);
    //几个关键属性:
    //element.offsetTop -->元素距离窗口页面的最上端的距离
    //window.pageYOffset -->纵向滚动条距离窗口的距离
    //window.innerHeight -->窗口可视高度
    if(oDiv.offsetTop <= window.pageYOffset + window.innerHeight){
        console.log("a");
        oDiv.timer = setInterval(function(){
            if(oDiv.style.opacity == ‘1‘){
                clearInterval(oDiv.timer);
            }else{
                oDiv.style.opacity = parseFloat(oDiv.style.opacity) + 0.1;
            }
        },100);
    }
}
//鼠标滑动事件
window.onscroll = function(){
    check(‘demo‘);
}
View Code

 1.3/screenLeft/screenTop/screenX/screenY返回浏览器相对于显示器最左侧和最上册的距离,这四个属性前后两个一组相对应的,表达的是同一个意思,只是在不同浏览器采用不同名称(注意兼容了)。

 1.4/parent返回父级窗口对象,这里涉及的内容是<iframe src=""></iframe>窗口嵌套,写在iframe内的子窗口也有自己的window对象,这个子窗口的window对象可以通过parent属性获取到父级窗口的window对象,从而达到获取父级窗口对象上的内容。但是没有父级窗口获取子级的window对象的属性和方法,这个涉及的内容与浏览器的网络相关内容,需要通过同源策略来解决。后期会有博客详细介绍。

 1.5/top返回最顶级窗口对象,这个属性与parent实质上如出一辙,parent通过逐级获取父级窗口,top直接获取的是最顶级窗口的window对象。

 1.6/self返回当前窗口的应用(window == window.self -->true)括号的表达式前提是window在相关作用域没有被修改指向的前提。

 1.7/name设置当前浏览器窗口的名称,这个名称不论当前窗口的页面或连接如何改变,name都不会变化。

(注意命名污染问题:parent、top、self、name在window上是特性,除name外是可写入的外,其他不能写,如果在全局作用遇上定义了这些变量可能会导致程序出错。)

1.8/下面是一些关于window的方法,处于版面控制,我用代码框折叠起来了(毕竟这些并不是这篇博客的重点)。

技术图片
//1.1-- alert() 弹窗 --用来
//1.2-- setInterval() 按照指定的周期(以毫秒计)来调用函数或计算表达。
//1.3-- clearInterval() 取消setInterval()方法设置的timeout。
//1.4-- setTimeout() 在指定的毫秒数后调用函数或计算表达式
//1.5-- clearTimeout() 取消setTimeout()方法设置的timenout
//1.6-- confirm("我帅不帅?") --弹出提示信息,带有"确定"、"取消"两个按钮,点击这两个按钮分别会返回true、false
//1.7-- prompt("hello world") -- 弹出输入框,参数是提示信息,输入的内容再确认后会作为返回值,如果取消则返回false
//   -- onbeforeunload 它不是window的方法,而是一个事件,也是弹窗功能,别误会了,离开页面时触发的。
//1.8-- scrollBy(x,y) 按照指定的像素值来滚动滚动条(对滚动条位置做加减操作) -- 数值可以为正负,正负关系就不解释了。
//1.9-- scrollTO(x,y) 安装指定的像素值来定位滚动条 -- (直接将滚动条定位到指定的位置)
//1.10- open(URL,name,specs,replace) 用来开启新窗口(独立的浏览器窗口或者当前浏览器窗口开启一个新的页面)
//        -- 只给url就是在当前浏览器窗口开启一个新的页面,给定宽高(特征值)就会开启一个新的浏览器窗口
//1.11- moveBy()可把窗口的当前坐标移动到指定位置,详细可参考手册:http://www.runoob.com/try/try.php?filename=try_win_moveby
//1.12- moveTO()将指定的窗口移动到相对当前窗口的位置,详细可参考手册:http://www.runoob.com/try/try.php?filename=try_win_moveto
View Code

 2.Navigator对象和方法

说明:navigator对象重要是提供浏览器信息,比如浏览器嗅探就是基于这个对象的userAgent属性来完成的。然后还有脱机判断的onLine判断是否脱机,cookieEnabled判断是否能使用cookie。

2.1封装浏览器嗅探对象

技术图片
var nVer = navigator.appVersion,
    nAgt = navigator.userAgent,
    browser = navigator.appName,
    version = ‘‘ + parseFloat(navigator.appVersion),
    majorVersion, nameOffset, verOffset, ix, network = ‘unknown‘;
// Opera
if ((verOffset = nAgt.indexOf(‘Opera‘)) != -1) {
    browser = ‘Opera‘;
    version = nAgt.substring(verOffset + 6);
    if ((verOffset = nAgt.indexOf(‘Version‘)) != -1) {
        version = nAgt.substring(verOffset + 8);
    }
}
// Opera Next 
if ((verOffset = nAgt.indexOf(‘OPR‘)) != -1) {
    browser = ‘Opera‘;
    version = nAgt.substring(verOffset + 4);
}
// MSIE
else if ((verOffset = nAgt.indexOf(‘MSIE‘)) != -1) {
    browser = ‘Microsoft Internet Explorer‘;
    version = nAgt.substring(verOffset + 5);
}
// Chrome
else if ((verOffset = nAgt.indexOf(‘Chrome‘)) != -1) {
    browser = ‘Chrome‘;
    version = nAgt.substring(verOffset + 7);
}
// Safari
else if ((verOffset = nAgt.indexOf(‘Safari‘)) != -1) {
    browser = ‘Safari‘;
    version = nAgt.substring(verOffset + 7);
    if ((verOffset = nAgt.indexOf(‘Version‘)) != -1) {
        version = nAgt.substring(verOffset + 8);
    }
}
// Firefox
else if ((verOffset = nAgt.indexOf(‘Firefox‘)) != -1) {
    browser = ‘Firefox‘;
    version = nAgt.substring(verOffset + 8);
}
// MSIE 11+
else if (nAgt.indexOf(‘Trident/‘) != -1) {
    browser = ‘Microsoft Internet Explorer‘;
    version = nAgt.substring(nAgt.indexOf(‘rv:‘) + 3);
}
// WeiXin
else if (nAgt.indexOf(‘NetType/‘) != -1) {
    browser = ‘WeiXin‘;
    if (nAgt.indexOf(‘NetType/WIFI‘) != -1) {
        network = ‘WIFI‘;
    }else if(nAgt.indexOf(‘NetType/2G‘) != -1) {
        network = ‘2G‘;
    }else if(nAgt.indexOf(‘NetType/3G+‘) != -1) {
        network = ‘3G+‘;
    }
    verOffset = nAgt.lastIndexOf(‘/‘)
    version = nAgt.substring(verOffset + 1);
    if (browser.toLowerCase() == browser.toUpperCase()) {
        browser = navigator.appName;
    }
}
// Other browsers
else if ((nameOffset = nAgt.lastIndexOf(‘ ‘) + 1) < (verOffset = nAgt.lastIndexOf(‘/‘))) {
    browser = nAgt.substring(nameOffset, verOffset);
    version = nAgt.substring(verOffset + 1);
    if (browser.toLowerCase() == browser.toUpperCase()) {
        browser = navigator.appName;
    }
}

// trim the version string
if ((ix = version.indexOf(‘;‘)) != -1) version = version.substring(0, ix);
if ((ix = version.indexOf(‘ ‘)) != -1) version = version.substring(0, ix);
if ((ix = version.indexOf(‘)‘)) != -1) version = version.substring(0, ix);
majorVersion = parseInt(‘‘ + version, 10);
if (isNaN(majorVersion)) {
    version = ‘‘ + parseFloat(navigator.appVersion);
    majorVersion = parseInt(navigator.appVersion, 10);
}

// mobile version
var mobile = /Mobile|mini|Fennec|android|iP(ad|od|hone)/.test(nVer);

// start system detect
var os = ‘‘;
var clientStrings = [
    {s: ‘Windows 10‘, r: /(Windows 10.0|Windows NT 10.0)/},
    {s: ‘Windows 8.1‘, r: /(Windows 8.1|Windows NT 6.3)/},
    {s: ‘Windows 8‘, r: /(Windows 8|Windows NT 6.2)/},
    {s: ‘Windows 7‘, r: /(Windows 7|Windows NT 6.1)/},
    {s: ‘Windows Vista‘, r: /Windows NT 6.0/},
    {s: ‘Windows Server 2003‘, r: /Windows NT 5.2/},
    {s: ‘Windows XP‘, r: /(Windows NT 5.1|Windows XP)/},
    {s: ‘Windows 2000‘, r: /(Windows NT 5.0|Windows 2000)/},
    {s: ‘Windows ME‘, r: /(Win 9x 4.90|Windows ME)/},
    {s: ‘Windows 98‘, r: /(Windows 98|Win98)/},
    {s: ‘Windows 95‘, r: /(Windows 95|Win95|Windows_95)/},
    {s: ‘Windows NT 4.0‘, r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},
    {s: ‘Windows CE‘, r: /Windows CE/},
    {s: ‘Windows 3.11‘, r: /Win16/},
    {s: ‘Android‘, r: /Android/},
    {s: ‘Open BSD‘, r: /OpenBSD/},
    {s: ‘Sun OS‘, r: /SunOS/},
    {s: ‘Linux‘, r: /(Linux|X11)/},
    {s: ios‘, r: /(iPhone|iPad|iPod)/},
    {s: ‘Mac OS X‘, r: /Mac OS X/},
    {s: ‘Mac OS‘, r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},
    {s: ‘QNX‘, r: /QNX/},
    {s: ‘UNIX‘, r: /UNIX/},
    {s: ‘BeOS‘, r: /BeOS/},
    {s: ‘OS/2‘, r: /OS/2/},
    {s: ‘Search Bot‘, r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves/Teoma|ia_archiver)/}
];
for (var id in clientStrings) {
    var cs = clientStrings[id];
    if (cs.r.test(nAgt)) {
        os = cs.s;
        break;
    }
}
var osVersion = ‘‘;
if (/Windows/.test(os)) {
    osVersion = /Windows (.*)/.exec(os)[1];
    os = ‘Windows‘;
}
switch (os) {
    case ‘Mac OS X‘:
        osVersion = /Mac OS X (10[.\_d]+)/.exec(nAgt)[1];
        break;
    case ‘Android‘:
        osVersion = /Android ([.\_d]+)/.exec(nAgt)[1];
        break;
    case ‘iOS‘:
        osVersion = /OS (d+)_(d+)_?(d+)?/.exec(nVer);
        osVersion = osVersion[1] + ‘.‘ + osVersion[2] + ‘.‘ + (osVersion[3] | 0);
        break;
}

//detect data
var params = {};
params.os = os;//操作系统
params.osVersion = osVersion ? osVersion : ‘unknown‘;//操作系统版本
params.mobile = mobile;//是否移动端访问
params.browser = browser;//浏览器
params.browserVersion = version;//浏览器版本
params.browserMajorVersion = majorVersion;//浏览器major版本

//输出对象
console.log(params);
View Code

这个代码量有点大,如果步理解浏览器嗅探是什么的话可以先查查资料,具体怎么用等到相关的内容再做说明。

2.2/onLine判断脱机主要用途就是用于离线缓存,实现离线应用。

3.History用于记录当前窗口的连接,可以通过该对象的方法实现历史访问页面的切换。

//属性:
length==>返回当前窗口浏览网页的历史次数
//方法:
back()==>加载history列表中的前一个url
forward()==>加载history列表中的下一个url
go()==>加载history列表中的某一个具体页面,提供索引作为参数

history在HTML5中有比较大的强化,具体到HTML5的内容中去说。

4.Location对象

location相对前面其他对象除了window以外是最有用的一个,比如通过锚点映射实现单页面应用就是在移动web开发中占有非常大地位。还有通过Location.Reload()方法重新载入当前文档,通过boolean值来实现是否绕开缓存重新加载当前页面,又或者是取浏览器的缓存的数据(这对于网络优化和交互体验有非常的地位)。(服务器可以通过报文定义当前页面在浏览器是否缓存,缓存多长时间时间,这些内容都会在网络博客剖析)

考虑到这部分有太多内容需要衍生,就简单的对location做一些引导性说明,后期在具体的博客中来阐述。

 三、重新认识浏览器

为什么我会在这里来聊浏览器呢?又或者说为什么需要在这里聊浏览器呢?还或者说我为什么要就着BOM的内容来引述浏览器呢?这是因为在前端开发中,我们通常理解的知识架构是HTML,CSS,JavaScript,这么说没有错,最多再在这个知识点上加一个ES6和JS的兼容性问题,这么说还是没有错,但是这么说的话我们就忽略了在我们前端开发中有着举足轻重的一个存在,那就是浏览器,没有浏览器那来我们的web前端开发,这是一个面向饭碗的话题。但是,这其中给值得我们思考的是你是否是一个合格web前端开发攻城狮,摆在你面前的永远是优化,因为前端承载的是用户体验的核心,首屏加载在2018年几乎成为年度总结的核心话题,而这些是离不开浏览器的技术问题,网络如何优化,渲染优化,资源加载优化等等问题,都是围绕浏览器来进行的。

刚刚开始学习浏览器时所理解的代码就是js操作html,这是一个初学者的认知,如果要想说进入前端工作,个人认为有必要对浏览器有一个系统的理解,JS操作DOM或许会占用开发的80%时间,但是JS操作浏览器接口几乎关系到一个前端攻城的生死存亡,最终可能会决定项目的存亡,有兴趣可以去了解以下亚马逊对首屏加载与用户体验的数据分析,这里就不多说了,百度以下就知道了。

这部分的话题核心就是网页的加载和渲染,这两个核心问题就是浏览器内核的工作原理的核心,内核的各个模块在BOM中给我预留了很多接口,但是这些接口可操作的还只能算是浏览器工作的九牛一毛,浏览器的内部封闭就要求我们只能按照浏览器的工作原理来优化我们自己的代码,从而实现极致的体验。而这些就会围绕浏览器以下的几个重要的工作部分:

  • 资源加载和网络栈
  • HTML解析和DOM模型
  • CSS解析器和样式布局
  • 渲染树
  • 硬件加速机制与js引擎(V8引擎)
  • 移动端渲染机制

 后面将就着这些问题对浏览器相关内容逐一阐述,本人也还在学习阶段,这些内容一方面是学习总结,另一方面是为了学习有清晰的目标和思路,写博客会让我对涉及到的知识点需要清晰的认识和理解,同时也可以表达自己的疑惑,相互学习,如果有不正确的内容还请不吝赐教。

以上是关于BOM:浏览器对象模型之浏览器剖析入门的主要内容,如果未能解决你的问题,请参考以下文章

前端入门06——BOM与DOM

JS之BOM与DOM

javascript-之-BOM 浏览器对象模型( BOM 的核心--window)

JavaScript进阶知识(BOM)-快速入门篇

JS学习笔记8之 BOM-浏览器对象模型

JavaScript之 ------ 浏览器对象模型 (BOM)