浏览器内置对象
一、BOM 和 DOM
JavaScript 在浏览器环境下一般有三部分组成:ECMAScript、BOM、DOM,(IE 浏览器多了 ActiveXObject 类)。
浏览器环境是 JavaScript 的宿主环境,浏览器中的编译引擎:V8, JSCore, SpiderMoney
- ECMAScript:实现 JavaScript 的标准,它的核心描述了 js 的语法和基本对象,我们常说的 ES6 ,就是属于这个部分的内容。
- BOM:Browser Object Model,浏览器对象模型。BOM 顾名思义其实就是为了控制浏览器的行为而出现的接口。
- 浏览器的本质,就是接管了你开发的软件中,负责渲染 GUI 的部分。
- BOM 是浏览器中除了代码展示部分。
题外话:W3C、ECMAScript 以及 MDN 这些关系是什么?
W3C
- W3C 是一个非标准化的组织,最重要的工作是发展 Web 规范。
- 这些规范描述了 Web 的通信协议(比如 HTML 和 XHTML)和其他的构建模块。
- 平时我们接触到的标准,比如:超文本标记语言、HTML5 规范、事件规范,我们平时接触到 DOM 一级、二级规范都是该组织制定的。
- 标准制定后,就需要各方支持实现。得到了浏览器厂商的支持,所以会有一致的表现。
ECMAScript
- ECMAScript 是一种标准,这个标准主要用于标准化 JavaScript 这种语言的,比如我们平时使用的 es6、es7 等都是该标准的产物。该标准由 ECMA International 进行制定,TC39 委员会进行监督。
- 指定的标准有以下等:
- 语法:关键词,解析规则,流程控制,对象初始化,等等...
- 类型:数字,字符串,布尔值,对象,函数,等等...
- 全局对象:在浏览器环境中,这个全局对象就是 window 对象,但是 ECMAScript 只定义那些不特定于浏览器的 API(例如:parseInt、parseFloat、decodeURI、encodeURI,等等)
- 内置对象和函数:JSON、Math、Array.prototype方法、对象内省(自检,自我检查,introspection)方法,等等...
- 错误处理机制:throw,try...catch,以及创建用户定义错误类型的能力
- 基于原型的继承机制
- Strict mode
MDN
- MDN:全称 Mozilla Developer Network。
- 它和前面的 W3C 和 ECMAscript 不太一样,这个组织不是为了标准化而诞生的。在 MDN 的官网上的左上⻆写着 MDN web docs,很明显这是一个专为开发者服务的开发文档。
- 当然,W3C 和 ECMAScript 也有对应的文档,但是平时开发的过程中,大家都比较习惯用 MDN 去查询资料,主要是 MDN 做的也太好了,有各种比较容易理解的使用说明和兼容性说明等。
MSDN
- 全称为 microsoft developer network,开发在 IE 浏览器上运行的程序需要参考的文档。
- 鉴于平时 IE 支持到 11 后,很多属性都已经标准化了,所以平时用的比较少。
二、BOM
1. navigator
属性 | 说明 |
---|---|
appName | 返回浏览器的名称--Netscape |
appCodeName | 返回浏览器的代码名 |
appVersion | 返回浏览器的平台和版本信息 |
cookieEnabled | 返回指明浏览器中是否启用 cookie 的布 尔值 |
platform | 返回运行浏览器的操作系统平台,可以用于判断快捷键,例如:ctrl+s / command+s |
userAgent | 返回由客户机发送服务器的 user-agent 头部的值 |
onLine | 判断浏览器是否在线 |
connection | 自动检测网络状况切换清晰度 |
onLine 和 connection 的 demo
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="status"></div>
<div id="desc"></div>
<div id="condition"></div>
</body>
<script>
window.addEventListener('load', () => {
const statusDiv = document.getElementById('status')
const descDiv = document.getElementById('desc')
const conditionDiv = document.getElementById('condition')
// 监听离线和在线事件
window.addEventListener('online', handleLineChange)
window.addEventListener('offline', handleLineChange)
function handleLineChange(event) {
const status = navigator.onLine ? 'online' : 'offline'
statusDiv.innerHTML = status
descDiv.innerHTML = `Event: ${event.type}; Status:${status}`
}
const connection = navigator.connection
let type = connection.effectiveType
function updateConnectionCondition() {
conditionDiv.innerHTML = `Network Contidition transferred from ${type} to ${connection.effectiveType}`
type = connection.effectiveType
}
// 当网络状态发生变化
connection.addEventListener('change', updateConnectionCondition)
})
</script>
</html>
2. location
属性 | 描述 | 栗子 |
---|---|---|
hash | 锚点,简单来说就是 url 的#后面所用内容 | #detail |
host | 主机名+端口 | www.baidu.com:8080 |
hostname | 主机名 | <www.baidu.com> |
href | 完整路径 | <www.baidu.com?id=1#detail> |
pathname | host 后面,hash # 号前面的路径部分 | /index.html |
port | 端口 | 8080 |
protocol | 协议 | http: , https: |
search | 参数,? 之后的部分,注意不能放在 hash 的后面,否则读取不到 | ?id=1 |
origin | 协议+主机名+端口 | 只有它是只读的,其余均可写 |
方法 | 描述 |
---|---|
assign | 加载新的文档 |
reload | 重新刷新⻚面,相当于刷新按钮 |
replace | 用新的文档替换当前文档,移动设备检测时的立刻跳转 |
3. screen
属性 | 说明 |
---|---|
availHeight | 返回屏幕的高度(不包括 Windows 任务栏) |
availWidth | 返回屏幕的宽度(不包括 Windows 任务栏) |
colorDepth | 返回目标设备或缓冲器上的调色板的比特深度 |
height | 返回屏幕的总高度 |
pixelDepth | 返回屏幕的颜色分辨率(每象素的位数) |
width | 返回屏幕的总宽度 |
浏览器的各种宽度、高度总结
- window.innerWidth 和 window.innerHeight :文档显示区的宽度/高度包括滚动条,只读属性,无默认值。IE 8 及更早 IE 版本不支持这两个属性,可以使用 clientWidth 和 clientHeight 属性。
- window.outerWidth 和 window.outerHeight :窗口的外部高度,包括所有界面元素(如:tab 标签栏/收藏夹栏、工具栏/滚动条),只读属性,无默认值。
- Element.clientWidth 和 Element.clientHeight : width/height + padding,只读属性,内联元素以及没有 CSS 样式的元素的 clientWidth 属性值为 0。
- Element.offsetWidth 和 Element.offsetHeight : width/height + padding + border,包含滚动条
- Element.scrollWidth 和 Element.scrollHeight : width/height + padding + 视口之外的宽度/高度
4. history
属性/方法 | 说明 |
---|---|
length | 返回历史列表中的网址数 |
back() | 加载 history 列表中的前一个 URL |
forward() | 加载 history 列表中的下一个 URL |
go() | 加载 history 列表中的某个具体页面 |
pushState() | HTML5 新增的方法,不会触发 onpopstate |
replaceState()) | HTML5 新增的方法,不会触发 onpopstate |
HTML5 为 history
对象添加了两个新方法,history.pushState()
和 history.replaceState()
,用来在浏览历史中添加和修改记录。
js
if (!!(window.history && history.pushState)) {
// 支持 pushState
} else {
// 不支持
}
history.pusuState() 接收三个参数
-
state
:一个与指定网址相关的状态对象,popstate
事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null
, 设置state
后会表现在history.state
。 -
title
:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null
。 -
url
:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。假定当前网址是
example.com/1.html
,我们使用pushState
方法在浏览记录(history
对象)中添加一个新记录。jsvar stateObj = { foo: 'bar' } history.pushState(stateObj, 'page 2', '2.html')
添加上面这个新记录后,浏览器地址栏立刻显示
example.com/2.html
,但并不会跳转到2.html
,甚至也不会检查2.html
是否存在,它只是成为浏览历史中的最新记录。这时,你在地址栏输入一个新的地址(比如访问google.com
),然后点击了倒退按钮,页面的 URL 将显示2.html
;你再点击一次倒退按钮,URL 将显示1.html
。总之,
pushState
方法不会触发页面刷新,只是导致history
对象发生变化,地址栏会有反应。如果
pushState
的 url 参数,设置了一个新的锚点值(即hash
),并不会触发hashchange
事件。如果设置了一个跨域网址,则会报错。js// 报错 history.pushState(null, null, 'https://twitter.com/hello')
上面代码中,
pushState
想要插入一个跨域的网址,导致报错。这样设计的目的是,防止恶意代码让用户以为他们是在另一个网站上。
history.replaceState()
replaceState
方法的参数与 pushState
方法一模一样,区别是它修改浏览历史中当前纪录。即 history.length 的长度不会发生变化
假定当前网页是 example.com/example.html
。
js
history.pushState({ page: 1 }, 'title 1', '?page=1')
history.pushState({ page: 2 }, 'title 2', '?page=2')
history.replaceState({ page: 3 }, 'title 3', '?page=3')
history.back()
// url显示为http://example.com/example.html?page=1
history.back()
// url显示为http://example.com/example.html
history.go(2)
// url显示为http://example.com/example.html?page=3
popstate 事件
每当同一个文档的浏览历史(即 history
对象)出现变化时,就会触发 popstate
事件。
需要注意的是,仅仅调用 pushState
方法或 replaceState
方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用 back
、forward
、go
方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。
使用的时候,可以为 popstate
事件指定回调函数。这个回调函数的参数是一个 event
事件对象,它的 state
属性指向 pushState
和 replaceState
方法为当前 URL 所提供的状态对象(即这两个方法的第一个参数)。
js
window.onpopstate = function (event) {
console.log('location: ' + document.location)
console.log('state: ' + JSON.stringify(event.state))
}
// 或者
window.addEventListener('popstate', function (event) {
console.log('location: ' + document.location)
console.log('state: ' + JSON.stringify(event.state))
})
上面代码中的 event.state
,就是通过 pushState
和 replaceState
方法,为当前 URL 绑定的 state
对象。
这个 state
对象也可以直接通过 history
对象读取。
js
var currentState = history.state
注意,页面第一次加载的时候,在 load
事件发生后,Chrome 和 Safari 浏览器(Webkit 核心)会触发 popstate
事件,而 Firefox 和 IE 浏览器不会。
5. Window 对象属性/方法
属性 | 描述 |
---|---|
open() | 打开一个新的浏览器窗口或查找一个已命名的窗口。 |
opener | 返回对创建此窗口的窗口的引用。 |
focus() | 把键盘焦点给予一个窗口。 |
close() | 关闭浏览器窗口。 |
closed | 返回窗口是否已被关闭。 |
parent | 返回父窗口,与 opener 类似 |
print() | 打印当前窗口的内容。 |
postMessage() | 安全地实现跨源通信。 |
encodeURI | 会将元字符和语义字符外的全转码。encodeURI('http://www.baidu.com/query=百度') 转成 http://www.baidu.com/query=%E7%99%BE%E5%BA%A6 |
encodeURIComponent | 会将语义字符以外的全部转码,元字符也会转。encodeURIComponent('http://www.baidu.com/query=百度') 转成 http%3A%2F%2Fwww.baidu.com%2Fquery%3D%E7%99%BE%E5%BA%A6 |
devicePixelRatio | 物理、逻辑像素比。一般的显示器 72 像素/英寸。 |
open(), opener, close(), closed, focus() 应用 demo
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>open(),opener,close(),closed</title>
<script>
function openNewWindown() {
newWindown = window.open()
newWindown.document.write('<p>这是新的窗口!</p>')
newWindown.focus()
newWindown.opener.document.body.style.background = 'pink'
}
function closeNewWindown() {
alert(newWindown.closed)
newWindown.close()
alert(newWindown.closed)
}
</script>
</head>
<body>
<input type="button" value="打开一个新的窗口" onclick="openNewWindown()" />
<input type="button" value="关闭新打开的窗口" onclick="closeNewWindown()" />
</body>
</html>
三、DOM
1. 事件级别
- DOM 级别一共可以分为四个级别:DOM0 级、DOM1 级、DOM2 级、DOM3 级。
- 而 DOM 事件分为 3 个级别:DOM0 级事件处理,DOM2 级事件处理和 DOM3 级事件处理。
- 由于 DOM1 级中没有事件的相关内容,所以没有 DOM1 级事件。
DOM0
DOM0 不是 W3C 规范,而仅仅是对在 NetscapeNavigator 3.0 和 MicrosoftInternetExplorer 3.0 中的等价功能性的一种定义。
js
const btn = document.getElementById('btn')
btn.onclick = function () {
alert(this.innerHTML)
}
- 当希望为同一个元素/标签绑定多个同类型事件的时候(如给上面的这个 btn 元素绑定 3 个点击事件),是不被允许的。
- DOM0 事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的。
DOM1
DOM1 专注于 HTML 和 XML 文档模型。它含有文档导航和处理功能。
DOM2
- DOM2 对 DOM1 添加了样式表对象模型,并定义了操作附于文档之上的样式信息的功能性。
- DOM2 同时还定义了一个事件模型(Events,规定了访问文档事件的 API),并提供了对 XML 命名空间的支持。
js
const btn = document.getElementById('btn')
btn.addEventListener('click', test, false)
function test(e) {
e = e || window.event
alert((e.target || e.srcElement).innerHTML)
btn.removeEventListener('click', test)
}
// IE9-: attachEvent(), detachEvent()
// IE9+/chrom/FF: addEventListener(), removeEventListener()
DOM3
- DOM3 规定了内容模型(DTD 和 Schemas)和文档验证。
- 同时规定了文档加载和保存、文档查看、文档格式化和关键事件。
- 在 DOM 2 级事件的基础上添加了更多的事件类型。
- UI 事件,当用户与页面上的元素交互时触发,如:load、scroll
- 焦点事件,当元素获得或失去焦点时触发,如:blur、focus
- 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup
- 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
- 文本事件,当在文档中输入文本时触发,如:textInput
- 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
- 合成事件,当为 IME(输入法编辑器)输入字符时触发,如:compositionstart 变动事件,当底层 DOM 结构发生变化时触发,如:DOMsubtreeModified
- 同时 DOM3 级事件也允许使用者自定义一些事件。
2. DOM 事件模型和事件流
- DOM 事件模型分为捕获 和冒泡。
- 一个事件发生后,会在子元素和父元素之间传播。这种传播分成三个阶段:
- 捕获阶段:事件从 window 对象自上而下向目标节点传播的阶段;
- 目标阶段:真正的目标节点正在处理事件的阶段;
- 冒泡阶段:事件从目标节点自下而上向 window 对象传播的阶段。
事件捕获具体流程,事件冒泡流程正好相反
- addEventListener 的第三个参数,如果为 true,就是代表在捕获阶段执行。如果为 false,就是在冒泡阶段进行,默认为 false 。
e.preventDefault()
阻止默认事件,例如阻止 a 标签链接的跳转。IE 用e.returnValue = false
e.stopPropagation()
阻止事件捕获或者冒泡。IE 用e.cancelBubble = true
只能阻止冒泡e.stopImmediatePropagation()
不仅会阻止捕获或者冒泡,而且对于多个相同类型的事件的事件监听函数绑定同一个元素的情况,会在触发时阻止该元素的其他事件。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#parent {
width: 800px;
height: 800px;
background-color: antiquewhite;
}
#child {
width: 400px;
height: 400px;
background-color: aquamarine;
}
#son {
width: 200px;
height: 200px;
background-color: rgb(121, 216, 11);
}
.flex-center {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: large;
}
</style>
</head>
<body>
<div id="parent" class="flex-center">
parent
<div id="child" class="flex-center">
child
<div id="son" class="flex-center">
son
<a id="link" href="https://www.baidu.com/">www.baidu.com</a>
</div>
</div>
<script>
const parent = document.getElementById('parent');
const child = document.getElementById('child');
const son = document.getElementById('son');
window.addEventListener('click', (e) => {
console.log('window 捕获---', e.target.id, e.currentTarget.id)
}, true)
document.addEventListener('click', (e) => {
console.log('document 捕获---', e.target.id, e.currentTarget.id)
}, true)
document.querySelector('html').addEventListener('click', (e) => {
console.log('html 捕获---', e.target.id, e.currentTarget.id)
}, true)
document.body.addEventListener('click', (e) => {
console.log('body 捕获---', e.target.id, e.currentTarget.id)
}, true)
parent.addEventListener('click', (e) => {
console.log('parent 捕获---', e.target.id, e.currentTarget.id)
}, true)
child.addEventListener('click', (e) => {
console.log('child 捕获---', e.target.id, e.currentTarget.id)
}, true)
son.addEventListener('click', (e) => {
// e.stopPropagation() 可以阻止向下捕获
console.log('son 捕获---', e.target.id, e.currentTarget.id)
}, true)
link.addEventListener('click', (e) => {
console.log('link 捕获---', e.target.id, e.currentTarget.id)
console.log('--------------------------------------------')
}, true)
window.addEventListener('click', (e) => {
console.log('window 冒泡---', e.target.id, e.currentTarget.id)
})
document.addEventListener('click', (e) => {
console.log('document 冒泡---', e.target.id, e.currentTarget.id)
})
document.querySelector('html').addEventListener('click', (e) => {
console.log('html 冒泡---', e.target.id, e.currentTarget.id)
})
document.body.addEventListener('click', (e) => {
console.log('body 冒泡---', e.target.id, e.currentTarget.id)
})
parent.addEventListener('click', (e) => {
console.log('parent 冒泡---', e.target.id, e.currentTarget.id)
})
child.addEventListener('click', (e) => {
console.log('child 冒泡---', e.target.id, e.currentTarget.id)
})
son.addEventListener('click', (e) => {
console.log('son 冒泡---', e.target.id, e.currentTarget.id)
})
link.addEventListener('click', (e) => {
e.preventDefault();
console.log('e.preventDefault() 阻止了a标签的链接跳转')
e.stopPropagation();
console.log('e.stopPropagation() 阻止了a标签的事件冒泡')
e.stopImmediatePropagation()
console.log('e.stopImmediatePropagation() 不单单可以阻止事件冒泡,还可以阻止其他的绑定事件')
console.log('link 冒泡---', e.target.id, e.currentTarget.id)
})
link.addEventListener('click', (e) => {
console.log('这里被 e.stopImmediatePropagation() 阻止了,不会被打印')
})
</script>
</body>
</html>
3. 事件委托
js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
const ul = document.querySelector('ul')
ul.addEventListener('click', function (e) {
const { target } = e
if (target.tagName.toLowerCase() === 'li') {
const liList = this.querySelectorAll('li')
let index = Array.prototype.indexOf.call(liList, target)
alert(index)
}
})
</script>
</body>
</html>