一、异步操作
1. 概述
单线程模型
单线程模型指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。
事件循环机制
CPU 完全可以不管 IO 操作,挂起处于等待中的任务,先运行排在后面的任务。等到 IO 操作返回了结果,再回过头,把挂起的任务继续执行下去。这种机制就是 JavaScript 内部采用的“事件循环”机制(Event Loop)。
同步任务和异步任务
举例来说,Ajax 操作可以当作同步任务处理,也可以当作异步任务处理,由开发者决定。如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。
任务队列和事件循环
JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。
引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)。
维基百科的定义是:“事件循环是一个程序结构,用于等待和发送消息和事件(a programming construct that waits for and dispatches events or messages in a program)”。
异步操作的模式
- 回调函数
- 事件监听
- 发布 / 订阅
- 这种方法的性质与“事件监听”类似,但是明显优于后者。
- 因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
异步操作的流程控制
- 串行执行
- 并行执行
- 并行与串行的结合
2. 定时器
JavaScript 提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()和setInterval()这两个函数来完成。它们向任务队列添加定时任务。
setTimeout()
// setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。// 它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。var timerId = setTimeout(func|code, delay);console.log(1);setTimeout('console.log(2)',1000);console.log(3);// 1// 3// 2setTimeout(f)// 等同于setTimeout(f, 0)// 除了前两个参数,setTimeout还允许更多的参数。它们将依次传入推迟执行的函数(回调函数)。setTimeout(function (a,b) {console.log(a + b);}, 1000, 1, 1);// 还有一个需要注意的地方,如果回调函数是对象的方法// 那么setTimeout使得方法内部的this关键字指向全局环境,而不是定义时所在的那个对象。var x = 1;var obj = {x: 2,y: function () {console.log(this.x);}};setTimeout(obj.y, 1000) // 1
setInterval()
// setInterval函数的用法与setTimeout完全一致,// 区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。var i = 1var timer = setInterval(function() {console.log(2);}, 1000)// 与setTimeout一样,除了前两个参数,setInterval方法还可以接受更多的参数,它们会传入回调函数。var div = document.getElementById('someDiv');var opacity = 1;var fader = setInterval(function() {opacity -= 0.1;if (opacity >= 0) {div.style.opacity = opacity;} else {clearInterval(fader);}}, 100);// 上面代码每隔100毫秒,设置一次div元素的透明度,直至其完全透明为止。// setInterval的一个常见用途是实现轮询。下面是一个轮询 URL 的 Hash 值是否发生变化的例子。var hash = window.location.hash;var hashWatcher = setInterval(function() {if (window.location.hash != hash) {updatePage();}}, 1000);
clearTimeout(),clearInterval()
// setTimeout和setInterval函数,都返回一个整数值,表示计数器编号。// 将该整数传入clearTimeout和clearInterval函数,就可以取消对应的定时器。var id1 = setTimeout(f, 1000);var id2 = setInterval(f, 1000);clearTimeout(id1);clearInterval(id2);
运行机制
setTimeout和setInterval的运行机制,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。
应用
// setTimeout(f, 0)有几个非常重要的用途。它的一大应用是,可以调整事件的发生顺序。// 比如,网页开发中,某个事件先发生在子元素,然后冒泡到父元素即子元素的事件回调函数// 会早于父元素的事件回调函数触发。如果,想让父元素的事件回调函数先发生,就要用到setTimeout(f, 0)。// HTML 代码如下// <input type="button" id="myButton" value="click">var input = document.getElementById('myButton');input.onclick = function A() {setTimeout(function B() {input.value +=' input';}, 0)};document.body.onclick = function C() {input.value += ' body'};// 另一个应用是,用户自定义的回调函数,通常在浏览器的默认动作之前触发。// 比如,用户在输入框输入文本,keypress事件会在浏览器接收文本之前触发。因此,下面的回调函数是达不到目的的。// HTML 代码如下// <input type="text" id="input-box">document.getElementById('input-box').onkeypress = function (event) {this.value = this.value.toUpperCase();}// 上面代码想在用户每次输入文本后,立即将字符转为大写。// 但是实际上,它只能将本次输入前的字符转为大写,因为浏览器此时还没接收到新的文本// 所以this.value取不到最新输入的那个字符。只有用setTimeout改写,上面的代码才能发挥作用。document.getElementById('input-box').onkeypress = function() {var self = this;setTimeout(function() {self.value = self.value.toUpperCase();}, 0);}上面代码将代码放入setTimeout之中,就能使得它在浏览器接收到文本之后触发。
由于setTimeout(f, 0)实际上意味着,将任务放到浏览器最早可得的空闲时段执行,所以那些计算量大、耗时长的任务,常常会被放到几个小部分,分别放到setTimeout(f, 0)里面执行。
var div = document.getElementsByTagName('div')[0];// 写法一for (var i = 0xA00000; i < 0xFFFFFF; i++) {div.style.backgroundColor = '#' + i.toString(16);}// 写法二var timer;var i=0x100000;function func() {timer = setTimeout(func, 0);div.style.backgroundColor = '#' + i.toString(16);if (i++ == 0xFFFFFF) clearTimeout(timer);}timer = setTimeout(func, 0);
3. Promise 对象
Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise 可以让异步操作写起来,就像在写同步操作的流程,而不必一层层地嵌套回调函数。
// 传统写法step1(function (value1) {step2(value1, function(value2) {step3(value2, function(value3) {step4(value3, function(value4) {// ...});});});});// Promise 的写法(new Promise(step1)).then(step2).then(step3).then(step4);
Promise 对象的状态
Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。* 异步操作未完成(pending)* 异步操作成功(fulfilled)* 异步操作失败(rejected)上面三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。因此,Promise 的最终结果只有两种。异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。
Promise 构造函数
// JavaScript 提供原生的Promise构造函数,用来生成 Promise 实例。var promise = new Promise(function (resolve, reject) {// ...if (/* 异步操作成功 */){resolve(value);} else { /* 异步操作失败 */reject(new Error());}});// 上面代码中,Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。// 它们是两个函数,由 JavaScript 引擎提供,不用自己实现。// resolve函数的作用是,将Promise实例的状态从“未完成”变为“成功”(即从pending变为fulfilled)// 在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。// reject函数的作用是,将Promise实例的状态从“未完成”变为“失败”(即从pending变为rejected)// 在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。function timeout(ms) {return new Promise((resolve, reject) => {setTimeout(resolve, ms, 'done');});}timeout(100)// 上面代码中,timeout(100)返回一个 Promise 实例。100毫秒以后,该实例的状态会变为fulfilled。
Promise.prototype.then()
// Promise 实例的then方法,用来添加回调函数。var p1 = new Promise(function (resolve, reject) {resolve('成功');});p1.then(console.log, console.error);// "成功"var p2 = new Promise(function (resolve, reject) {reject(new Error('失败'));});p2.then(console.log, console.error);// Error: 失败// then方法可以链式使用。p1.then(step1).then(step2).then(step3).then(console.log,console.error);// 上面代码中,p1后面有四个then,意味依次有四个回调函数。// 只要前一步的状态变为fulfilled,就会依次执行紧跟在后面的回调函数。// 最后一个then方法,回调函数是console.log和console.error,用法上有一点重要的区别。// console.log只显示step3的返回值,而console.error可以显示p1、step1、step2、step3之中任意一个发生的错误。// 举例来说,如果step1的状态变为rejected,那么step2和step3都不会执行了(因为它们是resolved的回调函数)。// Promise 开始寻找,接下来第一个为rejected的回调函数,在上面代码中是console.error。// 这就是说,Promise 对象的报错具有传递性。
then() 用法辨析
// 写法一f1().then(function () {return f2();});// 写法二f1().then(function () {f2();});// 写法三f1().then(f2());// 写法四f1().then(f2);
// 为了便于讲解,下面这四种写法都再用then方法接一个回调函数f3。// 写法一的f3回调函数的参数,是f2函数的运行结果。f1().then(function () {return f2();}).then(f3);// 写法二的f3回调函数的参数是undefined。f1().then(function () {f2();return;}).then(f3);// 写法三的f3回调函数的参数,是f2函数返回的函数的运行结果。f1().then(f2()).then(f3);// 写法四与写法一只有一个差别,那就是f2会接收到f1()返回的结果。f1().then(f2).then(f3);
实例:图片加载
var preloadImage = function (path) {return new Promise(function (resolve, reject) {var image = new Image();image.onload = resolve;image.onerror = reject;image.src = path;});};preloadImage('https://example.com/my.jpg').then(function (e) { document.body.append(e.target) }).then(function () { console.log('加载成功') })
小结
Promise 的优点在于,让回调函数变成了规范的链式写法,程序流程可以看得很清楚。它有一整套接口,可以实现许多强大的功能,比如同时执行多个异步操作,等到它们的状态都改变以后,再执行一个回调函数;再比如,为多个回调函数中抛出的错误,统一指定处理方法等等。
而且,Promise 还有一个传统写法没有的好处:它的状态一旦改变,无论何时查询,都能得到这个状态。这意味着,无论何时为 Promise 实例添加回调函数,该函数都能正确执行。所以,你不用担心是否错过了某个事件或信号。如果是传统写法,通过监听事件来执行回调函数,一旦错过了事件,再添加回调函数是不会执行的。
Promise 的缺点是,编写的难度比传统写法高,而且阅读代码也不是一眼可以看懂。你只会看到一堆then,必须自己在then的回调函数里面理清逻辑。
微任务
// Promise 的回调函数属于异步任务,会在同步任务之后执行。new Promise(function (resolve, reject) {resolve(1);}).then(console.log);console.log(2);// 2// 1// 上面代码会先输出2,再输出1。因为console.log(2)是同步任务,而then的回调函数属于异步任务// 一定晚于同步任务执行。// 但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。// 它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。// 这意味着,微任务的执行时间一定早于正常任务。setTimeout(function() {console.log(1);}, 0);new Promise(function (resolve, reject) {resolve(2);}).then(console.log);console.log(3);// 3// 2// 1
二、DOM
1. 概述
DOM
DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。
浏览器会根据 DOM 模型,将结构化文档(比如 HTML 和 XML)解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口。
DOM 只是一个接口规范,可以用各种语言实现。所以严格地说,DOM 不是 JavaScript 语法的一部分,但是 DOM 操作是 JavaScript 最常见的任务,离开了 DOM,JavaScript 就无法控制网页。另一方面,JavaScript 也是最常用于 DOM 操作的语言。后面介绍的就是 JavaScript 对 DOM 标准的实现和用法。
节点的类型有七种。
Document:整个文档树的顶层节点DocumentType:doctype标签(比如<!DOCTYPE html>)Element:网页的各种HTML标签(比如<body>、<a>等)Attr:网页元素的属性(比如class="right")Text:标签之间或标签包含的文本Comment:注释DocumentFragment:文档的片段// 浏览器提供一个原生的节点对象Node,上面这七种节点都继承了Node,因此具有一些共同的属性和方法。
节点树
// 浏览器原生提供document节点,代表整个文档。document// 整个文档树文档的第一层有两个节点,第一个是文档类型节点(<!doctype html>)第二个是 HTML 网页的顶层容器标签<html>。后者构成了树结构的根节点(root node)其他 HTML 标签节点都是它的下级节点。除了根节点,其他节点都有三种层级关系。父节点关系(parentNode):直接的那个上级节点子节点关系(childNodes):直接的下级节点同级节点关系(sibling):拥有同一个父节点的节点DOM 提供操作接口,用来获取这三种关系的节点。比如,子节点接口包括firstChild(第一个子节点)和lastChild(最后一个子节点)等属性同级节点接口包括nextSibling(紧邻在后的那个同级节点)和previousSibling(紧邻在前的那个同级节点)属性。
2. Node 接口
属性
// 14 个Node.prototype.nodeTypeNode.prototype.nodeNameNode.prototype.nodeValueNode.prototype.textContentNode.prototype.baseURINode.prototype.ownerDocumentNode.prototype.nextSiblingNode.prototype.previousSiblingNode.prototype.parentNodeNode.prototype.parentElementNode.prototype.firstChild,Node.prototype.lastChildNode.prototype.childNodesNode.prototype.isConnectednodeType属性返回一个整数值,表示节点的类型。不同节点的nodeType属性值和对应的常量如下。文档节点(document):9,对应常量Node.DOCUMENT_NODE元素节点(element):1,对应常量Node.ELEMENT_NODE属性节点(attr):2,对应常量Node.ATTRIBUTE_NODE文本节点(text):3,对应常量Node.TEXT_NODE文档片断节点(DocumentFragment):11,对应常量Node.DOCUMENT_FRAGMENT_NODE文档类型节点(DocumentType):10,对应常量Node.DOCUMENT_TYPE_NODE注释节点(Comment):8,对应常量Node.COMMENT_NODEnodeName属性返回节点的名称。nodeValue属性返回一个字符串,表示当前节点本身的文本值,该属性可读写。* 只有文本节点(text)、注释节点(comment)和属性节点(attr)有文本值textContent属性返回当前节点和它的所有后代节点的文本内容。* textContent属性自动忽略当前节点内部的 HTML 标签,返回所有文本内容。baseURI属性返回一个字符串,表示当前网页的绝对路径。浏览器根据这个属性,计算网页上的相对路径的 URL。该属性为只读。ownerDocument属性返回当前节点所在的顶层文档对象,即document对象。* document对象本身的ownerDocument属性,返回null。nextSibling属性返回紧跟在当前节点后面的第一个同级节点。如果当前节点后面没有同级节点,则返回null。previousSibling属性返回当前节点前面的、距离最近的一个同级节点。如果当前节点前面没有同级节点,则返回null。parentNode属性返回当前节点的父节点。对于一个节点来说,它的父节点只可能是三种类型:元素节点(element)、文档节点(document)和文档片段节点(documentfragment)。* 文档节点(document)和文档片段节点(documentfragment)的父节点都是null。另外,对于那些生成后还没插入 DOM 树的节点,父节点也是null。parentElement属性返回当前节点的父元素节点。如果当前节点没有父节点,或者父节点类型不是元素节点,则返回null。firstChild属性返回当前节点的第一个子节点,如果当前节点没有子节点,则返回null。* 注意,firstChild返回的除了元素节点,还可能是文本节点或注释节点。lastChild属性返回当前节点的最后一个子节点,如果当前节点没有子节点,则返回null。用法与firstChild属性相同。childNodes属性返回一个类似数组的对象(NodeList集合),成员包括当前节点的所有子节点。* 由于NodeList对象是一个动态集合,一旦子节点发生变化,立刻会反映在返回结果之中。isConnected属性返回一个布尔值,表示当前节点是否在文档之中。
方法
Node.prototype.appendChild()Node.prototype.hasChildNodes()Node.prototype.cloneNode()Node.prototype.insertBefore()Node.prototype.removeChild()Node.prototype.replaceChild()Node.prototype.contains()Node.prototype.compareDocumentPosition()Node.prototype.isEqualNode(),Node.prototype.isSameNode()Node.prototype.normalize()Node.prototype.getRootNode()appendChild()方法接受一个节点对象作为参数,将其作为最后一个子节点,插入当前节点。该方法的返回值就是插入文档的子节点。* 如果参数节点是 DOM 已经存在的节点,appendChild()方法会将其从原来的位置,移动到新位置。* 如果appendChild()方法的参数是DocumentFragment节点,那么插入的是DocumentFragment的所有子节点,而不是DocumentFragment节点本身。返回值是一个空的DocumentFragment节点。hasChildNodes方法返回一个布尔值,表示当前节点是否有子节点。判断一个节点有没有子节点,有许多种方法,下面是其中的三种。node.hasChildNodes()node.firstChild !== nullnode.childNodes && node.childNodes.length > 0cloneNode方法用于克隆一个节点。它接受一个布尔值作为参数,表示是否同时克隆子节点。它的返回值是一个克隆出来的新节点。该方法有一些使用注意点。(1)克隆一个节点,会拷贝该节点的所有属性,但是会丧失addEventListener方法和on-属性(即node.onclick = fn),添加在这个节点上的事件回调函数。(2)该方法返回的节点不在文档之中,即没有任何父节点,必须使用诸如Node.appendChild这样的方法添加到文档之中。(3)克隆一个节点之后,DOM 有可能出现两个有相同id属性(即id="xxx")的网页元素,这时应该修改其中一个元素的id属性。如果原节点有name属性,可能也需要修改。insertBefore方法用于将某个节点插入父节点内部的指定位置。* 由于不存在insertAfter方法,如果新节点要插在父节点的某个子节点后面,可以用insertBefore方法结合nextSibling属性模拟。parent.insertBefore(s1, s2.nextSibling);removeChild方法接受一个子节点作为参数,用于从当前节点移除该子节点。返回值是移除的子节点。replaceChild方法用于将一个新的节点,替换当前节点的某一个子节点。contains方法返回一个布尔值,表示参数节点是否满足以下三个条件之一。参数节点为当前节点。参数节点为当前节点的子节点。参数节点为当前节点的后代节点。compareDocumentPosition方法的用法,与contains方法完全一致,返回一个六个比特位的二进制值,表示参数节点与当前节点的关系。isEqualNode方法返回一个布尔值,用于检查两个节点是否相等。所谓相等的节点,指的是两个节点的类型相同、属性相同、子节点相同。isSameNode方法返回一个布尔值,表示两个节点是否为同一个节点。normalize方法用于清理当前节点内部的所有文本节点(text)。它会去除空的文本节点,并且将毗邻的文本节点合并成一个,也就是说不存在空的文本节点,以及毗邻的文本节点。getRootNode()方法返回当前节点所在文档的根节点document,与ownerDocument属性的作用相同。* 该方法可用于document节点自身,这一点与document.ownerDocument不同。
3. NodeList 接口,HTMLCollection 接口
NodeList 接口
NodeList实例是一个类似数组的对象,它的成员是节点对象。通过以下方法可以得到NodeList实例。• Node.childNodes• document.querySelectorAll()等节点搜索方法注意,NodeList 实例可能是动态集合,也可能是静态集合• 目前,只有Node.childNodes返回的是一个动态集合,其他的 NodeList 都是静态集合。NodeList.prototype.lengthNodeList.prototype.forEach()NodeList.prototype.item()NodeList.prototype.keys(),NodeList.prototype.values(),NodeList.prototype.entries()length属性返回 NodeList 实例包含的节点数量。forEach方法用于遍历 NodeList 的所有成员。它接受一个回调函数作为参数,每一轮遍历就执行一次这个回调函数,用法与数组实例的forEach方法完全一致。item方法接受一个整数值作为参数,表示成员的位置,返回该位置上的成员。* 所有类似数组的对象,都可以使用方括号运算符取出成员。一般情况下,都是使用方括号运算符,而不使用item方法。NodeList.prototype.keys(),NodeList.prototype.values(),NodeList.prototype.entries()* 个方法都返回一个 ES6 的遍历器对象,可以通过for...of循环遍历获取每一个成员的信息。区别在于,keys()返回键名的遍历器,values()返回键值的遍历器,entries()返回的遍历器同时包含键名和键值的信息。
HTMLCollection 接口
HTMLCollection是一个节点对象的集合,只能包含元素节点(element),不能包含其他类型的节点。它的返回值是一个类似数组的对象,但是与NodeList接口不同,HTMLCollection没有forEach方法,只能使用for循环遍历。HTMLCollection实例都是动态集合,节点的变化会实时反映在集合中。如果元素节点有id或name属性,那么HTMLCollection实例上面,可以使用id属性或name属性引用该节点元素。如果没有对应的节点,则返回null。HTMLCollection.prototype.lengthHTMLCollection.prototype.item()HTMLCollection.prototype.namedItem()length属性返回HTMLCollection实例包含的成员数量。item方法接受一个整数值作为参数,表示成员的位置,返回该位置上的成员。* 一般情况下,总是使用方括号运算符。namedItem方法的参数是一个字符串,表示id属性或name属性的值,返回对应的元素节点。如果没有对应的节点,则返回null。
4. ParentNode 接口,ChildNode 接口
ParentNode 接口
如果当前节点是父节点,就会混入了(mixin)ParentNode接口。由于只有元素节点(element)、文档节点(document)和文档片段节点(documentFragment)拥有子节点因此只有这三类节点会拥有ParentNode接口。ParentNode.childrenParentNode.firstElementChildParentNode.lastElementChildParentNode.childElementCountParentNode.append(),ParentNode.prepend()children属性返回一个HTMLCollection实例,成员是当前节点的所有元素子节点。该属性只读。* children属性只包括元素子节点,不包括其他类型的子节点(比如文本子节点)。如果没有元素类型的子节点,返回值HTMLCollection实例的length属性为0。firstElementChild属性返回当前节点的第一个元素子节点。如果没有任何元素子节点,则返回null。lastElementChild属性返回当前节点的最后一个元素子节点,如果不存在任何元素子节点,则返回null。childElementCount属性返回一个整数,表示当前节点的所有元素子节点的数目。如果不包含任何元素子节点,则返回0。append方法为当前节点追加一个或多个子节点,位置是最后一个元素子节点的后面。* 该方法不仅可以添加元素子节点,还可以添加文本子节点。prepend方法为当前节点追加一个或多个子节点,位置是第一个元素子节点的前面。它的用法与append方法完全一致,也是没有返回值。
ChildNode 接口
如果一个节点有父节点,那么该节点就拥有了ChildNode接口。ChildNode.remove()ChildNode.before(),ChildNode.after()ChildNode.replaceWith()remove方法用于从父节点移除当前节点。before方法用于在当前节点的前面,插入一个或多个同级节点。两者拥有相同的父节点。replaceWith方法使用参数节点,替换当前节点。参数可以是元素节点,也可以是文本节点。
5. Document 节点
document节点对象代表整个文档,每张网页都有自己的document对象。window.document属性就指向这个对象。只要浏览器开始载入 HTML 文档,该对象就存在了,可以直接使用。
document对象有不同的办法可以获取。
- 正常的网页,直接使用document或window.document。
- iframe框架里面的网页,使用iframe节点的contentDocument属性。
- Ajax 操作返回的文档,使用XMLHttpRequest对象的responseXML属性。
- 内部节点的ownerDocument属性。
document对象继承了EventTarget接口和Node接口,并且混入(mixin)了ParentNode接口。这意味着,这些接口的方法都可以在document对象上调用。除此之外,document对象还有很多自己的属性和方法。
属性
快捷方式属性(1)document.defaultView(2)document.doctype(3)document.documentElement(4)document.body,document.head(5)document.scrollingElement(6)document.activeElement(7)document.fullscreenElement节点集合属性(1)document.links(2)document.forms(3)document.images(4)document.embeds,document.plugins(5)document.scripts(6)document.styleSheets文档静态信息属性(1)document.documentURI,document.URL(2)document.domain(3)document.location(4)document.lastModified(5)document.title(6)document.characterSet(7)document.referrer(8)document.dir(9)document.compatMode文档状态属性(1)document.hidden(2)document.visibilityState(3)document.readyStatedocument.cookiedocument.designModedocument.currentScriptdocument.implementation快捷方式属性,以下属性指向文档内部的某个节点的快捷方式。document.defaultView属性返回document对象所属的window对象。如果当前文档不属于window对象,该属性返回null。document.doctype,指向<DOCTYPE>节点,即文档类型(Document Type Declaration,简写DTD)节点。document.documentElement属性返回当前文档的根元素节点(root)。document.body属性指向<body>节点,document.head属性指向<head>节点。document.scrollingElement属性返回文档的滚动元素。也就是说,当文档整体滚动时,到底是哪个元素在滚动。document.activeElement属性返回获得当前焦点(focus)的 DOM 元素。通常,这个属性返回的是<input>、<textarea>、<select>等表单元素,如果当前没有焦点元素,返回<body>元素或null。document.fullscreenElement属性返回当前以全屏状态展示的 DOM 元素。如果不是全屏状态,该属性返回null。节点集合属性document.links属性返回当前文档所有设定了href属性的<a>及<area>节点。document.forms属性返回所有<form>表单节点。document.images属性返回页面所有<img>图片节点。document.embeds属性和document.plugins属性,都返回所有<embed>节点。document.scripts属性返回所有<script>节点。document.styleSheets属性返回文档内嵌或引入的样式表集合除了document.styleSheets,以上的集合属性返回的都是HTMLCollection实例。文档静态信息属性document.documentURI属性和document.URL属性都返回一个字符串,表示当前文档的网址。* 不同之处是它们继承自不同的接口,documentURI继承自Document接口,可用于所有文档;URL继承自HTMLDocument接口,只能用于 HTML 文档。document.domain属性返回当前文档的域名,不包含协议和端口。Location对象是浏览器提供的原生对象,提供 URL 相关的信息和操作方法。通过window.location和document.location属性,可以拿到这个对象。document.lastModified属性返回一个字符串,表示当前文档最后修改的时间。不同浏览器的返回值,日期格式是不一样的。document.title属性返回当前文档的标题。默认情况下,返回<title>节点的值。document.characterSet属性返回当前文档的编码,比如UTF-8、ISO-8859-1等等。document.referrer属性返回一个字符串,表示当前文档的访问者来自哪里。document.dir返回一个字符串,表示文字方向。它只有两个可能的值:rtl表示文字从右到左,阿拉伯文是这种方式;ltr表示文字从左到右,包括英语和汉语在内的大多数文字采用这种方式。compatMode属性返回浏览器处理文档的模式,可能的值为BackCompat(向后兼容模式)和CSS1Compat(严格模式)。文档状态属性document.hidden属性返回一个布尔值,表示当前页面是否可见。如果窗口最小化、浏览器切换了 Tab,都会导致导致页面不可见,使得document.hidden返回true。document.visibilityState返回文档的可见状态。它的值有四种可能。* visible:页面可见。注意,页面可能是部分可见,即不是焦点窗口,前面被其他窗口部分挡住了。* hidden:页面不可见,有可能窗口最小化,或者浏览器切换到了另一个 Tab。* prerender:页面处于正在渲染状态,对于用户来说,该页面不可见。* unloaded:页面从内存里面卸载了。document.readyState属性返回当前文档的状态,共有三种可能的值。* loading:加载 HTML 代码阶段(尚未完成解析)* interactive:加载外部资源阶段* complete:加载完成另外,每次状态变化都会触发一个readystatechange事件。document.cookie属性用来操作浏览器 Cookie,详见《浏览器模型》部分的《Cookie》章节。document.designMode属性控制当前文档是否可编辑。该属性只有两个值on和off,默认值为off。一旦设为on,用户就可以编辑整个文档的内容。document.currentScript属性只用在<script>元素的内嵌脚本或加载的外部脚本之中,返回当前脚本所在的那个 DOM 节点,即<script>元素的 DOM 节点。document.implementation属性返回一个DOMImplementation对象。该对象有三个方法,主要用于创建独立于当前文档的新的 Document 对象。* DOMImplementation.createDocument():创建一个 XML 文档。* DOMImplementation.createHTMLDocument():创建一个 HTML 文档。* DOMImplementation.createDocumentType():创建一个 DocumentType 对象。
方法
document.open(),document.close()document.write(),document.writeln()document.querySelector(),document.querySelectorAll()document.getElementsByTagName()document.getElementsByClassName()document.getElementsByName()document.getElementById()document.elementFromPoint(),document.elementsFromPoint()document.createElement()document.createTextNode()document.createAttribute()document.createComment()document.createDocumentFragment()document.createEvent()document.addEventListener(),document.removeEventListener(),document.dispatchEvent()document.hasFocus()document.adoptNode(),document.importNode()document.createNodeIterator()document.createTreeWalker()document.execCommand(),document.queryCommandSupported(),document.queryCommandEnabled()document.getSelection()document.open方法清除当前文档所有内容,使得文档处于可写状态,供document.write方法写入内容。document.close方法用来关闭document.open()打开的文档。document.write方法用于向当前文档写入内容。现在完全有更符合标准的方法向文档写入内容(比如对innerHTML属性赋值)。所以,除了某些特殊情况,应该尽量避免使用document.write这个方法。document.writeln方法与write方法完全一致,除了会在输出内容的尾部添加换行符。document.querySelector方法接受一个 CSS 选择器作为参数,返回匹配该选择器的元素节点。如果有多个节点满足匹配条件,则返回第一个匹配的节点。如果没有发现匹配的节点,则返回null。document.querySelectorAll方法与querySelector用法类似,区别是返回一个NodeList对象,包含所有匹配给定选择器的节点。querySelectorAll的返回结果不是动态集合,不会实时反映元素节点的变化document.getElementsByTagName()方法搜索 HTML 标签名,返回符合条件的元素。它的返回值是一个类似数组对象(HTMLCollection实例),可以实时反映 HTML 文档的变化。如果没有任何匹配的元素,就返回一个空集。document.getElementsByClassName()方法返回一个类似数组的对象(HTMLCollection实例),包括了所有class名字符合指定条件的元素,元素的变化实时反映在返回结果中。document.getElementsByName()方法用于选择拥有name属性的 HTML 元素(比如<form>、<radio>、<img>、<frame>、<embed>和<object>等),返回一个类似数组的的对象(NodeList实例),因为name属性相同的元素可能不止一个。document.getElementById()方法返回匹配指定id属性的元素节点。如果没有发现匹配的节点,则返回null。document.elementFromPoint()方法返回位于页面指定位置最上层的元素节点。document.createElement方法用来生成元素节点,并返回该节点。document.createTextNode方法用来生成文本节点(Text实例),并返回该节点。它的参数是文本节点的内容。这个方法可以确保返回的节点,被浏览器当作文本渲染,而不是当作 HTML 代码渲染。因此,可以用来展示用户的输入,避免 XSS 攻击。document.createAttribute方法生成一个新的属性节点(Attr实例),并返回它。document.createComment方法生成一个新的注释节点,并返回该节点。document.createDocumentFragment方法生成一个空的文档片段对象document.createEvent方法生成一个事件对象(Event实例),该对象可以被element.dispatchEvent方法使用,触发指定事件。document.addEventListener(),document.removeEventListener(),document.dispatchEvent()这三个方法用于处理document节点的事件。它们都继承自EventTarget接口,详细介绍参见《EventTarget 接口》一章document.hasFocus方法返回一个布尔值,表示当前文档之中是否有元素被激活或获得焦点。document.adoptNode方法将某个节点及其子节点,从原来所在的文档或DocumentFragment里面移除,归属当前document对象,返回插入后的新节点。插入的节点对象的ownerDocument属性,会变成当前的document对象,而parentNode属性是null。document.importNode方法则是从原来所在的文档或DocumentFragment里面,拷贝某个节点及其子节点,让它们归属当前document对象。拷贝的节点对象的ownerDocument属性,会变成当前的document对象,而parentNode属性是nulldocument.createNodeIterator方法返回一个子节点遍历器。document.createTreeWalker方法返回一个 DOM 的子树遍历器。document.execCommand(),document.queryCommandSupported(),document.queryCommandEnabled() 看不懂,看链接document.getSelection()这个方法指向window.getSelection(),参见window对象一节的介绍。
6. Element 节点
Element节点对象对应网页的 HTML 元素。每一个 HTML 元素,在 DOM 树上都会转化成一个Element节点对象
- Element对象继承了Node接口,因此Node的属性和方法在Element对象都存在。
实例属性
元素特性的相关属性(1)Element.id(2)Element.tagName(3)Element.dir(4)Element.accessKey(5)Element.draggable(6)Element.lang(7)Element.tabIndex(8)Element.title元素状态的相关属性(1)Element.hidden(2)Element.contentEditable,Element.isContentEditableElement.attributesElement.className,Element.classListclassList对象有下列方法* add():增加一个 class。* remove():移除一个 class。* contains():检查当前元素是否包含某个 class。* toggle():将某个 class 移入或移出当前元素。* item():返回指定索引位置的 class。* toString():将 class 的列表转为字符串。Element.datasetElement.innerHTMLElement.outerHTMLElement.clientHeight,Element.clientWidthElement.clientLeft,Element.clientTopElement.scrollHeight,Element.scrollWidthElement.scrollLeft,Element.scrollTopElement.offsetParentElement.offsetHeight,Element.offsetWidthElement.offsetLeft,Element.offsetTopElement.styleElement.children,Element.childElementCountElement.firstElementChild,Element.lastElementChildElement.nextElementSibling,Element.previousElementSibling
实例方法
属性相关方法 6 个getAttribute():读取某个属性的值getAttributeNames():返回当前元素的所有属性名setAttribute():写入属性值hasAttribute():某个属性是否存在hasAttributes():当前元素是否有属性removeAttribute():删除属性Element.querySelector()Element.querySelectorAll()Element.getElementsByClassName()Element.getElementsByTagName()Element.closest()Element.matches()事件相关方法 3 个Element.addEventListener():添加事件的回调函数Element.removeEventListener():移除事件监听函数Element.dispatchEvent():触发事件Element.scrollIntoView()Element.getBoundingClientRect()Element.getClientRects()Element.insertAdjacentElement()Element.insertAdjacentHTML(),Element.insertAdjacentText()Element.remove()Element.focus(),Element.blur()Element.click()
7. 属性操作
Element.attributes 属性元素对象有一个attributes属性,返回一个类似数组的动态对象,成员是该元素标签的所有属性节点对象属性的实时变化都会反映在这个节点对象上。属性节点对象有name和value属性,对应该属性的属性名和属性值,等同于nodeName属性和nodeValue属性。元素的标准属性HTML 元素的标准属性(即在标准中定义的属性),会自动成为元素节点对象的属性。有些 HTML 属性名是 JavaScript 的保留字,转为 JavaScript 属性时,必须改名。主要是以下两个。* for属性改为htmlFor* class属性改为className6 个属性操作的标准方法Element.getAttribute()Element.getAttributeNames()Element.setAttribute()Element.hasAttribute()Element.hasAttributes()Element.removeAttribute()dataset 属性
8. Text 节点和 DocumentFragment 节点
Text 节点的概念
- 文本节点(Text)代表元素节点(Element)和属性节点(Attribute)的文本内容。如果一个节点只包含一段文本,那么它就有一个文本子节点,代表该节点的文本内容。
- 通常我们使用父节点的firstChild、nextSibling等属性获取文本节点,或者使用Document节点的createTextNode方法创造一个文本节点。
- 浏览器原生提供一个Text构造函数。它返回一个文本节点实例。它的参数就是该文本节点的文本内容。
- 文本节点除了继承Node接口,还继承了CharacterData接口。Node接口的属性和方法请参考《Node 接口》一章,这里不再重复介绍了,以下的属性和方法大部分来自CharacterData接口。
Text 节点的属性
datawholeTextlengthnextElementSibling,previousElementSibling
Text 节点的方法
appendData(),deleteData(),insertData(),replaceData(),subStringData()remove()splitText()appendData():在Text节点尾部追加字符串。deleteData():删除Text节点内部的子字符串,第一个参数为子字符串开始位置,第二个参数为子字符串长度。insertData():在Text节点插入字符串,第一个参数为插入位置,第二个参数为插入的子字符串。replaceData():用于替换文本,第一个参数为替换开始位置,第二个参数为需要被替换掉的长度,第三个参数为新加入的字符串。subStringData():用于获取子字符串,第一个参数为子字符串在Text节点中的开始位置,第二个参数为子字符串长度。remove方法用于移除当前Text节点。splitText方法将Text节点一分为二,变成两个毗邻的Text节点。
DocumentFragment 节点
DocumentFragment节点代表一个文档的片段,本身就是一个完整的 DOM 树形结构。它没有父节点,parentNode返回null,但是可以插入任意数量的子节点。它不属于当前文档,操作DocumentFragment节点,要比直接操作 DOM 树快得多。
它一般用于构建一个 DOM 结构,然后插入当前文档。document.createDocumentFragment方法,以及浏览器原生的DocumentFragment构造函数,可以创建一个空的DocumentFragment节点。然后再使用其他 DOM 方法,向其添加子节点。
DocumentFragment节点对象没有自己的属性和方法,全部继承自Node节点和ParentNode接口。也就是说,DocumentFragment节点比Node节点多出以下四个属性。
- children:返回一个动态的HTMLCollection集合对象,包括当前DocumentFragment对象的所有子元素节点。
- firstElementChild:返回当前DocumentFragment对象的第一个子元素节点,如果没有则返回null。
- lastElementChild:返回当前DocumentFragment对象的最后一个子元素节点,如果没有则返回null。
- childElementCount:返回当前DocumentFragment对象的所有子元素数量。
9. CSS 操作
div.setAttribute('style','background-color:red;' + 'border:1px solid black;');e.style.fontSize = '18px';e.style.color = 'black';var divStyle = document.querySelector('div').style;divStyle.backgroundColor = 'red';divStyle.border = '1px solid black';divStyle.width = '100px';divStyle.height = '100px';divStyle.fontSize = '10em';divStyle.backgroundColor // reddivStyle.border // 1px solid blackdivStyle.height // 100pxdivStyle.width // 100px
var divStyle = document.querySelector('div').style;divStyle.cssText = 'background-color: red;'+ 'border: 1px solid black;'+ 'height: 100px;'+ 'width: 100px;';
// 一个比较普遍适用的方法是,判断元素的style对象的某个属性值是否为字符串。typeof element.style.animationName === 'string';typeof element.style.transform === 'string';document.body.style['maxWidth'] // ""document.body.style['maximumWidth'] // undefined
CSS.escape()CSS.supports()window.getComputedStyle()
// CSS 伪元素是通过 CSS 向 DOM 添加的元素,主要是通过:before和:after选择器生成// 然后用content属性指定伪元素的内容。var test = document.querySelector('#test');var result = window.getComputedStyle(test, ':before').content;var color = window.getComputedStyle(test, ':before').color;// 此外,也可以使用 CSSStyleDeclaration 实例的getPropertyValue方法,获取伪元素的属性。var result = window.getComputedStyle(test, ':before').getPropertyValue('content');var color = window.getComputedStyle(test, ':before').getPropertyValue('color');
// StyleSheet接口代表网页的一张样式表,包括<link>元素加载的样式表和<style>元素内嵌的样式表。var sheets = document.styleSheets;var sheet = document.styleSheets[0];sheet instanceof StyleSheet // true// HTML 代码为 <style id="myStyle"></style>var myStyleSheet = document.getElementById('myStyle').sheet;myStyleSheet instanceof StyleSheet // true
// StyleSheet实例有以下属性。(1)StyleSheet.disabled(2)Stylesheet.href(3)StyleSheet.media(4)StyleSheet.title(5)StyleSheet.type(6)StyleSheet.parentStyleSheet(7)StyleSheet.ownerNode(8)CSSStyleSheet.cssRules(9)CSSStyleSheet.ownerRule
// StyleSheet 的实例方法(1)CSSStyleSheet.insertRule()(2)CSSStyleSheet.deleteRule()
10 Mutation Observer API
Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。
概念上,它很接近事件,可以理解为 DOM 发生变动就会触发 Mutation Observer 事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发。
这样设计是为了应付 DOM 变动频繁的特点。举例来说,如果文档中连续插入1000个
元素,就会连续触发1000个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;而 Mutation Observer 完全不同,只在1000个段落都插入结束后才会触发,而且只触发一次。
Mutation Observer 有以下特点。
- 它等待所有脚本任务完成后,才会运行(即异步触发方式)。
- 它把 DOM 变动记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变动。
- 它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。
MutationObserver 构造函数
var observer = new MutationObserver(callback);var observer = new MutationObserver(function (mutations, observer) {mutations.forEach(function(mutation) {console.log(mutation);});});
MutationObserver 的实例方法
observe()方法用来启动监听,它接受两个参数。* 第一个参数:所要观察的 DOM 节点* 第二个参数:一个配置对象,指定所要观察的特定变动disconnect()方法用来停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器。takeRecords()方法用来清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组。
三、章节链接
「@浪里淘沙的小法师」
