《jQuery风暴》第2章 必须知道的JavaScript知识

第2章 必须知道的JavaScript知识

JavaScript是jQuery应用的基础,掌握JavaScript这门语言是使用jQuery的基础条件。本章不会全面细致的讲解JavaScript的全部, 而是讲解其精髓,这些知识可以提升大家的JavaScript内功。切忌,要修炼上乘的武功,必须要有深厚的内功基础,否则只可学到其招式而发挥不了功力。JavaScript实际上包括三部分:

w   ECMAScript 描述了该语言的语法和基本对象。

w   DOM 描述了处理网页内容的方法和接口。

w   BOM 描述了与浏览器进行交互的方法和接口。

本章将讲解ECMAScript和DOM的相关知识。

2.1  JavaScript基础

通常所说的JavaScript语法,实际上是指JavaScript中的ECMAScript部分。本节主要讲解JavaScript的语法和语意特性。

2.1.1 JavaScript与ECMAScript

很多人知道JavaScript,却不知道ECMAScript为何物。这就好比你知道本书的作者叫做张子秋,但是却不知道作者也属于人类一样。为什么要知道两者的关系呢?因为除了JavaScript,还有微软的JScript,以及Flash中的ActionScript, 这几种语言写法上有太多的相似之处。懂得ECMAScript,就能够清楚地理解这些语言为何如此的相似

什么是ECMAScript?下面是维基百科中对于ECMAScript的定义:

ECMAScript是一种由ECMA国际(前身为欧洲计算机制造商协会)通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,但实际上后两者是ECMA-262标准的实现和扩展。

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   各种脚本语言的关系

2.1.2 JavaScript中的值类型和引用类型

不同于C#,Java中多种多样的“类”,JavaScript是一个相对单纯的世界。JavasScript中也分为值类型和引用类型两大类,但是这两大类中都只含有很少的几种类型。

了解C#语言的都对值类型和引用类型不会陌生。JavaScript中有时也称为原始值(primitive value)和引用值(reference value)。

值类型:存储在栈(stack)中,一个值类型的变量其实是一个内存地址,地址中存储的就是值本身。

引用类型:存储在堆(heap)中,一个引用类型的变量的值是一个指针,指向存储对象的内存处。

2.1.3 JavaScript中的原始类型

时刻记着“值类型”和“引用类型”的区别有助于更好的理解语言的精髓。为了化繁为简, 虽然从理论上应该分为“值类型”和“引用类型”,又可以将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对象是一个引用类型!

2.1.4 undefined,null和typeof运算符

如果你对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"

2.1.5 变量声明

因为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是全局变量。

变量可以隐式声明,但是不可以不声明。如果一个变量既没有隐式声明,也没有显式声明,那么在使用时会发生对象未定义的错误。

2.1.6 JavaScript命名规范

因为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命名法。记住匈牙利命名法才是正确的方法。写代码也是一门艺术,只要有机会就应该做正确的事。

2.1.7 变量的作用域与闭包

变量的作用域就是变量作用的范围,只有在变量的作用域内才可以访问该变量,否则是无法访问此变量的。比如:

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中的闭包是非常强大的,可以用于执行复杂的逻辑。但是在使用时要时刻小心,因为闭包的强大也导致了它的复杂,使用闭包会让程序难以理解和维护。

2.2 悟透JavaScript中的function

function类型是JavaScript的灵魂,因为程序是由数据和逻辑两部分组成的,基本的数据类型可以存储各种数据,而function则用来存储逻辑。当然这只是将function当作“方法”来看待而已。实际上function类型有着更加强大的生命力。

2.2.1 使用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"

2.2.2 function的本质

虽然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的本质。

2.2.3 new 运算符

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引擎内部,方法无法直接调用。

2.2.4 function的arguments参数对象

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对象实现函数的伪重载。

2.2.5 理解this指针

在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工具函数”一章中的“修改函数上下文”一节。

2.3 JavaScript中的原型

JavaScript中的原型(Prototype)是JavaScript最特别的地方之一。无论是实现JavaScript的面向对象还是继承,使用prototype都必不可少。

2.3.1 使用原型实现JavaScript的面向对象

“原型”表示对象的原始状态,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并没有带有任何参数,虽然也可以传递参数进去,但是不要这么做。这是为了下一节讲解的使用原型链实现继承机制而作的准备。

可以在创立了对象以后再为其属性赋值。虽然麻烦了一点,但是代码简单,易于理解。

2.3.2 使用原型链实现继承

除了面向对象的声明方式,在面向对象的世界中最常使用的就是对象继承。在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的面向对象和继承。

2.4 
DOM

DOM太常用了,以至于很多人操作了DOM却不知道使用的是DOM。从概念上区分JavaScript核心语法,DOM和BOM是很有必要的,“学院派”作风能够让Web的知识体系结构更加清晰。

2.4.1 什么是DOM

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 1、2、3,每个标准都规定了不同的功能,并不是所有的浏览器都能实现最高级别的DOM标准。Firefox是目前实现DOM标准最优秀的浏览器,但仍在为了实现全部的DOM标准而努力。本书不对DOM做更细致的讲解,本节的目的让开发人员正确的理解DOM与 HTML DOM的关系。

2.4.2 操作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内部未被发现的错误。

2.4.3 DOM元素与HTML元素

通过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
>

</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操作样式和属性将在后面的章节中详细讲解。

2.5 其他JavaScript秘籍

要讲解JavaScript的全部,需要厚厚的一本书。既然无法全面讲解,就只能抽取一些武功秘籍传授给大家。除了上面讲解的JavaScript知识,本节再介绍一些JavaScript的关键知识点。

2.5.1 数据通信格式JSON

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类的实现仅供参考。

2.5.2 动态语言-eval

使用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#中使用表达式目录树实现动态功能是多么“深奥”的一件事情。

2.5.3 JavaScript中的逻辑运算符

因为逻辑运算符太常用太普通了,所以很多的程序员会认为无非就是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对象的危险。

2.6 小结

本节介绍了JavaScript语言的一些精髓,指出了JavaScript开发人员的一些常见误区和知识盲点,比如理解ECMAScript,区分JavaScript语言和DOM,
理解DOM元素和HTML元素的区别等。因为篇幅有限不可能对所有的知识进行讲解。但是本章的学习将为后续学习jQuery打下坚实的基础,并且让Web开发人员的JavaScript知识体系得到升华。

转自:http://www.cnblogs.com/zhangziqiu/archive/2009/05/04/jQuery-Learn-3.html

时间: 2024-11-08 21:14:52

《jQuery风暴》第2章 必须知道的JavaScript知识的相关文章

从零开始学习jQuery(剧场版) 你必须知道的javascript

原文:从零开始学习jQuery(剧场版) 你必须知道的javascript 一.摘要 本文是jQuery系列教程的剧场版, 即和jQuery这条主线无关, 主要介绍大家平时会忽略的一些javascript细节.  适合希望巩固javascript理论知识和基础知识的开发人员阅读.   二.前言 最近面试过一些人, 发现即使经验丰富的开发人员, 对于一些基础的理论和细节也常常会模糊. 写本文是因为就我自己而言第一次学习下面的内容时发现自己确实有所收获和感悟.  其实我们容易忽视的javascrip

(转)【推荐】初级.NET程序员,你必须知道的EF知识和经验

转自:http://www.cnblogs.com/zhaopei/p/5721789.html [推荐]初级.NET程序员,你必须知道的EF知识和经验 阅读目录 [本文已下咒.先顶后看,会涨工资的哦 :)] 注意:以下内容如果没有特别申明,默认使用的EF6.0版本,code first模式. 推荐MiniProfiler插件 工欲善其事,必先利其器. 我们使用EF和在很大程度提高了开发速度,不过随之带来的是很多性能低下的写法和生成不太高效的sql. 虽然我们可以使用SQL Server Pro

五个你必须知道的javascript和web debug技术

转:http://js8.in/2013/11/20/%E4%BA%94%E4%B8%AA%E4%BD%A0%E5%BF%85%E9%A1%BB%E7%9F%A5%E9%81%93%E7%9A%84javascript%E5%92%8Cweb-debug%E6%8A%80%E6%9C%AF/ 在前端开发中,调试技术是必不可少的技能,本文将介绍五种前端开发必备的调试技术. Weinre移动调试 DOM 断点 debugger断点 native方法hook 远程映射本地调试 Weinre 在移动上面

海淘必须知道的VPN知识

因为工作的关系,几乎天天都需要用到VPN,看到很多海淘的朋友对VPN了解不多,感觉很有必要为大家普及一下海淘所必须知道的一些网络相关的VPN知识,并尽量用通俗易懂的方式来解释,而不是一些专业术语.如果你对VPN很了解的话,此贴可以跳过了. VPN是什么? VPN是虚拟专业网络(Virtual Private Network)的英文缩写,其专业上的名词解释,可以直接看维基百科-VPN和百度百科-VPN的定义.但是,看起来很吃力.从字面上来看,VPN是虚拟的(virtual).私密的或者专有的(pr

面向对象编程思想(前传)--你必须知道的javascript

什么是鸭子类型 javascript是一门典型的动态类型语言,也就弱类型语言.那什么是鸭子类型:[如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子] var 鸭子 = { 走路: function () { }, 咕咕咕: function () { } } var 鹦鹉 = { 走路: function () { }, 咕咕咕: function () { } } 这只鹦鹉同样有"走路"和"咕咕咕"的方法,那在js的世界里就可以把它当成鸭子.可以这样调用: v

细说Cocos2d-JS——你必须知道的JavaScript

对于使用游戏引擎开发游戏而言,你选择何种语言并不重要,重要的是你对这个游戏引擎的理解和掌握,你对开发游戏的实践和感悟,毕竟一种游戏引擎常常支持很多语言开发,不同的项目使用的语言也可能很不一样. --有些人说 这个观点确实不无道理,但是,我依旧认为,无论是对于Cocos2d还是Unity3d抑或是其他的游戏引擎,无论你选择的是C++,C#,Lua还是JavaScript,在对游戏引擎深入研究之前或者之外,对你所选语言的研究显得尤为重要.这可能会给你带来质的提升. 一.JavaScript--想说爱

【推荐】初级.NET程序员,你必须知道的EF知识和经验

[本文已下咒.先顶后看,会涨工资的哦 :)] 注意:以下内容如果没有特别申明,默认使用的EF6.0版本,code first模式. 推荐MiniProfiler插件 工欲善其事,必先利其器. 我们使用EF和在很大程度提高了开发速度,不过随之带来的是很多性能低下的写法和生成不太高效的sql. 虽然我们可以使用SQL Server Profiler来监控执行的sql,不过个人觉得实属麻烦,每次需要打开.过滤.清除.关闭. 在这里强烈推荐一个插件MiniProfiler.实时监控页面请求对应执行的sq

面向对象编程思想(前传)--你必须知道的javascript(转载)

原文地址:http://www.cnblogs.com/zhaopei/p/6623460.html阅读目录 什么是鸭子类型 javascript的面向对象 封装 继承 多态 原型 this指向 call apply band js中的闭包 什么是高阶函数 在写面向对象编程思想-设计模式中的js部分的时候发现很多基础知识不了解的话,是很难真正理解和读懂js面向对象的代码.为此,在这里先快速补上.然后继续我们的面向对象编程思想-设计模式. 什么是鸭子类型 javascript是一门典型的动态类型语

《你必须知道的javascript(上)》- 2.this与对象原型

1.1 为什么使用this 随着你的使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用this则不会这样.当我们介绍对象和原型时,你就会明白函数可以自动引用合适的上下文对象有多重要. 1.2 关于误解 首先需要消除一些关于this的错误认识. 1.2.1 指向自身 先来看个例子: function foo(num) { console.log("foo: " + num); // 记录foo被调用的次数,这里this指向window this.count++; } fo