读完还是能学到很多的基础知识,这里记录下,方便回顾与及时查阅。

内容也有自己的一些补充。

JavaScript DOM 编程艺术(第二版)

1、JavaScript简史

JavaScript由Netscape公司与Sun公司合作开发,在JavaScript之前,web浏览器只是显示文本文档的软件,JavaScript之后,网页内容不再局限于枯燥的文本,交互性显著改善。在JavaScript的第一个版本,即JavaScript 1.0版本,出现在1995年推出的Netscape Navigator 2浏览器中。

在JavaScript 1.0发布时,浏览器市场主要是Netscape Navigator,后来IE开始追赶,微软在IE3时发布了自己的VBScript语言,同时以JScript为名发布了JavaScript的第一个版本,并很快追上了Netscape的步伐。面对微软的竞争,Netscape与Sun公司联合ECMA(欧洲计算机制造商协会)对JavaScript语言进行了标准化,于是出现了ECMAScript语言,这是同一种语言的另一个名字。

到了1996年,JavaScript、ECMAScript、JScript–随便怎么称呼,已经站稳了脚本,Netscape与微软的第三版浏览器都不同程度的支持JavaScript 1.1语言。

在JavaScript早期版本向程序员提供了查询和操控Web文档某些内容的手段,比如:

<code>document.images[2]<br/>
dodument.forms['details']<br/>
</code>

现在人们通常把这种试验性质的初级DOM称为“第0级DOM”(DOM level 0),在还未形成统一标准的初级阶段,“第0级DOM”的常见用途是翻转图片和验证表单数据。

在1997年6月,Netscape Navigator 4发布,10月,IE4发布。这两个早起版本都对他们的浏览器进行了改进,大幅扩展了DOM,同时也接触到一个新名词:DHTML。

不幸的是,NN4和IE4浏览器使用了两种不兼容的DOM。程序员要写两套代码。

NN4中DOM元素为层,层有唯一的ID:

<code>document.layers['myElement']<br/>
</code>

而微软的DOM需要这样引用:

<code>document.all['myElement']<br/>
</code>

就在DOM产生分歧,浏览器厂商大战时,W3C推出了一个标准化的DOM,令人欣慰的事,Netscape与微软以及其他浏览器制造商都接受了新的标准,并于1998年10月完成了“第1级DOM”(DOM Level 1)。

二、JavaScript语法

三、DOM

DOM:Document Object Model 文档对象模型

JavaScript中的对象分为三种:

1、用户定义对象:由程序员创建的对象

2、內建对象:内建在JavaScript语言中的对象,如Array、Math和Date等

3、宿主对象:由宿主环境(浏览器)提供的对象,如window、document

window对象对应着浏览器窗口本身,这个对象的属性和方法通常称为BOM(浏览器文档模型)。

3.4 节点

先看三种:元素节点、文本节点、属性节点

<code><p class="info">my name is Gavin</p><br/>
</code>

标签的名字就是元素的名字,这里面有一个元素节点p,一个文本节点“my name is Gavin”,以及一个属性节点class=”info”。

属性节点总是被包含在元素节点中,并不是所有的元素节点都有属性节点,在所有的属性都被元素包含。

3.5 获取元素

  • getElementById()
  • getElementsByTagName()
  • getElementsByClassName() — HTML5 DOM

其中getElementById()返回一个对象,getElementsByTagName()与getElementsByClassName()返回一个对象数组。

每个节点都是一个对象。

3.6 获取和设置属性

  • object.getAttribute( attribute )
  • object.setAttribute( attribute, value )

四、案例研究:JavaScript图片库

setAttribute方法是“第一级DOM”(DOM level 1)的组成部分,可以设置任意元素的节点的任意属性。在“第一级DOM”出现之前,还可以用另外一个方法设置大部分元素的属性,这个方法到现在仍然有效。

<code>img.src = 'xxx'

// 等价<br/>
img.setAttribute( 'src', 'xxx' );<br/>
</code>

使用“第一级DOM”的优势是:

  • 不用特别记忆哪些属性可以用DOM之前的方法设置
  • 可移植性好,DOM用于任何一种标记语言,通用API。

4.4.1 childNodes

node.childNodes 获取任何一个元素的所有子元素

4.4.2 nodeType属性

nodeTyoe总共有12种可取值,但其中仅有3个具有实用价值。

  • 元素节点的nodeType属性值是1
  • 属性节点的nodeType属性值是2
  • 文本节点的nodeType属性值是3

4.4.5 nodeValue属性

如果要改变一个文本节点的值,那就实用DOM提供的nodeValue属性,它用来得到(和设置)一个节点的值。

<code>element.nodeValue<br/>
</code>

注意,文本节点可能是其他节点的子节点,在获取时尤其注意:

<code><p>my name is Gavin.</p>

<script><br/>
var oP = document.getElementsByTagName('p');<br/>
oP[0].nodeValue; // null<br/>
op[0].childNodes[0].nodeValue; // my name is Gavin.<br/>
</script><br/>
</code>

4.4.6 firstChild 和 lastChild

<code>node.childNodes[0] === node.firstChild<br/>
node.childNodes[ node.childNodes.length - 1 ] === node.lastChild<br/>
</code>

补充:nextSibling、previousSibling:兄弟节点

五、最佳实践

  • 平稳退化
  • javascript:伪协议
  • 结构与样式分离
  • 渐进增强
  • 分离JavaScript
  • 向后兼容
    • 对象检测

      <code>  if ( !xxx ) return false;<br/>
      </code>
    • 浏览器嗅探

  • 性能考虑
    • 尽量少访问DOM和尽量减少标记
      • 文档标记少,则DOM树规模少,遍历查找更快速
    • 合并和放置脚本:脚本放到body最后,可以让页面更快
    • 压缩脚本

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

通过伪协议来调用JavaScript函数的做法非常不好,不是最佳实践。

六、案例研究:图片库改进版

6.6 键盘访问

对于视力残疾的用户往往看不清屏幕上的鼠标指针,他们往往更喜欢使用键盘。众所周知,不使用鼠标也可以浏览web。键盘上的tab键可以让我们从这个链接移动到另一个链接,然后按下回车键启用当前链接。

在键盘事件中有一个onkeypress事件,按下键盘的任何一个键都会触发onkeypress事件,但这个键很容易出现问题,因为即使按下tab键也会触发该事件!

onclick事件更聪明,用tab键移到链接上,按下回车键,链接也能访问。

6.8 DOM Core 和 HTML – DOM

  • getElementById
  • getElementsByTagName
  • getAttribute
  • setAttribute

这些都是DOM Core的组成部分,他们并不专属于JavaScript,任何支持DOM的程序设计语言都可以使用它们。

而对于

  • element.onclick
  • element.forms
  • element.src

这些属性属于HTML DOM,在DOM Core出现很久之前就已经为人们所熟悉了。

大多数情况下,同样的操作即可以使用DOM Core实现也可以用HTML DOM实现。HTML DOM通过更加简短,只能用来处理HTML文档。

七、动态创建标记

7.1 一些传统方法

  • document.write()
  • innderHTML

MIME类型application/xhtml+xml与document.write()不兼容,浏览器呈现这种XHTML文档时不会执行document.write()方法。

另外,document.write()最大的问题是不能进行结构与行为分离。

备注:关于document.write()主要有两种使用方式:

第一种:当页面加载完成后,浏览器输出流自动关闭,此时进行document.write()方法将打开一个新的输出流,它将清除当前页面内容。

第二种:调用document.write()方法在窗口中打开窗口,框架中产生新文档,这种方式必须用document.close()关闭,并且继续使用document.write()写入的内容不会清除文档,而是继续追加。

element.innerHTML 方法可以读写元素的内容。几乎所有的浏览器都支持innerHTML,但这个属性不是W3C DOM的标准属性,现在已经在HTML5规范中,始于IE4浏览器。

和document.write()类似,innerHTML也是HTML专有属性,不能用于其他标记语言文档,在MIME类型为application/xhtml+xml的XHMLT文档中,该属性会被忽略。

7.2 DOM 方法

  • createElement( nodeName ) — 创建元素节点
  • createTextNode( text ) — 创建文本节点
  • appendChild( child ) — 把创建的节点插入到文档节点树中

通过createElement与createTextNode只是创建文档碎片,只有执行appendChild才会插入到文档中。

appendChild除了可以把文档碎片插入到文档树中,还可以连接文档碎片。

比如:

<code>var p = document.createElement('p');<br/>
var text = document.createTextNode('my name is Gavin.');<br/>
p.appendChild( text );<br/>
document.body.appendChild( p );<br/>
</code>
  • insertBefore — 在现有元素前插入元素

    <code>  parentElement.insertBefore( newElement, targetElement );<br/>
    </code>

没有insertAfter方法,可以结合insertBefore来实现

<code>function insertAfter ( newElement, targetElement ) {<br/>
	var parent = targetElement.parentNode;<br/>
	if ( parent.lastChild == targetElement ) {<br/>
		parent.appendChild( newElement );<br/>
	} else {<br/>
		parent.insertBefore( newElement, targetElement.nextSiblings );<br/>
	}<br/>
}<br/>
</code>

7.4 ajax

ajax的技术核心是XMLHttpRequest。在微软较早的IE5中以ActiveX对象的形式实现了一个名叫XMLHTTP的对象,在较早IE中,创建对象的方式为:

<code>var request = new ActiveXObject("Msxml2.XMLHTTP.3.0");<br/>
</code>

在其他浏览器上的创建方式为:

<code>var request = new XMLHttpRequest();<br/>
</code>

更烦的是,不同IE版本中使用的XMLHTTP对象不完全相同,为了兼容所有浏览器,需要这样写:

<code>function getHttpObject() {<br/>
	if ( typeof XMLHttpRequest == 'undefined ) {<br/>
		XMLHttpRequest = function () {<br/>
			try {<br/>
				return new ActiveXObject("Msxml2.XMLHTTP.6.0");<br/>
			} cache ( e ) {}

			try {<br/>
				return  new ActiveXObject("Msxml2.XMLHTTP.3.0");<br/>
			} cache ( e ) {}

			try {<br/>
				return  new ActiveXObject("Msxml2.XMLHTTP");<br/>
			} cache ( e ) {}<br/>
		}<br/>
	}<br/>
	return new XMLHttpRequest();<br/>
}<br/>
</code>

XMLHttpRequest对象由许多方法,最有用的是open方法,该方法有三个参数,第一个是请求类型:GET\POST\SEND,第二个是请求地址,第三个表示是否异步请求。

<code>request.open( 'GET', 'xxx.json', true );<br/>
request.onreadystatechange = function () {}<br/>
request.send();<br/>
</code>

onreadystatechange事件XMLHttpRequest返回响应时触发。

返回响应时,浏览器会在不同阶段更新readyState的值,它有5个可能值:

  • 0 表示未初始化
  • 1 表示正在加载
  • 2 表示加载完毕
  • 3 表示正在交互
  • 4 表示完成

如果readyState属性值变成了4,则说明服务器已经发回了数据。

访问服务器发送回来的数据需要通过两个属性,一个是responseText,这个用于保存文本字符串形式的数据,另一个是responseXML属性,用于保存Content-Type头部中指定为“text/xml”的数据,这其实是一个DocumentFragment对象。你可以使用各种方法来操作这个DOM对象,这也是XMLHttpRequest中有XML的原因。

八、充实文档的内容

渐进增强则必然支持平稳退化

8.3.1 选用HTML、XHTML还是HTML5

不管使用哪一种标记,必须要与DOCTYPE声明保持一致。

XHMLT比HTML的规则更严格。比如在写属性时,HTML允许使用大写字母,也可以使用小写字母,XHTML却要求所有的标签名和属性名都必须使用小写字母。

备注:关于HTML、XHTML、HTML5的区别,可以看这篇文档:

HTML4,HTML5,XHTML 之间有什么区别?

在HTML早起,W3C成立之前,基本没有标准,标准都在在实现中定义的。一直从HTML2.0到4.0、4.01,这种情况下,HTML标准不是很规范,浏览器也对HTML页面的错误相当宽容。导致HTML作者也出了很多错误的HTML页面。

后来W3C意识到这个问题,开始制定标准,为了规范HTML,W3C结合XML制定了XHTML1.0标准,按照XML的要求规范HTML,并定义了一个新MIME Type:application/xhtml+xml,W3C的初衷是对这个MIME TYPE的浏览器实行强制错误检查,如果页面有错误,就要显示错误。很多开发者拒绝使用这个MIME TYPE,W3C不得已,在XHTML1.0后附加了一个附录C。允许开发者使用XHTML来写页面,同时使用原来的MIME TYPE:application/html。这个MIME TYPE不会触发浏览器的强制错误检查。

W3C随后在XHTML1.1中取消了附录C,即使用XHTML1.1标准的页面必须使用新的MIME TYPE。但并没有多少人使用。

有了XHTML的教训,WHATWG和W3C在制定下一代HTML标准,也就是HTML5的时候,就将向后兼容作为一个很重要的原则。HTML5加入了很多特性,但最重要的特性是,不会break已有的网页。你可以将网页的第一行改为<!DOCTYPE html>,他就成了一个HTML5页面,可以照常在浏览器中显示。

<!DOCTYPE html>总共才15个字符,这个声明同时也支持HTML与XHTML标记。

某些浏览器要根据DOCTYPE来决定使用标准模式,还是兼容模式来呈现页面,兼容模式意味着浏览器要模仿早起浏览器的“怪异行为”,并允许不规范的页面也能正常工作。一般来说,我们都应该坚持使用标准模式,避免触发兼容模式,HTML5的DOCTYPE默认就是标准模式。

此外还有XHTML5,即用XML的规则来编写HTML5。

九、CSS-DOM

9.2 Style属性

文档中的每个元素都是一个对象,每个对象都有各种各样的属性,比如:parentNode、childNodes、firstChild、lastChild、nextSibling、previousSibling等,表示节点的关心信息。

还有一些属性:nodeName、nodeValue、nodeType,表示节点的类型信息。

除此之外,还有一个style属性,表示节点的样式信息。

9.2.1 获取样式

<code>element.style.color<br/>
</code>

对于font-family,在获取的时候会出错,原因为:JavaScript中连字符-为减法运算,所以会理解为获取font属性,然后对family做减法运算,而family此时为变量,但我们并没有声明这个变量,继而引发错误。

<code>// 错误写法!!<br/>
element.style.font-family;<br/>
</code>

当引用一个中间带减号的CSS属性时,DOM要求使用驼峰命名法:

<code>// 正确写法<br/>
element.style.fontFamily;<br/>
</code>

不管有多有连字符,一律采用驼峰写法:比如magrin-top-width,写为:marginTopWidth。

style属性只能返回内联样式。

9.2.2 设置样式

<code>element.style.property = value;<br/>
</code>

9.3 用DOM解耦本设置样式

:first-child,:last-child选择器是CSS2中的

:nth-child()和:nth-of-type()是CSS3中的

在使用CSS时,不要人云亦云的认为表格都是不好的,虽然用表格布局不是好主意,但利用表格来显示数据却是理所当然的。

9.4 className 属性

<code>// 读<br/>
element.className

// 写<br/>
element.className = value<br/>
</code>

className在设置的时候是替换(而不是追加)。可以利用字符串拼接,达到追加的效果

<code>element.clssName += ' info';<br/>
</code>

十、用JavaScript实现动画效果

十一、HTML5

HTML5是HTML语言当前及未来的新标准,HTML规范从HTML4到XHTML,再到Web Apps1.0,最后又回到HTML5,整个过程充满了艰辛与争议。

在结构层,HTML5添加了很多新的标记元素,如<section><article><header><footer>等。

HTML5还提供了更多的交互即媒体元素,如<canvas><audio><video>

表单也进行了增强,新增了颜色选择器数据选择器滑动条进度条等。

还有很多新的JavaScript API,比如:GeolocationStorageDrag-and-DropSocket以及多线程等。

另外,通过新的MIME TYPE,HTML5保持了向后兼容,就算写代码很不规范,也没问题。

参考链接:

十二、综合示例

附录 JavaScript库

所谓库,就是可重用的代码包,具有如下的一些优点:

  • 库代码经过了大量的用户的测试和验证
  • 库能够很容易的与已有的开发框架集成
  • 可为大多数日常琐碎的DOM变成工作提供了方便,简洁的方案,每个函数都能节省很多行代码
  • 库很好的解决了跨浏览器的问题,让你更省心

库也存在问题:

  • 库是别人写的,不是自己编写的,不了解内部机制,很难调试bug或由它导致的问题
  • 要使用库,就要把它集中到脚本中,增加页面加载负担
  • 混合使用多个库可能会造成冲突,同时也会造成功能浪费

A1:选择合适的库

在选择时,建议考虑如下问题:

  • 它具有你需要的所有功能吗?
  • 它的功能是否比你想要的还多?
  • 它是模块化的吗?
  • 它的支持情况怎么样?
  • 它有文档吗?
  • 它的许可合适吗?

A1.2 内容分发网络

一定要尽可能想办法减少网页文档的大小,并让浏览器缓存文件。除此之外,当然还要让用户尽可能的加载到页面。

内容分发网络(CDN,Content Delevery Network)可以解决分布共享库的问题。CDN是一个由服务器构成的网络,这个网络的用户就是分散存储一些公共的内容。CND的每台服务器都包含库的一份副本,这些服务器分布在世界上不同的国家和地区,以便达到和利用带宽和加快下载的目的。

浏览器访问库的时候使用一个公共的URL,而CDN的地层则通过地理位置最近、速度最快的服务器提供相应的文件,从而解决了整个系统中的瓶颈问题。