Another RayJune

JavaScript DOM 编程艺术小记

关于 JavaScript 和 DOM 脚本编程的基本原则、良好习惯和正确思路
有关本书的代码放在 https://github.com/rayjune/Dom-Scripting

本书要点

讲解 DOM 编程技术背后的思路和原则:

  • 平稳退化
  • 渐进增强
  • 以用户为中心

精华在于作者在书中提到的关于 JavaScript 和 DOM 脚本编程的基本原则、良好习惯和正确思路

本书的真正目的是让大家理解 DOM 脚本编程技术背后的思路和原则

作者的话

只要运用得当,并且注意避开那些“经典的” JavaScript 陷阱,DOM 编程技术就可以成为 Web 开发工具箱里又一件功能强大甚至是不可或缺的好东西。

只要抓住了代码背后的概念,就会发现你是在用一种新语言去阅读和编写代码。

归根结底,代码都是思想和概念的体现

没人能把一种程序设计语言的所有语法和关键字都记住,如果有拿不准的地方,查阅参考书就全解决了

DOM define

DOM:给文档增加交互能力

简单的说,DOM 是一套对文档的内容进行抽象和概念化的方法

DOM 是一种 API (应用程序接口)。简单的说,API 就是一组已经得到有关各方共同认可的基本约定。在现实世界中,相当于 AIP 的例子包括 (但不局限于) 摩尔斯电码、国际时区、化学元素的周期表等。

W3C 对 DOM 的定义是: “一个与系统平台和编程语言无关的接口,程序和脚本可以通过这个接口动态地访问和修改文档的内容、结构和样式。”

DOM == 节点树

当创建了一个网页并把它加载到浏览器中,DOM 就在幕后悄然而生,它把你编写的网页文档转换为一个文档对象。

DOM 结点:

  • 元素结点
  • 属性结点
  • 文本结点(如 p 元素中间包裹的文本就是文本结点)

获取元素结点

有三种 DOM 方法可以获取元素结点,分别是通过元素 ID、标签名和类名来获取:

  • document.getElementById()
  • document.getElementsByTagName()
  • (H5 新增)document.getElementsByClassName(): 通过 class 类名来访问元素,也是返回一个类数组,且可以添加多个参数,用空格分开即可

文档中的每个元素结点都是一个对象。不仅如此,这些对象中的每一个还天生具有一系列非常有用的方法,这要归功于 DOM。利用这些预先定义好的方法,我们不仅可以检索出文档里任何一个对象的信息,甚至还可以改变元素的属性

获取和设置属性

与此前我们介绍过的那些方法不同,getAttribute/setAttribute 方法不属于 document 对象,所以不能通过 document 对象调用,只能通过元素结点调用

小结

  • 一份文档就是一棵节点树
  • 节点分为不同的类型:元素结点,属性节点,文本节点
  • 每个节点都是一个对象,配备有响应操作的方法

图片库(案例)

也可以用下面方法来代替 setAttribute() 方法:

1
placeholder.src = source;

这个方法只用于 web 文档,而 DOM 则使用于任何一种标记语言。DOM 是一种适用于多种环境和多种程序设计语言的通用型 API。如果想运用在 Web 浏览器以外的应用环境里,应使用 DOM 方法。

事件处理函数的作用是:在特定事件发生时调用特定的 JavaScript 代码

事件处理函数的工作机制:在给某个元素添加了事件处理函数后,一旦事件发生,相应的 JavaScript 代码就会得到执行。被调用的 JavaScript 代码可以返回一个值,这个值将被传递给原始的事件处理函数。例如,我们可以给某个链接添加一个 onclick 事件处理函数,并让这个处理函数所出发的 JavaScript 代码返回 true 或 false。这样一来,当这个链接被点击时,如果那段 JavaScript 代码返回的值是 false,则 onclick 事件处理函数就认为 “ 这个链接没有被点击 “,反之被点击。

nodetype 属性总共有 12 种可取值,但其中仅有 3 种有使用价值:

  • 1: 元素结点
  • 2: 属性结点
  • 3: 文本结点

根据特定的 nodetype,可以编写出一个只处理元素节点的函数。(当然也可以只处理 属性节点/文本节点)。

本章主要介绍了 DOM 提供的几个新属性:

  1. childNodes
  2. nodeType
  3. nodeValue
  4. firstChild
  5. lastChild
  6. nodeName(总是返回大写值,如 IMG)

本章的学习重点:

  1. 如何利用 DOM 所提供的方法去编写图片库脚本
  2. 如何利用事件处理函数把 JavaScript 代码和网页集成在一起

达成目标的过程与目标本身同样重要

最佳实践

平稳退化

确保网页在没有 JavaScript 的情况下也能正常工作。

分离 JavaScript

把网页的结构和内容与 JavaScript 脚本的动作行为分开。

向后兼容

确保老版本的浏览器不会因为你的 JavaScript 脚本而死掉。

性能考虑

确定脚本执行的性能最佳。

尽量少访问 DOM 和尽量减少标记

访问 DOM 会对性能产生极大影响,因为:

不管什么时候,只要是查询 DOM 中的某些元素,浏览器都会搜索整个 DOM 树,从中查找可能匹配的元素

尽量减少文档中的表及数量。过多不必要的元素只会增加 DOM 树的规模,进而增加遍历 DOM 树以查找特定元素的时间

合并和放置脚本 (指放置在 body 元素最后)

压缩脚本

图片库改进版(案例)

  1. 把事件处理函数移出文档
  2. 向后兼容
  3. 确保可访问

“勤于思考” 是每个拥有创新精神的 coder 都应该具有的品质,即:“总是在每个细节上问自己这样一个问题:是否还有更好的解决办法”?

原则:如果想用 JavaScript 给某个网页添加一些行为,就不应该让 JavaScript 代码对这个网页的结构有任何依赖

1
2
3
var gallery = document.getElementById('imagegallery');

var links = gallery.getElementsByTagName('a');

事实上,与其说 links 是一个数组,不如说它是一个节点列表 (node list)来得更准确。它是一个由 DOM 节点构成的集合,这个集合里的每个节点都有自己的属性和方法

这一部分做的事情

  1. 尽量让 JavaScript 代码不再依赖于那些没有保证的假设,为此引入许多项测试和检查。这些测试和检查使得 JavaScript 代码能够平稳退化
  2. 没有使用 onkeypress 事件处理函数,这使我的 JavaScript 代码的可访问性得到了保证。
  3. 最重要的是把事件处理函数从标记文档分离到了一个外部的 JavaScript 文件,这使我的 JavaScript 代码不再依赖于 HTML 文档的内容和结构

动态创建标记

  • 传统技术:document.write 和 innnerHTML
  • 深入剖析 DOM 方法:createElement、createTextNode、appendChild 和 insertBefore

网页的结构由标记负责创建,JavaScript 函数只用来改变某些细节而不改变其底层结构,这是绝大多数 JavaScript 函数的工作原理。

JavaScript 也可以用来改变网页的结构和内容。本章中,我们将用一些 DOM 方法来通过创建新元素和修改现有元素来改变网页结构。

传统方法

不推荐用 document.write(),因为它必须要在 body 标签内调用,违背了 “行为与表现分离” 的原则

建议使用 innerHTML 属性。在需要把一大段 HTML 内容插入一份文档时,innerHTML 属性可以让我们又快又简单地完成这一任务。不过,innerHTML 属性不会返回任何对刚插入的内容的引用。如果想对刚插入的内容进行处理,则需要使用 DOM 提供的哪些更精确的方法和属性。

不过这是非 DOM 标准属性,是 HTML 专有属性

DOM 方法

浏览器实际展示的是 DOM 节点树,在浏览器看来,DOM 节点树才是文档。

在 DOM 看来,一个文档就是一棵节点树,如果想在节点树上添加内容,就必须插入新的结点。如果想添加一些标记到文档,就必须插入元素节点。

方法共有:

  1. createElement()
  2. createTextNode()
  3. appendChild()
  4. insertBefore()

把新创建的节点插入某个文档的节点树的最简单的办法是:让它成为这个文档某个现有节点的一个子节点。

既然有一些元素的存在只是为了让 DOM 方法去处理它们,那么用 DOM 方法来创建它们才是最合适的选择

在 DOM 里,元素节点的父元素必须是另一个元素结点(属性节点和文本节点的子元素不允许是元素结点)。

insertBefore():将一个新元素插入到一个现有元素的前面。需要的有:新参数、目标元素、父元素。 parentElement.insertBefore(newElement, targetElement)

因为没有 insertAfter(),需要自己写一个

1
2
3
4
5
6
7
8
9
function insertAfter(newElement, targetElement) {
var parentElement = targetElement.parentNode;

if (parentElement.lastChild === targetElement) {
parentElement.appendChild(newElement);
} else {
parentElement.insertBefore(newElement, targetElement.nextSibling);
}
}

Ajax 异步加载

Ajax:概括了异步加载页面的技术。

使用 Ajax 就可以做到只更新页面中的一小部分。其他内容——标志、导航、头部、脚步,都不用重新加载。用户仍然像往常一样点击链接,但这一次,已经加载的页面中只有一小部分区域会更新,而不必再次加载整个页面了。

Ajax 的主要优势就是对页面的请求以异步方式发送到服务器。而服务器不会用整个页面来响应请求,它会在后台处理请求,与此同时用户还能继续浏览页面并与页面交互,你的脚本则可以按需加载和创建页面内容,而不会打断用户的浏览体验。利用 Ajax,web 应用可以呈现出功能丰富、交互敏捷、类似桌面应用般的体验,就像使用谷歌地图的感觉一样。

Ajax 技术的核心就是 XMLHttpRequest 对象。这个对象充当浏览器中的脚本(客户端)与服务器之间的中间人的角色。以往的请求都由浏览器发出,而JavaScript 通过这个对象可以自己发送请求,同时也自己处理响应

1
var request = new XMLHttpRequest();

XMLHttpRequest 对象有许多的方法。其中最有用的是 open 方法,它用来制定服务器上将要访问的文件,制定请求类型:GET、POST 或 SEND。这个方法的第三个参数用于指定请求是否已异步方式发送和处理。

要构建成功的 Ajax 应用,关键在于将 Ajax 功能看做一般的 JavaScript 增强功能,在平稳退化的基础上求得渐进增强。

Ajax 应用主要依赖于服务器端处理,而非客户端处理。

Ajax 应用主要依赖后台服务器,实际上是服务器端的脚本语言完成了绝大部分工作。XMLHttpRequest 对象作为浏览器与服务器之间的 “中间人”,它只是负责传递请求和响应。

充实文档的内容(案例)

为文档创建

  • “缩略语列表” 的函数
  • “文献来源链接” 的函数
  • “快捷键清单” 的函数

获取元素文本:nodeValue, textContent(获取子孙元素所有的文本)
获取 body 所有元素,document.getElementsByTagName(‘body’)[0] or document.body
获取 body 所有子元素: document.getElementsByTagName(‘body’)[0].childNodes
获得当前节点下的所有子节点:

1
2
3
.getElementsByTagName('*');
// 然后就可以用下标来访问了
.getElementsByTagName('*')[number]

JavaScript 脚本只应该用来充实文档的内容,要避免使用 DOM 技术来创建核心内容。

CSS-DOM

在这之前,我们一直在使用 JavaScript 和 DOM 去维护和创建标记。

而 DOM 技术不仅可以用来改变网页的结构,还可以用来更新 HTML 页面元素的 CSS 样式

  • 结构层 (HTML)
  • 表示层 (CSS 页面如何呈现)
  • 行为层 (JavaScript 内容如何响应事件)

style 属性

回顾 DOM 的属性们:

关于元素在节点树位置的属性有:

  • parentNode
  • nextSibling
  • previousSibling
  • childNodes
  • firstChild
  • lastChild

包含元素自身信息的属性:

  • nodeType
  • nodeValue
  • nodeName(如果是元素结点,则返回大写,属性节点则返回属性名)
  • style(样式属性)

文档的每个元素节点都有一个属性 style。style 属性包含着元素的样式,查询这个属性将返回一个对象而不是一个简单的字符串。样式都存放在这个 style 对象的属性里。

用 style 来获取样式

可以用诸如 element.style.fontFamily 的格式来访问样式。但这个方法有很大的局限性。

只能访问内嵌样式(也能修改),不能用来检索在外部 CSS 文件里声明的样式(连 heade 中声明的都提取不到)。

设置样式

element.setAttribute(‘class’, ‘intro’);
或者:element.className = ‘intro’; element.className += ‘ intro’

只要有可能,就应该选择更新 className 属性,而不是去直接更新 style 对象的有关属性

1
2
3
4
5
6
7
8
function addClass(element, value) {
if (element.className) {
element.className = value;
} else {
value = ' ' + value;
element.className += value;
}
}

我们用 DOM 来操纵 CSS 样式的理由不外乎两点:

  1. CSS 无法让我们找到想要处理的目标元素
  2. 用 CSS 寻找目标元素的办法还未得到广泛的支持

或许,未来的 CSS 技术能够让我们远离这种 “不务正业” 的 DOM 脚本编程技术。

不过,JavaScript 能够定时重复执行一组操作,这是 CSS 所无法做到的

用 JavaScript 实现动态效果(案例)

JavaScript 能够按照预定的时间间隔重复调用一个函数,而这意味着我们可以随着事件的推移而不断改变某个元素的样式。 动画是样式随时间变化的完美例子之一。简单地说,动画就是让元素的位置随时间而不断发生变化。

1
2
setTimeout('function', interval);
clearTimeout(interval);

当既不能使用全局变量,也不能使用局部变量时。我们需要一种介乎它们二者之间的东西,这个就是属性。

HTML5

H5 的出现使得 DOM、样式和行为之间的界限变得模糊了。

  • 结构层 (HTML)
  • 表示层 (CSS 页面如何呈现)
  • 行为层 (JavaScript 内容如何响应事件)

文章标题:JavaScript DOM 编程艺术小记

文章作者:RayJune

时间地点:下午 17:06,于 fjnu 仓山校区文科楼自习教室 1-103

原始链接:https://www.rayjune.me/2017/08/27/Professional-JavaScript-for-Web-Developers-note/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。