Another RayJune

JS DOM编程艺术小记

RayJune关于JS DOM编程这本书的笔记整理,当然关于这本书更多的应该是代码,放在https://github.com/rayjune/Dom-Scripting

本书要点

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

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

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

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

作者的话

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

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

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

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

DOM入门

DOM:给文档增加交互能力

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

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

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

DOM==节点树

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

DOM结点:

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

获取元素结点

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

  • document.getElementById():根据id名返回一个对象
  • document.getElementsByTagName(): 根据标签名返回一个数组, 而且即使只有一个元素,也会返回一个长度为1的数组。而且可以document.getElementsByTagName(“*”),即将整个dom树作为数组返回
  • (H5新增)document.getElementsByClassName(): 通过class类名来访问元素,也是返回一个数组,且可以添加多个参数,用空格分开即可

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

获取和设置属性

得到需要的元素以后,我们就可以设法获取它的各个属性。getAttribute方法就是用来做这件事的。相应的,setAttribute方法则可以更改属性结点的值。

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

小结

  • 一份文档就是一棵节点树
  • 节点分为不同的类型:元素结点,属性节点,文本节点
  • getElementByID返回一个对象,该对象对应着文档里的一个特定的元素节点
  • getElementsByTagName 和 getElementsByClassName将返回一个对象数组,它们分别对应着文档里的一组特定的元素节点。
  • 每个节点都是一个对象。

JS图片库案例

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

1
placeholder.src = source;

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

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

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

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

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

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

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

  • childNodes
  • nodeType
  • nodeValue
  • firstChild
  • lastChild
  • nodeName(总是返回大写值,如IMG)

本章的学习重点:

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

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

最佳实践

平稳退化

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

分离JS

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

向后兼容

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

性能考虑

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

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

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

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

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

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

压缩脚本

比如Google的Closure Complier

图片库改进版

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

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

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

1
2
3
var gallery = document.getElementById("imagegallery");
var links = gallery.getElementsByTagName("a");

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

这一部分做的事情

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

动态创建标记

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

网页的结构由标记负责创建,JS函数只用来改变某些细节而不改变其底层结构,这是绝大多数JS函数的工作原理。而JS也可以用来改变网页的结构和内容。本章中,我们将用一些DOM方法来通过创建新元素和修改现有元素来改变网页结构。

传统方法

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

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

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

DOM方法

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

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

方法共有:

  • createElement()
  • createTextNode()
  • appendChild()
  • insertBefore()

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

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

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

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

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

1
2
3
4
5
6
7
8
9
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对象。这个对象充当浏览器中的脚本(客户端)与服务器之间的中间人的角色。以往的请求都由浏览器发出,而JS通过这个对象可以自己发送请求,同时也自己处理响应

1
var request = new XMLHttpRequest();

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

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

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]

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

CSS-DOM

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

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

style属性
如何检索样式
如何改变样式

我们在浏览器里看到的网页是由以下三层信息构成的一个共同体:结构层(HTML) 表示层(CSS 页面如何呈现) 行为层(JS 内容如何响应事件)

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
9
function addClass(element, value) {
if(element.className) {
element.className = value;
}
else {
value = ' ' + value;
element.className += value;
}
}

我们用DOM来操纵CSS样式的理由不外乎两点:其一是CSS无法让我们找到想要处理的目标元素,其二是用CSS寻找目标元素的办法还未得到广泛的支持。或许,未来的CSS技术能够让我们远离这种“不务正业”的DOM脚本编程技术。

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

用JS实现动态效果

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

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

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

HTML5

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

谈到Web设计,最准确的理解是把网页看成三个层: 结构层、样式层、行为层。

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

文章作者:RayJune

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

原始链接:http://rayjune.xyz/2017/08/27/Professional-JavaScript-for-Web-Developers/

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