JavaScript是jQuery应用的基础,掌握JavaScript这门语言是使用jQuery的基础条件。本章不会全面细致的讲解JavaScript的所有, 而是讲解其精髓,这些知识能够提高你们的JavaScript内功。切忌,要修炼上乘的武功,必需要有深厚的内功基础,不然只可学到其招式而发挥不了功力。JavaScript实际上包括三部分:javascript
w ECMAScript 描述了该语言的语法和基本对象。php
w DOM 描述了处理网页内容的方法和接口。html
w BOM 描述了与浏览器进行交互的方法和接口。java
本章将讲解ECMAScript和DOM的相关知识。程序员
一般所说的JavaScript语法,其实是指JavaScript中的ECMAScript部分。本节主要讲解JavaScript的语法和语意特性。正则表达式
不少人知道JavaScript,殊不知道ECMAScript为什么物。这就比如你知道本书的做者叫作张子秋,可是殊不知道做者也属于人类同样。为何要知道二者的关系呢?由于除了JavaScript,还有微软的JScript,以及Flash中的ActionScript, 这几种语言写法上有太多的类似之处。懂得ECMAScript,就可以清楚地理解这些语言为什么如此的类似算法
什么是ECMAScript?下面是维基百科中对于ECMAScript的定义:编程
ECMAScript是一种由ECMA国际(前身为欧洲计算机制造商协会)经过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用普遍,它每每被称为JavaScript或JScript,但实际上后二者是ECMA-262标准的实现和扩展。json
1995年Netscape公司发布的Netscape Navigator 2.0中,发布了与Sun联合开发的JavaScript 1.0而且大获成功, 而且随后的3.0版本中发布了JavaScript1.1,恰巧这时微软进军浏览器市场,IE 3.0搭载了一个JavaScript的克隆版-JScript, 再加上Cenvi的ScriptEase(也是一种客户端脚本语言),致使了三种不一样版本的客户端脚本语言同时存在。为了创建语言的标准化,1997年JavaScript 1.1做为草案提交给欧洲计算机制造商协会(ECMA),第三十九技术委员会(TC39)被委派来“标准化一个通用的,跨平台的,中立于厂商的脚本语言的语法和语意标准”。最后在Netscape、Sun、微软、Borland等公司的参与下制订了ECMA-262,该标准定义了叫作ECMAScript的全新脚本语言。数组
今后之后的Javscript,JScript,ActionScript等脚本语言都是基于ECMAScript标准实现的。
因此,ECMAScript其实是一种脚本在语法和语义上的标准。实际上JavaScript是由ECMAScript,DOM和BOM三者组成的。 因此说,在JavaScript,JScript和ActionScript中声明变量,操做数组等语法彻底同样,由于它们都是ECMAScript。可是在操做浏览器对象等方面又有各自独特的方法,这些都是各自语言的扩展。图2-1显示了ECMAScript与各个语言的关系。
图 2-1 各类脚本语言的关系
不一样于C#,Java中多种多样的“类”,JavaScript是一个相对单纯的世界。JavasScript中也分为值类型和引用类型两大类,可是这两大类中都只含有不多的几种类型。
了解C#语言的都对值类型和引用类型不会陌生。JavaScript中有时也称为原始值(primitive value)和引用值(reference value)。
值类型:存储在栈(stack)中,一个值类型的变量实际上是一个内存地址,地址中存储的就是值自己。
引用类型:存储在堆(heap)中,一个引用类型的变量的值是一个指针,指向存储对象的内存处。
时刻记着“值类型”和“引用类型”的区别有助于更好的理解语言的精髓。为了化繁为简, 虽然从理论上应该分为“值类型”和“引用类型”,又能够将JavaScript中对象分为“本地对象”,“内置对象”和“宿主对象”,可是在实际应用中为了让JavaScript变得真正单纯,能够将JavaScript中的类型分为:
undefined,null,number,string,boolean,function, 其余object引用类型。
即便一个JavaScript初学者对这些类型也不会陌生。这种分类方法前6种都是最常使用的JavaScript类型,第7种object引用类型其实并非独立的类型,由于function就是一种引用类型,另外JavaScript中的值类型背后其实也是一个“引用类型”,这一点和C#极其类似,就是全部的类型都是从Object中派生而来。好比number是一个“值类型”, 可是其实存在一个引用类型“Number”,咱们可使用以下的方式声明一个变量:
var oNumberObject = new Number(55);
Number对象是ECMAScript标准中定义的。可是本文不许备深刻的讲解它们,由于最经常使用的仍是使用下面的方式建立一个“值类型”的数值为55的变量:
var iNumberObject = 55;
这就够了不是吗?可是要记住藏在背后的Number对象是一个引用类型!
若是你对undefined和null这两种类型常常分辨不清,那么恭喜,由于你会找到不少的知音。其实要理解这两种类型, 首先要知道它们设计的初衷:
undefined:表示一个对象没有被定义或者没有被初始化。
null:表示一个还没有存在的对象的占位符。
有意思的是undefined类型是从null派生来的。因此它们是相等的:
alert(null == undefined); //输出 “true”
对于全部的JavaScript开发人员,最常碰到的就是对象不存在错误。正如在C#中的空引用错误同样。不少程序员习惯的觉得JavaScript中的if会自动将undefined和null对象转化为false,好比:
var oTemp = null;
if(oTemp){}; //false
if(undefined){}; //false
上面的语句都是正确的,if中的条件都是false。可是若是注释掉oTemp的声明部分,状况就不一样了:
//var oTemp = null; 注释掉变量声明语句
if(oTemp){}; //error
会抛出错误。可是不管是否声明过oTemp对象,使用typeof运算符获取到的都是undefined而且不会报错:
//var oTemp1; 注释掉变量声明语句
alert(typeof oTemp1); //输出 “undefined”
var oTemp2;
alert(typeof oTemp2); //输出 “undefined”
因此若是在程序中使用一个可能没有定义过的变量,而且没有使用typeof作判断,那么就会出现脚本错误。而若是是此变量是null或者没有初始化的undefined对象,能够经过if或者“==”来判断。切记,未声明的对象只能使用typeof运算符来判断!
正由于如此,typeof常常和undefined变量一块儿使用。typeof运算符返回的都是一个字符串,而时常程序员会看成类型来使用。是否你也犯过以下的错误呢?
//var oTemp; 注释掉变量声明语句
if(typeof oTemp == undefined ){…}; //false
这里if将永远是false。要时刻铭记typeof返回的是字符串,应该使用字符串比较:
//var oTemp; 注释掉变量声明语句
if(typeof oTemp ==”undefined”){…};//true
下面是typeof运算符对各种型的返回结果:
w undefined:“undefined”
w null:“object”
w string:“string”
w number:“number”
w boolean:“Boolean”
w function:“function”
w object:“object”
结果只有null类型让人吃惊。null类型返回object,这实际上是JavaScript最初实现的一个错误,而后被ECMAScript沿用了,也就成为了如今的标准。因此须要将null类型理解为“对象的占位符”,就能够解释这一矛盾,虽然这只是一中 “辩解”。对于代码编写者必定要时刻警戒这个“语言特性”,由于:
alert(typeof null == “null”);//输出 false
永远为false。
还要提醒,一个没有返回值的function(或者直接return返回)实际上返回的是undefined。
function voidMethod()
{
return;
}
alert(voidMethod()); //输出 "undefined"
由于JavaScript是弱类型语言,因此在变量的声明上体现了与C#等强类型语言的明显不一样。
JavaScript可使用var显式的声明变量:
var iNum;
var sName;
也能够在一个var语句中声明多个变量,用“,”分割变量:
var iNum, sName;
变量的类型是在赋值语句中肯定的,JavaScript使用“=”赋值,能够在声明变量的同时对其进行赋值:
var sName=”ziqiu.zhang”;
由于是弱类型语言,即便变量的类型在初始化时已经被肯定,仍然能够在以后把它设置成其它类型,好比:
var sName = “ziqiu.zhang”;
alert(sName); //输出 “ziqiu.zhang”
sName = 55;
alert(sName); //输出 输出 “55”
变量除了能够显式声明,也能够隐式声明。所谓隐式声明,就是不使用var关键词声明,而直接为变量赋值,好比:
//var sName; 注释掉变量声明语句
sName = “ziqiu.zhang”
alert(sName); //输出 输出 “ziqiu.zhang”
上面的语句不会出任何的错误。就如同使用var声明过变量同样。可是不一样之处是变量的做用域。隐式声明的变量老是被建立为全局变量。即便是在一个函数中建立的变量也依然是全局的。好比:
function test()
{
sName = " ziqiu.zhang ";
}
//var sName;
test();
alert(sName); //输出 输出 “ziqiu.zhang”
虽然sName是在函数中建立的,可是在函数外层仍然可以访问sName,由于sName是全局变量。
变量能够隐式声明,可是不能够不声明。若是一个变量既没有隐式声明,也没有显式声明,那么在使用时会发生对象未定义的错误。
由于JavaScript语言的灵活性,不少人会忽视变量的命名,有的公司即便制定了后段代码如C#的命名规范,却忽视了JavaScript变量的命名规范。
从一开始就制定完整的JavaScript命名规范是颇有必要的,尤为是在愈来愈追求用户体验的今天,JavaScript将会承载愈来愈多的用户逻辑。
先来学习三种命名方法:
w Camel命名法:首字母小写,接下来的每一个单词首字母大写。好比:
var firstName, var myColor;
w Pascal命名法:首字母大写,接下来的每一个单词首字母大写。好比:
var FirstName, var MyColor;
w 匈牙利类型命名法:在以Pascal命名法的变量前附加一个小写字母来讲明该变量的类型。例如s表示字符串,则声明一个字符串类型的变量为:
var sFirstName;
在JavaScript中应该使用匈牙利命名法命名变量,使用Camel命名法命名函数。
这一点是和C#、Java不一样的。一般服务器端语言都是用Pascal命名法命名方法,即首字母要大些。可是JavaScript中的全部方法首字母都是小写的,为了和JavaScript中的默认命名一致,因此要采用Camel命名法命名方法。好比:
function testMethod(){};
虽然JavaScript中的变量能够变换类型,可是熟悉强类型语言的人都知道,这是很危险的作法,颇有可能最后在使用时都没法肯定变量的类型。因此应该尽可能使用匈牙利命名法命名变量,下面是匈牙利命名法的前缀列表:
类型 |
前缀 |
示例 |
Array |
a |
aNameList |
Boolean |
b |
bVisible |
Float |
f |
fMoney |
Function |
fn |
fnMethod |
Int |
i |
iAge |
Object |
o |
oType |
Regexp(正则表达式) |
re |
rePattern |
string |
s |
sName |
可变类型 |
v |
vObj |
表 2-1 JavaScript中的匈牙利命名法前缀
使用匈牙利命名法是推荐的作法。可是不少程序由于历史遗留缘由都使用Camel命名法。记住匈牙利命名法才是正确的方法。写代码也是一门艺术,只要有机会就应该作正确的事。
变量的做用域就是变量做用的范围,只有在变量的做用域内才能够访问该变量,不然是没法访问此变量的。好比:
function test()
{
var sName = " ziqiu.zhang ";
}
alert(sName); //输出 "sName未定义";
会提示错误”sName”未定义。
JavaScript变量的做用域基本上与C#、Java等语言相同。可是由于JavaScript语言的特殊性,有些须要特殊理解的地方。
首先要了解,全局变量是Window对象的属性。
前面已经提到过隐式声明的变量都是全局变量。说是“全局”但实际上仍是有做用域的,那就是当前窗口window对象。一个全局变量就是window对象的一个属性:
function test()
{
sName = " ziqiu.zhang ";
}
//var sName;
test();
alert(sName);
alert(window.sName);
隐式声明了一个全局变量sName后,既能够直接访问sName,也能够经过window.sName访问,二者的效果是相同的。
若是说全局变量仍是传统的做用域模型,那么闭包(closure)的概念是会让初学者迷惑的。闭包的概念比较难以理解, 先看闭包的定义:
闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(一般是一个函数),于是这些变量也是该表达式的一部分。
简单表述:
闭包就是function实例以及执行function实例时来自环境的变量。
不管是定义仍是简单表述,都让人难以理解。由于这都是理论的抽象。经过实例就能够快速的理解闭包的含义:
【代码路径:jQueryStorm.Web/chapter2/closure.aspx】
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> jQuery storm - 闭包举例</title>
</head>
<body>
<div id="divResult">
</div>
<script type="text/JavaScript">
function start()
{
var count = 10;
//设置定时器,每隔3秒钟执行一次
window.setInterval(function()
{
//设置定时器,每隔3秒钟执行一次
document.getElementById("divResult").innerHTML += count + "<br/>";
count++;
}, 3000);
};
start();
</script>
</body>
上面的实例使用setInterval函数,设置了一个定时器,每3秒钟会向divResult容器中,添加count变量的值,而且count变量自增。
count是start函数体内的变量,按照一般的理解count的做用域是在start()函数内,在调用start()函数结束后应该也会消失。可是此示例的结果是count变量会一直存在,而且每次被加1,数据结果是:
10
11
12
13
这是由于count变量是setInterval中建立的匿名函数(也就是包含count++的函数)的闭包的一部分!
再通俗的讲, 闭包首先就是函数自己,好比上面这个匿名函数自己,同时加上在这个函数运行时须要用到的count变量。
JavaScript中的闭包是隐式的建立的,而不像其余支持闭包的语言那样须要显式建立。在C#语言中不多碰到是由于C#中没法在方法中再次声明方法. 而在一个方法中调用另外一个方法一般使用参数传递数据。
JavaScript中的闭包是很是强大的,能够用于执行复杂的逻辑。可是在使用时要时刻当心,由于闭包的强大也致使了它的复杂,使用闭包会让程序难以理解和维护。
function类型是JavaScript的灵魂,由于程序是由数据和逻辑两部分组成的,基本的数据类型能够存储各类数据,而function则用来存储逻辑。固然这只是将function看成“方法”来看待而已。实际上function类型有着更增强大的生命力。
可使用function声明一个方法,好比:
function testMethod()
{
alert("Hello world!");
}
testMethod(); //输出 "Hello world!"
调用该方法将显示“Hello world!”。然而除了方法,function还能够用来声明“类型”。JavaScript中本没有“类型”的概念,也就是C#中的Class的概念,可是可使用function来假装一个类型。好比:
function Car()
{
this.color = "none";
if (typeof Car._initialized == "undefined")
{
Car.prototype.showColor = function()
{
alert(this.color);
}
}
Car._initialized = true;
};
上面的代码声明了一个Car类,而且带有一个color属性和一个showColor方法。这里使用的是“动态原型”方法,此方法将在下一节中作详细讲解。
好了,如今可使用“new”运算符建立一个类的实例,并使用它:
var car = new Car();
car.showColor(); //输出 "none"
car.color = "blue";
car.showColor(); //输出 "blue"
虽然function有这么多的能力,可是其本质却很简单:
function变量是引用类型,内容存储的是指向function内容的指针,function的内容就是函数体。
JavaScript中的对象有一个特色,里面存储的都是name/value(名/值)。用name/value存储属性是比较容易理解的,一个属性的名称就是name,好比color属性。属性的值就是value,好比color的属性值red。这与C#语言是同样的。可是JavaScript中的function也是一个name/value,这一点十分独特。可使用alert方法输出function变量的内容:
alert(testMethod);
输出:
图 2-2 函数变量输出结果
发现输出的内容就是testMethod的方法体自己。如同一个属性输出的是其属性值。
认清这一点有助于更好的理解function的本质。
function类型的对象,配合new运算法,能够建立一个实例。依然是上面建立一个Car类实例的例子:
var car = new Car();
首字母大写的Car是一个函数,可是使用new运算符生成的是一个object:
alert(typeof car); //输出 object
而Car函数自己则更像是一个类的构造函数。实际上,new与运算符的操做,等价于下面的语句:
var car2 = {}; //建议一个空对象
//将car2的原型设置为Car.prototype,这一部是经过javascript内部的Object.create实现的,可是此函数是内部函数没法直接访问。
Car.call(car2); //修改函数调用的上下文
alert(car2.color);
其中第二步没法直接用语句替代,是由于实现这一步是在javascript引擎内部,方法没法直接调用。
JavaScript不支持方法的重载,缘由是在JavaScript中同名的function只能有一个,而且function函数的参数个数可使任意的。
虽然没法直接支持“重载”,可是能够经过arguments对象来假装重载,让一个function根据参数的不一样实现不一样的功能。
arguments是在function中的特殊对象,经过arguments对象能够访问到function调用者传递的全部参数信息,好比获取到传入的参数个数:
function testMethod()
{
alert(arguments.length);
}
testMethod(); //输出 "0"
testMethod("abc"); //输出 "1"
能够经过arguments的索引arguments[index]获取到每个传入的参数值:
function myMethod()
{
alert(arguments instanceof Array);
if (arguments.length == 0)
{
alert("no arguments");
}
else if (arguments.length == 1)
{
alert("Hello:" + arguments[0].toString())
}
}
myMethod();
myMethod("ziqiu.zhang");
显然,上面的方法经过判断参数的个数实现了假装重载。可是这种实现并很差,由于全部的重载逻辑都集中在一个方法中,而且有使人厌烦的多个if-else分支。
可是使用arguments能够开发出功能强大灵活的函数。在开发jQuery的插件时就要常用arguments对象实现函数的伪重载。
在C#中,this变量一般指类的当前实例。JavaScript则不一样,JavaScript中的“this”是函数上下文,,不是在声明时决定的,而是在调用时决定的。由于全局函数其实就是window的属性,因此在顶层调用全局函数时的this是指window对象,下面的例子能够很好的说明这一切:
<script type="text/JavaScript">
var oName = { name: "ziqiu.zhang" };
window.name = "I am window";
//showName是一个函数,用来显示对象的name属性
function showName()
{
alert(this.name);
}
oName.show = showName;
window.show = showName;
showName(); //输出 输出为 "I am window"
oName.show(); //输出 输出为 "ziqiu.zhang"
window.show(); //输出 输出为 "I am window"
</script>
showName是一个function,使用alert语句输出this.Name,。由于showName是一个全局的函数,因此其实showName是window 的一个属性:window.showName,调用全局的showName就是调用window.showName, 由于window上的name属性为”I am window”,因此直接调用showName和调用window.showName都会输出”I am window”。
oName是一个对象,前面已经讲过函数也是一个属性,能够像属性同样赋值,因此将showName方法复制给oName的show方法,当调用oName.show方法时,也会触发alert(this.name)语句,可是此时方法的调用者是oName,因此this.name输出的是oName的name属性。
利用“this指向函数调用者”的特性,能够实现链式调用。jQuery中大部分都是链式调用。首先实现一个链式调用的例子:
var oName = { name: "ziqiu.zhang", age:999 };
oName.showName = function()
{
alert(this.name);
return this;
};
oName.showAge = function()
{
alert(this.age);
return this;
};
oName.showName().showAge();
上面的代码首先输出”ziqiu.zhang”,而后输出”999”。oName使用链式调用方法分别调用了两个方法,等效于:
oName.showName();
oName.showAge();
使用链式调用的关键点就是要返回调用者自己,也就是this指针。
在使用this时,也经常由于this指向函数上下文的特性,致使引用的错误,好比:
var myClass =
{
checkName: function() { return true; },
test: function() {
if (this.checkName()) {
alert("ZZQ");
}
}
}
$("#btnTest").click(myClass.test);
上面的例子中试图使用面向对象的方式将一些方法封装在myClass中,并为btnTest对象添加单击事件的事件处理函数。如今若是单击btnTest调用myClass.test方法,会发生错误,提示找不到checkName方法。jQuery.proxy函数解决此问题。参见“jQuery工具函数”一章中的“修改函数上下文”一节。
JavaScript中的原型(Prototype)是JavaScript最特别的地方之一。不管是实现JavaScript的面向对象仍是继承,使用prototype都必不可少。
“原型”表示对象的原始状态,JavaScript中的每一个对象都有一个prototype属性,可是只有Function类型的prototype属性可使用脚本直接操做。Object的prototype属于内部属性,没法直接操做。prototype属性自己是一个object类型。一个函数的prototype上全部定义的属性和方法,都会在其实例对象上存在。因此说prototype就是C#类中的实例方法和实例属性。实例方法和静态方法是不一样的,静态方法是指不须要声明类的实例就可使用的方法,实例方法是指必需要先使用"new"关键字声明一个类的实例, 而后才能够经过此实例访问的方法。
下面是两种方法的声明方式:
function staticClass() { }; //声明一个类
staticClass.staticMethod = function() { alert("static method") }; //建立一个静态方法
staticClass.prototype.instanceMethod = function() { "instance method" }; //建立一个实例方法
上面首先声明了一个类staticClass, 接着为其添加了一个静态方法staticMethod 和一个动态方法instanceMethod,区别就在于添加动态方法要使用prototype原型属性。
对于静态方法能够直接调用:
staticClass.staticMethod();
可是实例方法不能直接调用:
staticClass.instanceMethod(); //语句错误, 没法运行.
实例方法须要首先实例化后才能调用:
var instance = new staticClass();//首先实例化
instance.instanceMethod(); //在实例上能够调用实例方法
使用prototype除了能够声明实例方法,也能够声明实例属性。正由于原型有着如此强大的功能,因此可以使用原型来实现JavaScript的面向对象。目前存在着不少以面向对象的形式建立对象的方法,本书不一一详细讲解,只介绍一种最经常使用,最容易理解和使用的方法:动态原型方法。
假设要定义一个汽车Car类型, 具备属性color和方法showColor(),则可使用下面的方式声明和使用:
function Car()
{
//声明属性
this.color = "none";
//声明方法
if (typeof Car._initialized == "undefined")
{
Car.prototype.showColor = function()
{
alert(this.color);
}
}
Car._initialized = true;
};
var car = new Car();
car.showColor(); //输出 输出为 "none"
car.color = "blue";
car.showColor(); //输出 输出为 "blue"
动态原型方法的精髓在于使用prototype声明实例方法,使用this声明实例属性。除了更加接近面向对象的编程方式,这种方式特色就是简单易懂。注意充当“类定义”的function并无带有任何参数,虽然也能够传递参数进去,可是不要这么作。这是为了下一节讲解的使用原型链实现继承机制而做的准备。
能够在创立了对象之后再为其属性赋值。虽然麻烦了一点,可是代码简单,易于理解。
除了面向对象的声明方式,在面向对象的世界中最常使用的就是对象继承。在JavaScript中能够经过prototype来实现对象的继承。继续上一节Car的例子。假设Car有两个派生类,一个是GoodCar,特色是跑得快。一个是BadCar,特色是跑得慢。可是它们和Car同样都具备color属性和showColor方法。
以GoodCar类为例,只要让GoodCar的prototype属性为Car类的一个实例,便可实现类型的继承:
GoodCar.prototype = new Car();
如今,GoodCar类已经有了Car的全部属性和方法:
var goodCar = new GoodCar();
goodCar.showColor(); //输出 "none"
实现了继承后,GoodCar还要实现本身的run()方法,一样使用prototype实现,下面是GoodCard类的完整定义:
//建立GoodCar类
function GoodCar()
{ }
GoodCar.prototype = new Car();
GoodCar.prototype.run = function() { alert("run fast"); }
须要注意GoodCar类自身的方法必定要在实现继承语句以后定义。
为什么称这种方式为原型链继承呢?由于GoodCar的prototype是Car,Car的prototype是object,也就是说GoodCar也具备object对象全部的属性和方法。这是一个“链”的结构。
使用原型能够简单快捷的实现JavaScript的面向对象和继承。
DOM太经常使用了,以致于不少人操做了DOM殊不知道使用的是DOM。从概念上区分JavaScript核心语法,DOM和BOM是颇有必要的,“学院派”做风可以让Web的知识体系结构更加清晰。
DOM(文档对象模型)是 HTML 和 XML 的应用程序接口(API)。
上面是DOM的定义。有人将操做HTML理解为操做DOM,这没有错。可是HTML并非DOM。操做HTML也不是DOM的惟一功能。DOM是一种模型以及操做这些模型的API。DOM分为不一样的部分和级别。按照部分来划分,DOM包括:
w Core DOM
定义了一套标准的针对任何结构化文档的对象
w XML DOM
定义了一套标准的针对 XML 文档的对象
w HTML DOM
定义了一套标准的针对 HTML 文档的对象。
因此Web开发人员常说的DOM实际上是指HTML DOM。这其实只是DOM的一部分。DOM标准分为level 一、二、3,每一个标准都规定了不一样的功能,并非全部的浏览器都能实现最高级别的DOM标准。Firefox是目前实现DOM标准最优秀的浏览器,但仍在为了实现所有的DOM标准而努力。本书不对DOM作更细致的讲解,本节的目的让开发人员正确的理解DOM与 HTML DOM的关系。
经过JavaScript,能够经过DOM操做HTML的各个元素。现在的页面标准已是XHTML时代。可是页面的本质依然是XML,一种特殊的用于页面显示的XML。不管是ASP.NET,JavaScriptp仍是php,任何的服务器页面最后都要输出为HTML页面—浏览器只认识HTML。
一个页面会首先被浏览器解析成DOM,好比下面就是一个页面的DOM结构:
图 2-3 页面的文档对象模型
页面被浏览器解析为DOM模型后,经过JavaScript操做页面元素其实是经过浏览器提供的基于DOM的API方法实现的。理解了这些就不会提出相似“DOM元素是JavaScript变量吗?”等疑问了。
既然DOM是一个文档树,就必定会有根节点:html元素。页面的document对象表明文档,documentElement属性就表示html对象:
var oHtml = document.documentElement;
DOM核心的API方法能够获取到一个节点的第一个元素和最后一个元素,由于一个html对象一般只有head和body两个元素,因此能够经过下面的方式获取:
var oHead = oHtml.firstChild;
var oBody = oHtml.lastChild;
理解了树状结构,使用DOM核心的方法就能够获取到页面上任意的一个元素。下面是最经常使用的获取元素节点的方法:
(1)getElementById
根据元素id获取元素。好比获取id为divMsg的元素:
document.getElementById("divMsg")
这是最常使用的获取元素的方法,一个页面首先应该保证每一个元素的id是惟一的。由于任何元素均可以添加id属性,因此经过id可以获取到页面上面的任何一个元素。这也是效率最高的一个方法。即便使用jQuery功能强大的选择器,也应该首选使用id选择器。
可是须要注意getElementById方法只能经过document对象调用,使用oBody. getElementById会引起错误。
(2)getElementsByName
经过name属性获取一组元素。方法的名字中是复数形式的“Elements”,说明了获取到的是一组元素,经过Element的单复数形式区分获取到的是单个对象仍是多个对象,是一种便于记忆的方法。getElementsByName不常使用,由于一般只有表单元素才带有name属性。
(3)getElementsByTagName
经过html元素的名称获取一组元素。好比页面上全部的div:
var aDiv = document.getElementsByTagName("div");
也能够在一个元素上调用getElementsByTagName方法,获取到的是这个元素内的元素:
var oHtml = document.documentElement;
var oBody = oHtml.lastChild;
var aMyDiv = oBody.getElementsByTagName("div");
上面的语句会得到body元素下全部的div元素。由于全部div都是在body中的,因此其实和document.getElementsByTagName("div") 的结果是相同的。
除了获取,还有设置属性,建立元素,附加元素等各类DOM方法。DOM的系统学习不在本书的范围内,由于随后的章节将介绍使用jQuery来控制页面元素,jQuery的内部也是使用DOM实现的,理解了DOM的原理有助于更好的学习jQuery,甚至发现jQuery内部未被发现的错误。
经过DOM接口操做HTML元素,实际上并非直接改变HTML代码,中间有浏览器的帮助。在JavaScript中操做的都是DOM元素,可使用JavaScript动态的建立一个div,而后附加到body中:
【代码路径:jQueryStorm.Web/chapter2/DOM.aspx】
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>jQuery Storm - DOM</title>
<script type="text/JavaScript">
function onLoad()
{
//获取到HTML对象
var oHtml = document.documentElement;
//获取到Body对象
var oBody = oHtml.lastChild;
//建立一个div对象
var oDiv = document.createElement("div");
//建立一个文本节点
var oText = document.createTextNode("Hello DOM");
//将文本节点放到div对象中
oDiv.appendChild(oText);
//将div对象(带有文本节点)放到body对象中
oBody.appendChild(oDiv);
}
</script>
</head>
<body onload="onLoad()">
</body>
</html>
上面的例子使用了标准的DOM方法建立了div元素节点oDiv,以及一个内容节点oText。在DOM中内容也是一个节点。
注意,若是要改变页面的DOM结构,应该在浏览器加载完页面的DOM结构以后进行。不然在IE6中会常常出现浏览器操做终止的错误。本实例将添加元素的工做放在了body的onload事件中。使用jQuery则会有更好更简单的处理方式,使用$(function(){…})这种形式就能够在DOM加载完毕后执行函数内的语句。在jQuery核心一节会详细讲解。
动态的添加了一个DOM元素,实际上改变了页面的DOM模型,随后浏览器会从新解析DOM模型并转换成页面显示,因而页面上出现了“Hello DOM”,就好像一开始就包含在HTML代码中同样。
不少时候就是在操做HTML元素,为什么本节要刻意的强调DOM元素与HTML元素呢?这是为了引出关于DOM属性与HTML属性的区别。顾名思义,DOM属性就是DOM元素的属性,HTML属性就是HTML元素的属性。DOM是没法直接经过源代码看到的。可是HTML元素是实实在在存在于页面的HTML代码中的,好比:
<img src="images/image.1.jpg" id="hibiscus" alt="Hibiscus" class="classA" />
这是一个HTML元素,图片的地址就是img元素的src属性,值为“images/image.1.jpg”,由于HTML元素最后都要转化成DOM元素供浏览器显示,因此浏览器最后将这部分代码解析成一个DOM元素,而且也具备一个src属性,只是属性值变成了“http://localhost/images/image.1.jpg”(假设网页在localhost站点下)。HTML中的元素最后会映射成DOM元素,并且中间的转化并非彻底一致的,img元素的图片地址会转化为绝对路径,还有些属性好比“class”转化为DOM会后会改变属性名称,变成了“className”。
牢记, 在JavaScript中能够直接获取或设置"DOM属性",因此若是要设置元素的CSS样式类, 要使用的是DOM属性“className”而不是HTML元素“class”:
document.getElementById("img1").className = "classB";
在执行完上面的语句后,id为img1的元素已经应用了classB样式,可是查看网页源代码,发现HTML元素的class仍然是classA。
有了jQuery,就不须要Web开发人员刻意的理解class和className的区别。有关使用jQuery操做样式和属性将在后面的章节中详细讲解。
要讲解JavaScript的所有,须要厚厚的一本书。既然没法全面讲解,就只能抽取一些武功秘籍传授给你们。除了上面讲解的JavaScript知识,本节再介绍一些JavaScript的关键知识点。
JSON是指JavaScript Object Notation, 即JavaScript对象表示法. 因此说JSON实际上是一种数据格式,由于JavaScript原生的支持因此赋予了JSON强大的生命力。好比可使用下面的语句建立一个对象:
var oPerson = {
name: "ziqiu.zhang",
age: 999,
school:
{
college: "BITI",
"high school" : "No.18"
},
like: ["mm"]
};
JSON的语法格式是使用"{"和"}"表示一个对象, 使用"属性名称:值"的格式来建立属性, 多个属性用","隔开.
上例中school属性又是一个对象. like属性是一个数组. 使用JSON格式的字符串建立完对象后, 就能够用"."或者索引的形式访问属性:
objectA.school["high school"];
objectA.like[1];
JSON常常在Ajax中使用。让服务器端页面只返回JSON格式的数据,使用JavaScript的eval方法将JSON格式的数据转换成对象,以便使用JavaScript已经操做。
JavaScript原生的支持了JSON格式,而各类语言也都有支持JSON格式的类库。在.net framework 3.5中微软已经提供了原生的JSON序列化器。JSON是目前客户端与服务器端交互数据的最好的数据格式。下面提供一个以.NetFramework3.5版本中自带的JSON序列化器为基础开发的JSON帮助类,代码也能够在本书光盘的代码中或者网站上找到:
【代码路径:jQueryStorm.Common.Utility/JsonHelper.cs】
/****************************************************************************************************
* *
* * File Name : JsonHelper.cs
* * Creator : ziqiu.zhang
* * Create Time : 2008-10-8
* * Functional Description : JSON帮助类。用于将对象转换为Json格式的字符串,或者将Json的字符串转化为对象。
* 使用限制:
* (1)只能在.NET Framework3.5及以上版本使用。
* (2)对象类型须要支持序列化,类上添加[DataContract],属性上添加[DataMember]
* 使用举例请参见单元测试项目。
*
* * Remark :
* *
* * Copyright (c) eLong Corporation. All rights reserved.
* ****************************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Json;
using System.IO;
namespace jQueryStorm.Common.Utility
{
/// <summary>
/// JSON帮助类。用于将对象转换为Json格式的字符串,或者将Json的字符串转化为对象。
/// 只能在.NET Framework3.5及以上版本使用。
/// </summary>
public class JsonHelper
{
/// <summary>
/// 将对象转化为Json字符串
/// </summary>
/// <typeparam name="T">源类型</typeparam>
/// <param name="instance">源类型实例</param>
/// <returns>Json字符串</returns>
public static string ObjectToJson<T>(T instance)
{
string result = string.Empty;
//建立指定类型的Json序列化器
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(T));
//将对象的序列化为Json格式的Stream
MemoryStream stream = new MemoryStream();
jsonSerializer.WriteObject(stream, instance);
//读取Stream
stream.Position = 0;
StreamReader sr = new StreamReader(stream);
result = sr.ReadToEnd();
return result;
}
/// <summary>
/// 将Json字符串转化为对象
/// </summary>
/// <typeparam name="T">目标类型</typeparam>
/// <param name="jsonString">Json字符串</param>
/// <returns>目标类型的一个实例</returns>
public static T JsonToObject<T>(string jsonString)
{
T result;
//建立指定类型的Json序列化器
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(T));
//将Json字符串放入Stream中
byte[] jsonBytes = new UTF8Encoding().GetBytes(jsonString);
MemoryStream stream = new MemoryStream(jsonBytes);
//使用Json序列化器将Stream转化为对象
result = (T)jsonSerializer.ReadObject(stream);
return result;
}
}
}
在使用时,假设有一个MyClass的类:
public class MyClass
{
public string MyName
{ get; set; }
public int Age
{ get; set; }
}
使用JsonHelper的两个泛型方法ObjectToJson和JsonToObject,就能够在对象实例和JSON字符串之间序列化和反序列化:
//建立一个类实例
MyClass myClass = new MyClass() { MyName = "ziqiu.zhang", Age = 99 };
//将对象实例转化为JSON字符串
string jsonString = JsonHelper.ObjectToJson < MyClass > (myClass);
//将JSON字符串转化为对象实例
myClass = JsonHelper.JsonToObject<MyClass>(jsonString);
有关服务器端程序的序列化和反序列化,还有许多种实现方式。JsonHelper类的实现仅供参考。
使用eval方法能够将JSON格式的字符转化为JavaScript对象:
var sJson = "{ name:'ziqiu.zhang' }";
eval(" var oName =" + sJson);
alert(oName.name); //输出 “ziqiu.zhang”
注意这里的sJson对象存储的是JSON格式的字符串,这个时侯字符串的内容尚未被解析成对象。使用eval方法能够将sJson字符串转化为对象存储在oName对象中。
eval 函数可计算某个字符串,并执行其中的的 JavaScript 代码。这让JavaScript摇身一变成为了动态语言,能够在运行时构造语句,经过eval执行,就像上面的解析JSON字符同样。
eval函数是有返回值的:
var iNum = eval("5+2");
alert(iNum); //输出 "7"
eval强大的功能让JavaScript开发人员能够发挥无穷的想象力。实如今一些高级语言中没法实现或者实现起来很困难的功能。要知道在C#中使用表达式目录树实现动态功能是多么“深奥”的一件事情。
由于逻辑运算符太经常使用太普通了,因此不少的程序员会认为无非就是NOT、AND、OR三个运算符,返回布尔值。可是在JavaScript中却不只仅如此,AND和OR还会返回对象。
(1)NOT运算符
NOT运算符用“!”表示,与逻辑 OR 和逻辑 AND 运算符不一样的是,逻辑 NOT 运算符返回的必定是 Boolean 值。与不少程序不一样之处在于,NOT运算符不只仅能够运算Boolean类型的对象,任何的定义了的对象均可以进行“!”运算。这里的定义了的对象主要是为了排除“未定义的undifined”对象,由于即便对象的类型是undifined,也有两种状况,一种是未定义,一种是未初始化。未初始化的undifined类型的对象是能够参与OR、AND和NOT逻辑运算的,只有未定义的undifined对象在逻辑运算中会出现脚本错误。这一点目前在一些权威教程的网站上面的解释都是有错误的,请你们尤为注意。
NOT运算符的规则以下:
w 若是运算数是对象,返回 false
w 若是运算数是数字 0,返回 true
w 若是运算数是 0 之外的任何数字,返回 false
w 若是运算数是 null,返回 true
w 若是运算数是 NaN,返回 true
w 若是运算数是 未初始化的undefined,返回true
w 若是运算数是未定义的undefined,发生错误
NOT运算符其实和if条件语句的行为是同样的,只是结果相反。
(2)AND运算符
AND 运算符用双和号(&&)表示。AND运算符的运算数若是都是Boolean类型的对象,那么运算规则就是若是有一个运算对象是false,则返回false。
JavaScript中的AND与NOT运算符最特别的地方是运算数不必定是Boolean类型,返回的也不必定是 Boolean 值,可能返回对象。
AND运算符的规则以下:
若是一个运算数是对象,另外一个是 Boolean 值,返回该对象。
w 若是两个运算数都是对象,返回第二个对象。
w 若是某个运算数是 null,返回 null。
w 若是某个运算数是 NaN,返回 NaN。
w 若是某个运算数是未初始化的 undefined,返回undefined。
w 若是运算数是未定义的undefined,发生错误
虽然上面描述了AND运算符的行为,可是在应用时经常会碰到下面的疑惑:
alert(false && null); //输出 "false"
alert(true && null); //输出 "null"
alert(null && false);//输出 "null"
alert(null && true);//输出 "null"
由于有一个对象是“null”,因此天然想到要应用规则“若是某个元素是null,返回null”,可是第一行语句却返回false。认为这些规则还具备“优先级”,实际上是由于AND运算符最本质逻辑规则:
若是第一个运算数决定告终果,就再也不计算第二个运算数。对于逻辑 AND 运算来讲,若是第一个运算数是 false,那么不管第二个运算数的值是什么,结果都不可能等于 true。
也就是说AND运算符遇到第一个运算数“false”就中止计算返回false了。而若是第一个运算数是“true”则还要计算第二个运算数,第二个运算数为“null”,因此根据规则返回null。
注意看规则中说明是“返回”的,说明运算到此运算数则已经返回结果,不会再继续计算其余运算数。因此当运算数是null,就已经返回告终果“null”,即便第二个运算数是false也不会参与运算。
(3)OR运算符
OR 运算符与C#中的相同,都由双竖线(||)表示。若是两个运算数都是boolean类型,OR运算符的运算逻辑和在几乎全部的语言中都是相同的,即有一个为“true”则返回true。
一样在JavaScript中,OR运算符的运算数不必定是Boolean类型,返回的也不必定是 Boolean 值,可能返回对象。
OR运算符的规则以下:
w 若是一个运算数是对象,另外一个是 Boolean 值,返回该对象。
w 若是两个运算数都是对象,返回第一个对象。
w 若是某个运算数是 null,返回 null。
w 若是某个运算数是 NaN,返回 NaN。
w 若是某个运算数是为初始化的undefined,则忽略此操做数。
w 若是某个运算数是未定义的undefined,发生错误。
当两个操做数都是对象时,OR和AND的规则是不一样的,是否感受记忆困难?其实能够将对象理解为“true”,在AND中遇到“true”时须要继续运算,因此继续计算到第二个操做数。返回的也是第二个操做数即最后一个参与运算的操做数。在OR运算时遇到“true”即返回,因此返回第一个对象也一样是最后一个操做数。
OR运算符对于未初始化的undefined类型的对象处理也比较特别,会忽略此操做数,也能够理解为看成“false”处理。若是还有第二个操做数则会继续运算。这一点和AND不一样,AND运算中若是碰到了未初始化的undefined,则马上返回undefined。
再次强调,未定义的undefined在全部的逻辑运算符中都会出现错误。要尽可能避免使用未定义的对象。
理解了JavaScript的逻辑运算符,就能够介绍一种在jQuery插件开发等地方常常会使用的JavaScript技巧。假设有下面的方法:
function tsetMethod(param1)
{
alert(param1);
//alert(param2); //输出 “error”
}
testMethod方法有一个参数param1,由于方法的签名就带有param1参数,因此param1和彻底未声明过的param2是不同的,当调用testMethod方法而且不传入参数是,会输出“undefined”。即param1此时在函数体内属于“未初始化的undefined”对象。
若是但愿在testMethod方法中为param1参数设置默认值“abc”,若是没有传入param1参数则使用默认值。有两种思路,一是能够经过typeof或者arguments对象判断是否传入了param1参数。
经过typeof判断:
function testMethod1(param1)
{
if (typeof param1 == "undefined")
{
param1 = "abc";
}
alert(param1);
}
或者经过arguments对象判断:
function testMethod2(param1)
{
if (arguments.length < 1)
{
param1 = "abc";
}
alert(param1);
}
再有能够借助OR运算符的规则,让语句变得更精简:
function testMethod3(param1)
{
alert(param1 || "abc")
}
由于param1是未初始化的undefined,而字符串“abc”是对象,因此会返回“abc”对象。而若是传入了param1则会返回param1。这个JavaScript技巧在处理可能为未初始化的undefined时经常用到。可是在使用可能未定义undefined对象时一样会发生语法错误。有些开发人员不经过参数传递数据,而是在方法中经过闭包来使用外层的变量,此时就会有使用未声明的undefined对象的危险。
本节介绍了JavaScript语言的一些精髓,指出了JavaScript开发人员的一些常见误区和知识盲点,好比理解ECMAScript,区分JavaScript语言和DOM, 理解DOM元素和HTML元素的区别等。由于篇幅有限不可能对全部的知识进行讲解。可是本章的学习将为后续学习jQuery打下坚实的基础,而且让Web开发人员的JavaScript知识体系获得升华。