JavaScript DOM编程艺术(第2版)笔记

239 阅读18分钟

摘要

本书重点介绍了几种最有用的DOM方法和属性。本书的精华在于JavaScript和DOM脚本编程工作的基本原则、良好习惯和正确思路。本书强调最佳实践,特别是渐进增强。本书于2011年出版,较为经典,但也略微过时。适合初学者看,其中有一些案例可以在看的时候同步练习,加深掌握程度。

本书涉及领域

  • javascript : Js 发展史 / Js 语法 / Js 操作 Dom / Js 操作 Css
  • HTML5
  • Ajax
  • jQuery

受众

  • 程序员
  • 喜欢使用CSS和HTML的人
  • 愿意遵守编程规范的Web设计师

目的

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

宗旨

  • 平稳退化、渐进增强、以用户为中心的设计。

javascript起源

  • 由Netscape公司与Sun公司合作开发。目前网景已倒闭。sun(发明java)被甲骨文收购。
  • Javascript 1.0 出现在1995年,Netscape 2.0 浏览器中。此时,IE还是小弟。
  • 微软在IE 3发布了VBScript语言,同时以JScript为名发布了JavaScript的一个版本。
  • 面对微软的竞争,Netscape和Sun公司联合ECMA(欧洲计算机制造商协会)对JavaScript语言进行了标准化。出现了ECMAScript语言。
  • 到了1996年,JavaScript、ECMAScript、JScript——随便你们怎么称呼它——已经站稳了脚跟。
  • Netscape和微软公司在各自的第3版浏览器中都不同程度地支持JavaScript 1.1语言。

BOM

  • BOM 浏览器对象模型,可以用来调用整个浏览器窗口的高度、宽度和位置属性

DOM

  • 文档对象模型
  • DOM是一套对文档的内容进行抽象和概念化的方法。DOM树就是以树形结构组织文档的模型。

“第0级DOM”(DOM Level 0)

  • 试验性质的初级dom。在还未形成统一标准的初期阶段,常见用途是翻转图片和验证表单数据。

浏览器战争

  • 1997年微软和IE在双方第四版浏览器的战争
  • DHTML
  • 浏览器间的不兼容问题导致对DOM操作的差异
  • 结局:IE获胜。原因:所有运行Windows操作系统的个人电脑都预装了它。

“第1级DOM”(DOM Level 1)

  • W3C结合大家的优点推出了一个标准化的DOM。
  • W3C对DOM的定义是:一个与系统平台和编程语言无关的接口,程序和脚本可以通过这个接口动态地访问和修改文档的内容、结构和样式。

开源Web浏览器引擎

image.png

小结

  • 以上是JavaScript发展简史。
  • DHTML曾被认为是HTML/XHTML、CSS和JavaScript相结合的产物,就像今天的HTML5那样,但把这些东西真正凝聚在一起的是DOM。

DOM

把你编写的网页文档转换为一个文档对象

  • D:document。

  • O:object。(“对象”是一种自足的数据集合,与某个特定对象相关联的变量被称为这个对象的属性;只能通过某个特定对象去调用的函数被称为这个对象的方法。)

    • 对象有三种类型。
      • 用户定义对象(user-defined object):由程序员自行创建的对象。
      • 内建对象(native object):内建在JavaScript语言里的对象,如Array、Math和Date等。
      • 宿主对象(host object):由浏览器提供的对象。
  • M:model。

节点

  • 文档是由节点构成的集合
  • 其中重要的三种:元素节点、文本节点和属性节点。 nodeType属性总共有12种可取值,但其中仅有3种具有实用价值。
  • 元素节点的nodeType属性值是1。
  • 属性节点的nodeType属性值是2。
  • 文本节点的nodeType属性值是3。

小结

5个常用DOM方法:

  • getElementById

  • getElementsByTagName

  • getElementsByClassName

    • (使用这个方法还可以查找那些带有多个类名的元素。)
  • getAttribute

    • (getAttribute方法不属于document对象,所以不能通过document对象调用。它只能通过元素节点对象调用。)
  • setAttribute

  • DOM的工作模式:先加载文档的静态内容,再动态刷新,动态刷新不影响文档的静态内容


javascript图片库

  • html结构如下
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>image gallery</title>
	<link rel="stylesheet" href="index.css">
</head>
<body>
	<h1>Snapshots</h1>
	<ul>
		<li><a href="./images/pic1.png" title="a firework display" onclick="showPic(this);return false;">fireworks</a></li>
		<li><a href="./images/pic2.png" title="a cup of black coffee" onclick="showPic(this);return false;">coffee</a></li>
		<li><a href="./images/pic3.png" title="a red, red rose" onclick="showPic(this);return false;">rose</a></li>
	</ul>
	<img src="#" alt="占位符" id="placeholder">
	<p id="description">choose an image.</p>
	<script type="text/javascript" src='index.js'></script>
</body>
</html>
  • 在点击事件中使用this关键字。this表示“这个a元素节点”
  • 我们可以把任意数量的JavaScript语句放在onclick="XX;YY;"这对引号之间,只要把各条语句用分号隔开即可。
  • onclick事件处理函数的工作机制:事件处理函数执行时可以返回一个值,这个值将被传递给那个事件处理函数。例如,给某个链接添加一个onclick事件处理函数,并让这个处理函数所触发的JavaScript代码返回布尔值true或false。这样一来,当这个链接被点击时,如果那段JavaScript代码返回的值是true, onclick事件处理函数就认为“这个链接被点击了”;反之,如果返回的值是false, onclick事件处理函数就认为“这个链接没有被点击”。就不会跳转。

index.js如下:

function showPic(whichpic) {
  var source = whichpic.getAttribute('href');
  var title = whichpic.getAttribute('title');
  var placeholder = document.getElementById('placeholder');
  placeholder.setAttribute('src', source);
  var description = document.getElementById('description');
  description.innerHTML = title;
}

index.css如下:

body{
	font-family: 'Helvetica','Arial',serif;
	color: #333;
	background-color: #ccc;
	margin: 1em 10%;
}
h1{
		color: #333;
		background-color: transparent;
}
a{
	color: #c60;
	background-color: transparent;
	font-weight: bold;
	text-decoration: none;
}
ul{
	padding: 0;
}
li{
	float: left;
	padding: 1em;
	list-style: none;
}
img{
	display: block;
	clear: both;
}

小结

DOM提供的几个新属性:

  • childNodes
  • nodeType (值是一个数字)
  • nodeValue
  • firstChild
  • lastChild

最佳实践

  • ❑ 平稳退化:确保网页在不支持运行JavaScript的情况下也能正常访问。
  • ❑ 分离JavaScript:把网页的结构和内容与JavaScript脚本的动作行为分开。
  • ❑ 向后兼容性:确保老版本的浏览器不会因为你的JavaScript脚本而死掉。
  • ❑ 性能考虑:确定脚本执行的性能最优。

Flash的遭遇

制作短小精悍的矢量图形和视频片段本是Flash技术的强项之一。当视频片段的数量越来越多、体积也越来越大,网页的下载时间也不可避免地变得越来越长。于是它就有了降低网站可用性和可访问性的坏名声。

“javascript:”伪协议

“真”协议用来在因特网上的计算机之间传输数据包,如HTTP协议(http://)、FTP协议(ftp://)等,伪协议则是一种非标准化的协议。“javascript:”伪协议让我们通过一个链接来调用JavaScript函数。

  • 最好别用伪协议。 <a href="javascript:abc();">execute abc</a>较老的浏览器则会去尝试打开那个链接但失败。<a href="#">execute abc</a> 是未指向任何目标的内部链接。

平稳退化为什么这么重要?

  • 平稳退化的含义是:让不支持js的浏览器也能顺利访问你的网站。你可能会疑惑,现代浏览器还有不支持js的吗?如果这个访问者是一个搜索机器人,它们浏览Web的目的是为了把各种网页添加到搜索引擎的数据库里。搜索机器人不能够理解JavaScript代码。所以,如果你的JavaScript网页不能平稳退化,它们在搜索引擎上的排名就可能大受损害。 解决办法:在href中写一个真实的url。

分离js

  • 就是将javascript文件单独拎出来。在window.onload时执行函数。文档将被加载到一个浏览器窗口里,document对象又是window对象的一个属性。当window对象触发onload事件时,document对象已经存在。也就能获取dom了。

向后兼容

  • 对象检测。判断有无document.getElementById。使用if(document.getElementById)
  • “浏览器嗅探”指通过提取浏览器供应商提供的信息来解决向后兼容问题。这种方法不好的地方在于,浏览器可以伪装,甚至有一些允许用户修改。

性能考虑

  • 尽量少访问DOM(document.getElementById获取一次,多复用)和尽量减少标记(减少不必要的html元素,不然过多不必要的元素只会增加DOM树的规模,进而增加遍历DOM树以查找特定元素的时间)
  • 合并脚本。推荐的做法是把functionA.js、functionB.js、functionC.js和functionD.js合并到一个脚本文件中。这样,就可以减少加载页面时发送的请求数量。而减少请求数量通常都是在性能优化时首先要考虑的。
  • 压缩脚本。去掉空格换行。

为什么将js脚本放在文档末尾?

  • 脚本在html中的位置对页面初次加载影响很大。如果放在head中,可能阻塞图像或者其他脚本加载。因为根据http规范,浏览器每次从同一域名中最多同时下载两个文件。在下载脚本期间,不会下载其他任何(无论是不是资源)文件(无论是同一域名还是不同域名)。其他资源就要等待。
  • 将script放到文档末尾。</body>前。在加载脚本时,window.onload事件就能对document文档执行各种操作,因为document是window的一个children节点,有window必然有document。

小结

确定脚本执行的性能最优,文件不宜过大,压缩文件/拆分文件,js脚本放在文档末尾。请求不宜过多,适量合并小资源文件请求。

案例研究:图片库改进版

  • 检验函数/对象是否存在,是什么类型之类的。
 if (!document.getElementById)
  • 有多个出口,将其集中在函数头部
 if (!document.getElementById) {
        return false;
    }
    if (!document.getElementsByTagName) {
        return false;
    }
    if (!document.getElementById("imagegalley")) {
        return false;
    }
  • 函数执行的时机在link[i]被点击时执行
 for (var i = 0; i < links.length; i++) {
        links[i].onclick = function() {
            return !showPic(this); // 这里定义了一个匿名函数,将在links[i]元素所对应的链接被点击时执行;
        }
    }
  • 共享onload事件

    • 把现有的 window.onload 事件处理函数的值存入变量 oldonload。
    • 如果在这个处理函数上还没有绑定任何函数,就想平时那样把新函数添加给他。
    • 如果这里处理函数上已经绑定了一些函数,就把新函数追加到现有指令的末尾。
function addLoadEvent(func) {
    var oldFunc = window.onload;
    //如果window.onload没有内容,则执行新添加的func函数
    if (typeof window.onload != 'function') {
        window.onload = func;
    }
    //如果window.onload已经存在函数,则window.onload先执行旧函数,再执行新添加的func函数
    else {
        window.onload = function() {
            oldFunc();
            func();
        }
    }
}
  • 键盘访问

    • 作为一个众所周知的事实,不使用鼠标也可以浏览Web。键盘上的Tab键可以让我们从这个链接移动到另一个链接,而按下回车键将启用当前链接。不使用 onkeypress 事件。因为即使按tab键也会触发onkeypress事件。

dom core和html dom

  • 核心 DOM - 针对任何结构化文档(包括HTM、XHTML和XML)的标准模型
  • XML DOM - 针对 XML 文档的标准模型
  • HTML DOM - 针对 HTML 文档的标准模型

相同点:HTML DOM的很多对象模型来自于核心DOM。

比如:

  • getElementById
  • getElementsByTagName
  • getAttribute
  • setAttribute

不同点:HTML DOM可以以简单的方式访问DOM树

image-20230313194456704.png

小结

  • 结构与行为的分离程度越大越好。
  • 支持平稳退化:禁用 js后也可以浏览图片,链接可以正常工作。

动态创建标签

传统方法

  • document.write

    • 违背了“行为应该与表现分离”的原则。
    • 即使将它放在函数中,也还是要在部分使用
    • MIME类型application/xhtml+xml与document.write不兼容,浏览器在呈现这种XHTML文档时根本不会执行document.write方法。(MIME表示文档文件或字节流的性质和格式)
  • innerHTML属性

    • innerHTML属性也是HTML专有属性,不能用于任何其他标记语言文档。
    • 浏览器在呈现正宗的XHTML文档(即MIME类型是application/xhtml+xml的XHTML文档)时会直接忽略掉innerHTML属性。

dom方法

  • createElement

    • 用这个方法创建的元素是一个文档碎片
  • appendChild

  • createTextNode

    • 你需要创建一个文本节点,你可以用createTextNode方法来实现它。
  • insertBefore

    • Node.insertBefore

实现insertAfter函数

function insertAfter(newElement, targetElement //现存元素) {
  var parent = targetElement.parentNode;
  if (parent.lastChild == targetElement) { //目标函数是不是 parent 的最后一个子元素
    parent.appendChild(newElement); //如果是,直接追加子元素到最后
  } else { //如果不是,把新元素插入到目标元素的下一个兄弟元素之前
    parent.insertBefore(newElement, targetElement.nextSibling);
  }
}

Ajax

  • 使用 Ajax 就可以做到只更新页面中的一小部分,不必每次加载整个页面。
  • Ajax的主要优势就是对页面的请求以异步方式发送到服务器。而服务器不会阻塞整个页面来响应请求,它会在后台处理请求,与此同时用户还能继续浏览页面并与页面交互。
  • 在使用Ajax时,千万要注意同源策略。使用XMLHttpRequest对象发送的请求只能访问与其所在的HTML处于同一个域中的数据,不能向其他域发送请求。此外,有些浏览器还会限制Ajax请求使用的协议。比如在Chrome中,如果你使用file://协议从自己的硬盘里加载example.txt文件,就会看到“Cross origin requests are only supported for HTTP”(跨域请求只支持HTTP协议)的错误消息。

XMLHttpRequest对象

  • ajax技术的核心
  • 这个对象充当着浏览器中的脚本(客户端)与服务器之间的中间人的角色。以往的请求都由浏览器发出,而JavaScript通过这个对象可以自己发送请求,同时也自己处理响应。
addloadEvent(getNewContent);
​
function getHTTPObject() {
    if (typeof XMLHttpRequest == 'undefined') {
        XMLHttpRequest = function() {
            try {
                return new ActiveXObject('Msxml2.XMLHTTP.6.0');
            } catch (e) {
                // statements
                console.log(e);
            }
            try {
                return new ActiveXObject('Msxml2.XMLHTTP.3.0');
            } catch (e) {
                // statements
                console.log(e);
            }
            try {
                return new ActiveXObject('Msxml2.XMLHTTP');
            } catch (e) {
                // statements
                console.log(e);
            }
        }
    }
    return new XMLHttpRequest();
}
​
function getNewContent() {
    var request = getHTTPObject();
    // XMLHttpRequest对象有许多的方法。其中最有用的是open方法,它用来指定服务器上将要访问的文件,指定请求类型:GET、POST或SEND。这个方法的第三个参数用于指定请求是否以异步方式发送和处理。
    request.open('GET', 'example.txt', true);
    if (request) {
// onreadystatechange是一个事件处理函数,它会在服务器给XMLHttpRequest对象送回响应的时候被触发执行。根据服务器的具体响应做相应的处理。    
        request.onreadystatechange = function() {
        //readyState可能有5个值:❑ 0表示未初始化❑ 1表示正在加载❑ 2表示加载完毕❑ 3表示正在交互❑ 4表示完成
            if (request.readyState == 4) {
                var para = document.createElement('p');
                //访问服务器发送回来的数据要通过两个属性完成。一个是responseText属性,这个属性用于保存文本字符串形式的数据。另一个属性是responseXML属性,用于保存Content-Type头部中指定为"text/xml"的数据,其实是一个DocumentFragment对象。
                var txt = document.createTextNode(request.responseText);
                para.appendChild(txt);
                document.getElementById('new').appendChild(para);
            }
        };
        request.send(null);
    } else {
        alert('sorry');
    }
}

小结

  • 动态创建标签的方法
  • Ajax和异步请求

8.充实文档的内容

不应该做什么

不要滥用 DOM 技术,重要内容不要通过 Javascript 添加到网页,各大搜索引擎的搜索机器人还不支持 Javascript。

  • 渐进增强(prograssive enhancement):根据内容使用标记良好的结构(HTML);然后再逐步加强这些内容,通过 CSS 改进呈现效果。
  • 平稳退化:样式和行为支持平稳退化,缺乏 CSS 和 DOM 支持的访问者仍可以访问到核心内容。

把“不可见”变成“可见”

通过 DOM 实现一些小部件:

  • 得到隐藏在属性里的信息
  • 创建标记封装这些信息
  • 把这些标记插入到文档
  • 这个标签的含义是缩略语,其title属性可以写缩略语的全称。

是html5的文档类型声明

  • 在写标签和属性时,HTML既允许使用大写字母(比如

    ),也允许使用小写字母(比如

    );XHTML却要求所有的标签名和属性名都必须使用小写字母。

  • HTML5的文档类型声明
<!DOCTYPE html>

html5默认使用标准模式呈现文档。兼容模式意味着浏览器要模仿某些早期浏览器的“怪异行为”,并容许那些不规范的页面在新浏览器也能正常工作。

  • 浏览器大战中,网景公司和微软公司曾把和标签当做它们的武器之一。在竞争最激烈时,微软决定不在自己的浏览器里实现abbr元素。微软的IE浏览器直到IE7才支持abbr元素。所以,在老式IE浏览器中,如果你去获取abbr元素中的属性/文本节点,很有可能会得到一条报错信息。

小结

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

DOM core 以属性方式访问getAttrbute()

Html-dom 以.的方式访问

第9章 CSS-DOM

  • 三位一体的网页指的是:结构层html/表示层css/行为层js

style

  • 读取

    • element.style.property
    • 返回颜色:element.style.color
    • 返回font-family:element.style.fontFamily,注意不能出现连字符(-),使用驼峰命名法代替。
    • style 属性只能返回内嵌样式,不能检索外部样式的属性。
  • 设置

    • element.style.property = value (note:value as string)
    • 会覆盖原有属性值
  • 通过元素class属性去更新样式

    • Element.className=value (note:value as string)
    • 追加className,Element.className+=' '+value (note:空格+value )
  • 为类型都是text的input添加样式。
  •  input[type*='text']{
     font-size:16px;
     }
    

小结

  • 表示层应该与行为层分离。但常有灰色地带。诸如:hover和:focus之类的伪类允许你根据用户触发事件改变元素的呈现效果。改变元素的呈现效果当然是表示层的“势力范围”,但响应用户触发的事件却是行为层的领地。表示层和行为层的这种重叠形成了一个灰色地带。

第10章 用JavaScript实现动画效果

  • 要想用js获取style.left必须将style.left写成行内样式。而不是外部引入的css。
  • position 属性的合法值有:
  • static:默认值,出现在文档流中
  • fixed:脱离文档流,相对于浏览器可视区的固定位置
  • relative:不文档流,相对于自身的定位
  • absolute:脱离文档流,相对于父容器的固定位置
  • overflow属性的可取值有4种:visible、hidden、scroll和auto。

❑ visible:不裁剪溢出的内容。浏览器将把溢出的内容呈现在其容器元素的显示区域以外,全部内容都可见。

❑ hidden:隐藏溢出的内容。内容只显示在其容器元素的显示区域里,这意味着只有一部分内容可见。

❑ scroll:类似于hidden,浏览器将对溢出的内容进行隐藏,但显示一个滚动条以便让用户能够滚动看到内容的其他部分。

❑ auto:类似于scroll,但浏览器只在确实发生溢出时才显示滚动条。如果内容没有溢出,就不显示滚动条。

  • 安全检查的重要性。在常见常用的各种库里面的,第一步就是安全检查。

缓动动画函数

​
function animate(obj, target, callback) {
  //如果给按钮绑定点击后添加定时器的功能 多次点击会导致元素的速度加快
  //解决方法就是让元素只有一个定时器执行 即清除多余(原来的)定时器
    clearInterval(obj.timer);
    obj.timer = setInterval(function () {
            // 将步长值写在定时器里面
        var step = (target - obj.offsetLeft) / 10;
            // 把步长值改为整数 不要出现小数的问题 使用Math.ceil()方法向上取整
            // 往回退的时候 step为负值 则需要Math.floor()方法向下取整
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        if(obj.offsetLeft == target){
            clearInterval(obj.timer);
            if(callback) {
                callback();
            }
        }
        obj.style.left = obj.offsetLeft + step +'px';
    }, 15);
​

第11章 HTML5

新增加的标签:

  • <section>
  • <article>
  • <header>
  • <footer>

多媒体元素:

  • <audio>
  • <video>
  • <canvas>

Javascript API:

  • Geolocation
  • Storage
  • Drap-and-Drop
  • Socket

Canvas

  • 绘制矢量及位图

利用 canvas 在浏览器中把一副彩色图片变成灰度图片。 当用户的鼠标悬停到图片上面时,再把它切换回原始的彩色图片。

  • 在这个例子中遇到了一个大问题:pen.js:14 Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.通过在img上面加上crossorigin="anonymous"这个属性就解决了。这个属性允许我们访问未经验证的资源,所以当我们加上时,我们最好确认这是可以信任的资源。如果加上后依然有跨域问题,那么就是后端设置了 Access-Control-Allow-Origin 的header

audio 和 video

  • 在二者之前,通过 <object> 引用各种影片播放器,例如QuickTime、RealPlayer或Flash,并使用这些插件在浏览器中播放影片。问题在于它需要向网页中嵌入视频需要用到一大堆重复的和元素,其中一些在HTML4中甚至都无法通过有效性验证。
    • 编解码器决定了浏览器在播放时应该如何解码音频和视频。编解码器的核心就是一个算法,用于压缩和存储视频,以减少原始文件的大小,同时可能会也可能不会损失品质。

    表单

    • 以前只有type='text'

    新的输入控件类型包括:

    ❑ email,用于输入电子邮件地址;

    ❑ url,用于输入URL;

    ❑ date,用于输入日期和时间;

    ❑ number,用于输入数值;

    ❑ range,用于生成滑动条;

    ❑ search,用于搜索框;

    ❑ tel,用于输入电话号码;

    ❑ color,用于选择颜色。

    html5其他新特性

    • 使用localStorage和sessionStorage在客户端存储大型和复杂数据集的更有效方案
    • 使用WebSocket与服务器端脚本进行开放的双向通信
    • 使用Web Worker在后台执行JavaScript
    • 标准化的拖放实现
    • 在浏览器中实现地理位置服务