JavaScript 快速语法参考(全)
原文:JavaScript Quick Syntax Reference
协议:CC BY-NC-SA 4.0
一、使用 JavaScript
要开始试验 JavaScript,您应该安装一个支持这种语言的集成开发环境(IDE)。有很多不错的选择,比如 NetBeans、Eclipse、Visual Studio、括号。在本书中,我们将使用 NetBeans,它可以从 Netbeans.org 免费获得。确保你下载了一个包含 HTML 5 支持的包,因为它也包含对 JavaScript 的支持。
或者,您可以使用简单的文本编辑器(如记事本)进行开发,尽管这不如使用 IDE 方便。如果您选择这样做,只需创建一个文件扩展名为.html
的空文档,并在您选择的编辑器中打开它。
创建项目
安装 NetBeans 后,继续运行该程序。然后,您需要创建一个项目,该项目将管理 HTML 源文件和网站的其他资源。转到文件➤新项目以显示新项目窗口。从这里,在左侧框架中选择 HTML 5 类别,然后在右侧框架中选择 HTML 5 应用项目。单击 Next 按钮,您可以配置项目的名称和位置。完成后,单击“完成”让向导创建您的项目。
您现在已经创建了一个 HTML 5 项目。在“项目”面板(“➤项目”窗口)中,您可以看到该项目由一个名为“index.html”的文件组成,该文件位于站点的根文件夹中。该文件包含一些基本的 HTML 5 标记,可以进一步简化为如下所示的标记。
<!doctype html>
<html>
<head><title>JavaScript Test</title></head>
<body></body>
</html>
嵌入 JavaScript
将 JavaScript 插入 web 文档有两种方法。第一种是将代码放在一个script
元素中。一个文档可以有多个这样的元素,每个元素可以包含任意数量的 JavaScript 语句。
<script></script>
另一种更常见的方法是将代码包含在外部文件中,然后使用script
元素的src
属性链接到该文件。这样,几个文档就可以使用相同的代码,而不必在每一页上都重复代码。
<script src="mycode.js"></script>
按照惯例,.js
扩展名用于包含 JavaScript 代码的文件。要将此名称的新文件添加到 NetBeans 项目中,请右键单击“项目”面板中的站点根文件夹,然后选择“新建➤ JavaScript 文件”。在对话框中,将源文件命名为“mycode.js”,单击 Finish,该文件将被添加到您的项目中并为您打开。
出于实验的目的,您可以使用第一种嵌入方法来内联您的代码。然而,对于真实世界的应用,除了最简单的脚本之外,所有的脚本都应该是外部的。这使得代码更容易阅读和维护,因为它将 JavaScript 代码(页面行为)与 HTML 标记(页面内容)分开。由于浏览器缓存了外部文件,这也提高了网站的性能。
显示文本
在学习一门新的编程语言时,第一个示例 JavaScript 代码将显示一个“Hello World”文本字符串,这是很常见的。这是通过在 web 文档的body
元素中添加下面一行来实现的。
<script>
document.write("Hello World");
</script>
这段代码语句使用了属于document
对象的write
方法。该方法接受文本作为其参数,用双引号分隔。这些概念将在后面的章节中进一步探讨。
JavaScript 中的语句用分号分隔。如果语句后跟换行符,可以省略分号,因为这也被解释为语句分隔符。
document.write("Hello World")
完整的 web 文档现在应该是这样的。
<!doctype html>
<html>
<head><title>JavaScript Test</title></head>
<body>
<script>
document.write("Hello World");
</script>
</body>
</html>
若要查看该网页,请用 web 浏览器打开 HTML 文件。在 NetBeans 中,这是通过单击运行➤运行项目(F6)或单击工具栏上的绿色箭头来完成的。您可以从“运行➤集项目浏览器”中选择您的首选浏览器。在浏览器中查看文档时,一旦页面加载并显示文本字符串,就会执行脚本。
查看源代码
当浏览器打开时,您可以通过按 Ctrl + U 查看组成页面的源代码。该快捷方式适用于所有主流浏览器,包括 Chrome、Firefox 和 Internet Explorer (IE)。源代码窗口显示 HTML 标记,以及未解析的 JavaScript 代码。
以这种方式查看 web 页面的源代码提供了一个向其他 web 开发人员学习的好方法。每当你在一个网页上发现一个有趣的功能——无论它是用 HTML、CSS、JavaScript 还是其他语言制作的——页面源代码通常会揭示它是如何创建的。
浏览器兼容性
JavaScript 通常运行在客户端,在浏览器内部,而不是服务器端。因此,要执行代码,需要客户端在支持 JavaScript 的浏览器中查看文档。
因为 JavaScript 是最流行的客户端脚本语言,所以它几乎可以在今天使用的所有浏览器上工作。但是,客户端可能会选择禁用 JavaScript,因此无法保证客户端代码得到执行。即便如此,今天大多数网站都使用 JavaScript,而且许多网站都依赖它才能正常运行。
HTML 提供了noscript
元素,为不支持 JavaScript 或禁用了 JavaScript 的浏览器指定替代内容。
<noscript>
Please enable JavaScript for full functionality of this site.
</noscript>
控制台窗口
大多数浏览器都有一个开发控制台,允许您查看 JavaScript 代码中的信息,以便进行调试。要将信息打印到该控制台,可以使用 console 对象的 log 方法。
<script>
console.log("Hello Console");
</script>
在 Chrome、Firefox 和 Internet Explorer 中,打开控制台的过程是相同的。右键单击页面并选择 Inspect Element。这将打开开发窗口,从中可以找到 Console 选项卡。在 Internet Explorer 中,您需要首先启动开发窗口,然后刷新页面以查看控制台输出。
评论
注释用于向开发人员阐明代码,它们对代码的解析没有影响。JavaScript 具有单行(//
)和多行(/**/
)注释的标准符号,许多其他语言也使用这种符号。
<script>
// single-line comment
/* multi-line
comment */
</script>
和 HTML 一样,空白字符——比如空格、制表符和注释——在 JavaScript 中通常会被忽略。这让你在如何格式化你的代码上有很大的自由度。您使用的格式是个人喜好的问题。选择一种对你有意义的风格,并力求保持一致。
代码提示
如果您不确定一个特定的对象包含什么,或者一个函数接受什么参数,您可以利用一些 ide 中的代码提示,比如 NetBeans。通过按 Ctrl + Space 打开代码提示窗口,并提供对您能够在当前上下文中使用的任何代码实体的快速访问。这是一个强大的特性,你应该学会好好利用它。
Footnotes 1
netbeans . org/downloads/
。
二、变量
变量是用于存储数据(如数字或字符串)的容器,因此它们可以在脚本中多次使用。
声明变量
要创建一个变量,可以使用关键字var
,后跟一个名称,称为标识符。变量的一个常见命名约定是,除了第一个单词,每个单词最初都要大写。
var myVar;
可以使用等号给变量赋值,这叫做赋值操作符(=
)。这被称为分配或初始化变量。
myVar = 10;
声明和赋值可以合并成一条语句。当一个变量被赋值时,它就被定义了。
var myVar = 10;
通过使用逗号操作符(,
),有一种在同一个语句中创建多个变量的简便方法。
var myVar = 10, myVar2 = 20, myVar3;
一旦变量被声明,就可以通过引用变量名来使用它。例如,通过将标识符传递给document.write
方法,可以将变量的值打印到 web 文档中。
document.write(myVar); // "10"
请记住,变量标识符区分大小写,因此大写和小写字母有不同的含义。JavaScript 中的标识符可以包括字母、美元符号($
)、下划线(_
)和数字,但不能以数字开头。它们也不能包含空格或特殊字符,并且不能是保留关键字。
var _myVar32; // allowed
var 32Var; // incorrect (starts with number)
var my Var; // incorrect (contains space)
var var@32; // incorrect (contains special character)
var var; // incorrect (reserved keyword)
动态打字
JavaScript 是一种动态类型语言。因此,不需要指定变量的数据类型,任何变量都可以包含任何数据类型。
var myType = "Hi"; // string type
myType = 1.5; // number type
此外,变量的值将根据需要自动转换,这取决于使用它的上下文。
// Number type evaluated as string type
console.log(myType); // "1.5"
由于这些隐式类型转换,知道变量的基础类型并不总是必要的。尽管如此,了解 JavaScript 在后台处理的数据类型还是很有用的。这六种类型如下:数字、布尔、字符串、对象、未定义和空。
数字类型
JavaScript 对于整数和浮点数只有一种类型。整数可以用十进制(基数为 10)、八进制(基数为 8)或十六进制(基数为 16)来表示。整数值上的前导零表示它是八进制的,前导 0x(或 0X)表示十六进制的。十六进制整数可以包括数字 0–9 和字母 A–F,而八进制整数只能包括数字 0–7。下面的整数文字都代表同一个数,在十进制记数法中是 10。
var dec = 10; // decimal notation
var oct = 012; // octal notation
var hex = 0xA; // hexadecimal notation
浮点数可以用十进制或指数(科学)记数法来表示。指数记数法的用法是在十进制指数后加上E
(或e
)。
var num = 1.23;
var exp = 3e2; // 3*10² = 300
请记住,JavaScript 中的所有数字都在后台存储为双精度浮点数。
布尔类型
bool 类型可以存储布尔值,该值只能为 true 或 false。这些值由关键字true
和false
指定。
var myBool = true;
布尔值通常与条件语句和循环语句一起使用,这将在后面的章节中讨论。
未定义的类型
JavaScript 有一个名为undefined
的值,用来表示没有值。这是声明的变量在没有初始值的情况下得到的值。
var myUndefined;
console.log(myUndefined); // "undefined"
用于存储该值的数据类型也被命名为undefined
。这可以通过使用typeof
操作符来显示,它检索一个类型的字符串表示。
console.log(typeof myUndefined); // "undefined"
请记住,未定义的变量不同于未声明的变量。任何试图访问未声明变量的行为都将导致抛出ReferenceError
异常,从而中止脚本的执行。
console.log(myUndeclared); // throws a ReferenceError
零点类型
类型和值 null 表示没有值的对象。与可能由语言级行为导致的 undefined 相反,null 值总是通过代码设置的。它通常用作指示异常或错误情况的函数返回值。
var myNull = null;
console.log(myNull); // "null"
虽然 null 是一种数据类型,但是typeof
操作符会将这种类型作为一个对象来计算。这被认为是语言规范中的一个错误。
console.log(typeof myNull); // "object"
在布尔上下文中,null 和 undefined 都被评估为false
。以下示例使用 not 运算符(!
)将这些值强制转换为布尔类型。该运算符反转布尔结果,因此被两次用于检索原始值的布尔表示。
console.log(!!null); // "false"
console.log(!!undefined); // "false"
相反,在数值上下文中,null 表现为 0,而 undefined 导致整个表达式计算为 NaN。
console.log(null * 5); // "0"
console.log(undefined * 5); // "NaN"
特殊数值
JavaScript 有三个特殊的数值:Infinity、-Infinity 和 NaN。这些值用于表示在计算过程中发生了一些异常。例如,以下计算得出这三个值。
console.log(1 / 0); // "Infinity"
console.log(-1 / 0); // "-Infinity"
console.log(0 / 0); // "NaN"
值 NaN 是非数字的缩写,表示不可表示的数值。它是数学运算失败时通常使用的返回值。例如,从-1 中取平方根得到 NaN。这个计算可以使用全局 Math 对象的 sqrt 方法来执行。
var myNaN = Math.sqrt(-1);
console.log(myNaN); // "NaN"
console.log(typeof myNaN); // "number"
尝试在数值上下文中计算非数值也会导致 NaN。
console.log("Hi" * 3); // "NaN"
NaN 有一个奇怪的特性,它与任何其他值都不相等,包括另一个 NaN 值。要确定一个值是否为 NaN,可以使用全局 is NaN 函数。
console.log(NaN == NaN); // "false"
console.log(isNaN(myNaN)); // "true"
三、运算符
运算符是一个符号,它使脚本执行特定的数学或逻辑操作。JavaScript 中的运算符可以分为五种类型:算术、赋值、比较、逻辑和按位运算符。
算术运算符
算术运算符包括四种基本算术运算,以及用于获得除法余数的模数运算符(%
)。
x = 3 + 2; // 5 - addition
x = 3 - 2; // 1 - subtraction
x = 3 * 2; // 6 - multiplication
x = 3 / 2; // 1.5 - division
x = 3 % 2; // 1 - modulus (division remainder)
赋值运算符
第二组是赋值操作符。最重要的是赋值操作符(=
)本身,它给变量赋值。
x = 0; // assignment
组合赋值运算符
赋值运算符和算术运算符的一个常见用途是对变量进行运算,然后将结果保存回同一个变量中。使用组合赋值操作符可以缩短这些操作。
x += 5; // x = x+5;
x -= 5; // x = x-5;
x *= 5; // x = x*5;
x /= 5; // x = x/5;
x %= 5; // x = x%5;
递增和递减运算符
另一种常见的操作是将变量加 1 或减 1。这可以用增量(++
)和减量(--
)操作符来简化。
x++; // x = x+1;
x--; // x = x-1;
这两者都可以用在变量之前或之后。
x++; // post-increment
x--; // post-decrement
++x; // pre-increment
--x; // pre-decrement
无论使用哪个变量,变量的结果都是相同的。不同的是,后运算符在改变变量之前返回原始值,而前运算符先改变变量,然后返回值。
x = 5; y = x++; // y=5, x=6
x = 5; y = ++x; // y=6, x=6
比较运算符
比较运算符比较两个值,并返回 true 或 false。它们主要用于指定条件,即计算结果为 true 或 false 的表达式。
x = (2 == 3); // false - equal to
x = (2 === 3); // false - identical
x = (2 !== 3); // true - not identical
x = (2 != 3); // true - not equal to
x = (2 > 3); // false - greater than
x = (2 < 3); // true - less than
x = (2 >= 3); // false - greater than or equal to
x = (2 <= 3); // true - less than or equal to
严格相等运算符===
和!==
用于比较类型和值。这些是必要的,因为常规的等于(==
)和不等于(!=
)操作符会在比较操作数之前自动执行类型转换。
x = (1 == "1"); // true (same value)
x = (1 === "1"); // false (different types)
当不需要等于运算的类型转换功能时,使用严格比较被认为是一种好的做法。
逻辑运算符
逻辑运算符通常与比较运算符一起使用。如果左右两边都为真,则逻辑 and ( &&
)计算为真,如果左右两边都为真,则逻辑 or ( ||
)为真。对一个布尔结果取反,有一个逻辑非(!
)运算符。请注意,对于“逻辑与”和“逻辑或”,如果结果已经由左侧确定,则不会计算右侧。
x = (true && false); // false - logical and
x = (true || false); // true - logical or
x = !(true); // false - logical not
按位运算符
按位运算符可以处理组成整数的各个位。例如,右移位运算符(>>
)将除符号位之外的所有位向右移动,而零填充右移位(>>>
)将包括符号位在内的所有位向右移动。这两个运算符对正数的计算是相同的。
x = 5 & 4; // 101 & 100 = 100 (4) - and
x = 5 | 4; // 101 | 100 = 101 (5) - or
x = 5 ^ 4; // 101 ^ 100 = 001 (1) - xor
x = 4 << 1; // 100 << 1 =1000 (8) - left shift
x = 4 >> 1; // 100 >> 1 = 010 (2) - right shift
x = 4 >>>1; // 100 >>> 1 = 010 (2) - zero-fill right shift
x = ∼4; // ∼00000100 = 11111011 (-5) - invert
按位运算符也有组合赋值运算符。
x=5; x &= 4; // 101 & 100 = 100 (4) - and
x=5; x |= 4; // 101 | 100 = 101 (5) - or
x=5; x ^= 4; // 101 ^ 100 = 001 (1) - xor
x=4; x <<= 1; // 100 << 1 =1000 (8) - left shift
x=4; x >>= 1; // 100 >> 1 = 010 (2) - right shift
x=4; x >>>=1; // 100 >>> 1 = 010 (2) - right shift
请记住,JavaScript 数字存储为双精度浮点数。但是,位运算需要对整数进行操作,因此在执行位运算时,数字会临时转换为 32 位有符号整数。
运算符优先级
在 JavaScript 中,表达式通常从左到右计算。但是,当表达式包含多个运算符时,这些运算符的优先级决定了它们的求值顺序。下表显示了优先级顺序,其中优先级最低的运算符将首先被计算。同样的顺序也适用于许多其他语言,比如 PHP 和 Java。
| 在…之前 | 操作员 | 在…之前 | 操作员 | | --- | --- | --- | --- | | one | `() [] . x++ x--` | eight | `&` | | Two | `! ∼ ++x --x` | nine | `^` | | three | `* / %` | Ten | `|` | | four | `+ -` | Eleven | `&&` | | five | `<< >> >>>` | Twelve | `||` | | six | `< <= > >=` | Thirteen | `= op=` | | seven | `== != === !===` | Fourteen | `,` |举个例子,乘法比加法更难绑定,因此将在下面的代码行中首先进行计算。
x = 4 + 3 * 2; // 10
这可以通过将表达式中首先被求值的部分用括号括起来来说明。从表中可以看出,括号在所有运算符中优先级最低。
x = 4 + (3 * 2); // 10
四、数组
数组是用于存储值集合的数据结构。JavaScript 数组可以分为三类:数值型、关联型和多维型。这些数组之间的区别只是概念上的,因为 JavaScript 认为它们都是数组对象。
数字数组
数字数组用数字索引存储数组中的每个元素。可以通过以下方式使用数组构造函数创建空数组。
var a = new Array(); // empty array
要将元素添加到数组中,可以通过将元素的索引放在方括号中来一次引用一个元素。为元素赋值会自动为该元素创建空间,并增加数组的容量。请注意,数组索引从零开始。
a[0] = 1;
a[1] = 2;
a[2] = 3;
数组的初始容量可以通过向数组构造函数传递一个数值参数来指定。这可用于在预先知道数组将容纳的元素数量的情况下提高性能。
var b = new Array(3);
将多个参数或非数字参数传递给数组构造函数会将这些值赋给数组的第一个元素。
var c = new Array(1, 2, 3);
创建数组的另一种方法是将元素值放在方括号中,即所谓的数组文字。这是创建数组的最短且最常用的方法。
var d = [1, 2, 3];
省略数组中的值为创建空数组提供了一种快捷方式。
var e = []; // empty array
通过引用方括号内所需元素的索引来访问数组的内容。
var f = [1, 2, 3];
document.write(f[0] + f[1] + f[2]); // "6"
如果引用的元素不存在,则返回未定义类型的对象。
document.write(f[3]); // "undefined"
注意,就像常规变量一样,数组中的元素可以存储任何数据类型或其组合。
var mixed = [0, 3.14, "string", true];
关联数组
关联数组使用键字符串而不是数字索引来标识元素。要创建一个数组,首先声明一个空数组,然后将值赋给所需的键。
var g = new Array();
g["name"] = "Peter";
g["age"] = 25;
当访问这些元素时,记住键名是很重要的,因为这些数组不能用索引来访问。
document.write(g["name"] + " is " + g["age"]); // "Peter is 25"
JavaScript 中的数组是对象,它们的元素是对象属性。因此,关联数组的元素也可以用点符号来引用。
var h = new Array();
h.name = "Peter";
h.age = 25;
document.write(h.name + " is " + h.age); // "Peter is 25"
数字元素不能以这种方式访问,必须使用括号符号引用。
h[0] = 1;
可以在同一个数组中混合数字元素和关联元素,因为 JavaScript 不会区分它们。事实上,索引是作为键字符串存储在后台的,也可以这样引用。
h["0"] = 1;
多维数组
通过将数组作为元素添加到另一个数组中,可以使数组成为多维的。
var m = [ ["00","01"], ["10","11"] ];
多维数组可以有任意多个维度,但是很少需要两个以上的维度。对于每个额外的维度,添加另一组方括号。
document.write(m[1][1]); // "11"
与许多其他语言不同,JavaScript 中的多维数组不需要所有子数组的长度都相同。随着阵列容量的自动调整,也可以稍后在脚本中更改尺寸。
m[1][2] = "12";
数组对象
array 对象提供对许多用于操作数组的成员的访问。一个这样的成员是 length 属性,它检索或设置数组的当前容量。
var x = [1, 2, 3];
var len = x.length; // 3
x.length = 2; // deletes third element
document.write(x[2]); // "undefined"
IDE 中的代码提示提供了数组对象可用的成员列表。举个例子,pop 方法从数组中移除最后一个元素,push 将一个或多个元素追加到数组的末尾。
var y = [1, 2];
y.push(3); // add element to end of array
y.pop(); // remove last element
五、字符串
字符串由一系列用双引号或单引号分隔的字符组成。使用哪种符号是个人喜好的问题。
var s1 = "Hello";
var s2 = ' World';
有两个运算符可以对字符串进行操作。对于组合字符串,有一个加号(+
),在这个上下文中称为连接运算符。它有一个伴随的赋值操作符(+=
),将一个字符串附加到一个字符串变量的末尾。
var greeting = s1 + s2; // "Hello World"
s1 += s2; // "Hello World"
要在字符串中换行,必须添加反斜杠。该字符对换行符进行转义,换行符在 JavaScript 中通常表示语句的结束。反斜杠和换行符都从字符串的值中删除。
greeting = "Hello \
World";
转义字符
转义字符用于书写特殊字符,如新行和制表符。这些字符前面总是有一个反斜杠“\
”。例如,要在单引号字符串中插入单引号,该标记前面需要有反斜杠。
var s = 'It\'s'; // "It’s"
下表列出了 JavaScript 中可用的转义字符。
| 性格;角色;字母 | 意义 | 性格;角色;字母 | 意义 | | --- | --- | --- | --- | | `\n` | 新行 | `\f` | 换页 | | `\t` | 横表 | `\v` | 垂直标签 | | `\'` | 单引号 | `\"` | 双引号 | | `\b` | 退格键 | `\r` | 回车 | | `\\` | 反斜线符号 | | |除了这些转义字符之外,还有用于引用 Unicode 和 Latin-1 编码字符集的符号。Unicode 字符表示为“\u
”,后跟一个 4 位十六进制数。Latin-1 字符可以表示为以“\x
”开头的三位八进制数或两位十六进制数。如下图所示,换行符用四种不同的方式表示。
var line = '\n'; // escape code
line = '\012'; // octal Latin-1
line = '\x0A'; // hexadecimal Latin-1
line = '\u000A'; // hexadecimal Unicode
字符串和数字
在同时包含字符串和数值的表达式中,串联运算符会将数字转换为字符串。如果可能的话,其他数值运算符将尝试将字符串转换为数字,否则将计算为 NaN。
"5" + 5; // "55"
"5" - 5; // 0
"a" - 5; // NaN
用字符串表示的数值可以用parseInt
函数转换成整数。
parseInt("5") + 5; // 10
类似地,parseFloat
可以用来将字符串转换成浮点数。对于这两个函数,只返回字符串中的第一个数字,否则,如果第一个字符不是数字,则该方法返回 NaN。
parseFloat("3.14"); // 3.14
parseFloat("Hi"); // NaN
或者,一元加法运算符(+
)可用于执行字符串到数字的转换,方法是将加法符号放在字符串之前。
+"5" + 5; // 10
字符串对象
JavaScript 中的所有字符串都是字符串对象。因此,它们提供了对执行常见字符串操作时有用的属性和方法的快速访问。例如,字符串中的字符数可以使用 length 属性来确定。
var a = "Hello";
var len = a.length; // 5
当您键入点号来访问 string 对象的成员时,IDE 会提供代码提示,为您提供可用成员的完整列表。例如,toLowerCase
方法将字符串转换成小写字母。返回结果字符串,而不更改原始字符串。
var lower = a.toLowerCase(); // "hello"
JavaScript 将任何一段文本解释为 string 对象的一个实例。因此,可以直接在字符串常量上调用方法,就像在字符串变量上一样。
var upper = "abc".toUpperCase(); // "ABC";
六、条件语句
条件语句用于根据不同的条件执行不同的代码块。
如果语句
只有当括号内的表达式被求值为 true 时,if
语句才会执行。在 JavaScript 中,这不必是布尔表达式。它可以是任何表达式,在这种情况下,零、null、NaN、空字符串和未定义的变量被计算为 false,而所有其他值为 true。
if (x < 1) {
document.write("x < 1");
}
为了测试其他条件,if
语句可以被任意数量的else if
子句扩展。只有当前面的条件为假时,才会测试每个附加条件。
else if (x > 1) {
document.write("x > 1");
}
对于处理所有其他情况,可以在末尾有一个else
子句,如果所有先前的条件都为假,则执行该子句。
else {
document.write("x == 1");
}
如果只需要有条件地执行一条语句,可以省去花括号。但是,始终包含它们被认为是一种好的做法,因为它们可以提高代码的可读性。
if (x < 1)
document.write("x < 1");
else if (x > 1)
document.write("x > 1");
else
document.write("x == 1");
交换语句
switch
语句检查表达式和一系列 case 标签之间的相等性,然后将执行传递给匹配的 case。表达式可以是任何类型,并且将使用严格比较(===
)来匹配案例标签。开关可以包含任意数量的 case 子句,并且可以以处理所有其他情况的默认标签结束。
switch (x) {
case 0: document.write("x is 0"); break;
case 1: document.write("x is 1"); break;
default: document.write("x is not 0 or 1"); break;
}
注意,每个 case 标签后的语句以关键字break
结束,以跳过开关的其余部分。如果省略了 break,执行将一直进行到下一个案例,如果需要以相同的方式评估几个案例,这将非常有用。
三元运算符
除了if
和switch
语句之外,还有三元运算符(?:
,它为单个if else
语句提供了快捷方式。这个运算符有三个表达式。如果第一个为真,则计算并返回第二个表达式;如果为假,则计算并返回第三个。
// Ternary operator expression
y = (x === 1) ? 1 : 2;
在 JavaScript 中,该运算符也可以用作独立的代码语句,而不仅仅是表达式。
// Ternary operator statement
(x === 1) ? y = 1 : y = 2;
编程术语“表达式”指的是计算出一个值的代码,而语句是以分号或右花括号结束的代码段。
七、循环
循环语句用于多次执行一个代码块。JavaScript 有四种循环:while
、do-while
、for
和for-in
。与条件if
语句一样,如果代码块中只有一条语句,可以省略这些循环的花括号。
While 循环
只有当条件为真时,while
循环才会遍历代码块,并且只要条件保持为真,循环就会继续。
var i = 0;
while (i < 10) {
document.write(i++); // 0-9
}
这里的循环将打印出数字 0 到 9。请记住,条件只在每次迭代开始时检查。
Do-While 循环
除了检查代码块之后的条件之外,do-while
循环的工作方式与while
循环相同。因此,它将始终至少在代码块中运行一次。注意,这个循环以分号结束。
var j = 0;
do {
document.write(j++); // 0-9
} while (j < 10);
For 循环
for
循环在代码块中运行特定的次数。它使用三个参数。第一个初始化一个计数器,并且总是在循环之前执行一次。第二个参数保存循环的条件,并在每次迭代之前进行检查。第三个参数包含计数器的增量,在每次迭代结束时执行。
for (var k = 0; k < 10; k++) {
document.write(k); // 0-9
}
这个循环有几种变化,因为任何一个参数都可以省略。例如,如果省略第一个和第三个参数,它的行为方式与while
循环相同。
var k;
for (; k < 10;) {
document.write(k++); // 0-9
}
第一个和第三个参数也可以使用逗号运算符(,
)拆分成几个语句。
for (var k = 0, m = 0; k < 10; k++, m--) {
document.write(k+m); // 000... (10x)
}
属性获取数组中元素的数量。与for
循环一起,它可以用来遍历一个数组。
var a = [1, 2, 3];
for (var i = 0; i < a.length; i++) {
document.write(a[i]); // "123"
}
如果不需要跟踪迭代,for-in
循环提供了一个更短的遍历数组的语法。
For-in 循环
for-in
循环提供了一种简单的方法来遍历数组中的元素或对象中的属性。在每次迭代中,将下一个属性的键或索引赋给变量,循环继续迭代,直到遍历完对象的所有成员。
var colors = ["red","green","blue"];
for (i in colors) {
document.write(colors[i] + " "); // "red green blue"
}
中断并继续
有两个跳转语句可以在循环内部使用:break
和continue
。break
关键字结束循环结构,而continue
跳过当前迭代的剩余部分,并在下一次迭代的开始处继续。
for (var i = 0; i < 10; i++)
{
if (i == 2) continue; // start next iteration
else if (i == 5) break; // end loop
document.write(i); // "0134"
}
要中断当前循环之上的循环,必须首先标记外部循环,方法是在外部循环前添加一个名称,后跟一个冒号。有了这个标签,它现在可以用作break
语句的参数,告诉它从哪个循环中退出。这也适用于continue
关键字,以便跳到指定循环的下一次迭代。
myloop:
for (var i = 0; i < 10; i++)
{
var j = 0;
while (++j < 10)
{
break myloop; // end for loop
}
}
八、函数
函数是可重用的代码块,只有在被调用时才会执行。它们允许开发人员将他们的脚本分成更小的部分,更容易理解和重用。
定义函数
要创建一个函数,可以使用 function 关键字,后跟一个名称、一组括号和一个代码块。函数的命名惯例与变量相同——使用一个描述性的名称,除了第一个单词以外,每个单词都要大写。
function myFunc()
{
document.write("Hello World");
}
这个函数只是向 web 文档显示一个文本字符串。函数代码块可以包含任何 JavaScript 代码,包括其他函数定义。
调用函数
一旦定义了一个函数,就可以在文档的任何地方调用它,只需键入它的名字,后面加上一组括号。函数名区分大小写,所以字母的大小写需要一致。
myFunc(); // "Hello World"
即使函数定义稍后出现在脚本中,也可以调用函数。这是因为 JavaScript 中的声明是在代码执行之前处理的。
foo(); // ok
function foo() {}
函数参数
函数名后面的括号用于向函数传递参数。为此,必须首先将相应的参数添加到函数的参数列表中。这些参数可以作为常规变量在函数中使用。
function sum(a, b) {
var sum = a + b;
console.log(sum);
}
一个函数可以被定义为接受任意数量的参数。调用该函数时,参数以逗号分隔列表的形式提供。在此示例中,该函数接受两个数值参数,并显示它们的总和。
sum(2, 3); // "5"
像变量一样,函数参数也没有在声明中指定类型。因此,不会自动执行类型检查,函数参数也不限于任何特定的数据类型。
可变参数列表
允许调用一个函数,其参数个数与定义的参数个数不同。如果调用函数时使用的参数较少,那么剩余的参数将被设置为未定义。
function say(message) {
console.log(message);
}
say(); // "undefined"
当调用一个函数的参数比它的定义中的多时,多余的参数将没有名字。可以通过类似数组的 arguments 对象引用这些额外的参数,该对象包含传递给函数的所有参数。
function say() {
console.log(arguments[0]);
}
say("Hello"); // "Hello"
arguments 对象可用于创建 varadic 函数,这些函数能够处理不同数量的参数。举个例子,可以将任意数量的参数传递给下面的函数。该函数遍历参数,并将它们组合成一个字符串,输出到控制台。
function combine() {
var result = "";
for (var i = 0; i < arguments.length; i++) {
result += arguments[i];
}
console.log(result);
}
combine(1, 2, 3); // "123";
返回语句
Return 是一个跳转语句,它使函数退出,并将指定的值返回到调用该函数的地方。举例来说,下面的函数返回其两个参数的和。这个函数又可以作为参数传递给另一个函数,在那里它将计算出结果数。
function getSum(a, b) {
return a + b; // exit function and return value
}
console.log( getSum(1, 2) ); // "3"
return 语句也可以用来在到达 end 块之前退出函数,而不返回任何特定的值。没有返回值的函数将隐式返回 undefined。
function foo() {
return; // exit function
}
console.log( foo() ); // "undefined"
就像变量和参数一样,返回值不进行类型检查。函数需要被适当地文档化,以便函数的用户知道他们的输入和输出应该是什么。
参数传递
参数通过值传递给函数。对于基本类型,这意味着只有值的副本被传递给函数。因此,以任何方式更改参数都不会影响原始变量。
function set(y) {
y = 1;
}
var x = 0;
set(x); // copy of value passed
console.log(x); // "0"
当对象类型用作参数时,传递的是对该对象的引用。这允许函数对原始对象的属性进行更改。
function addFruit(basket) {
basket[0] = "Apple";
}
var fruits = [];
addFruit(fruits); // copy of reference passed
console.log( fruits[0] ); // "Apple"
将新对象赋给参数不会影响函数外部的原始对象。这是因为赋值改变了参数的值,而不是对象的一个属性值。
function makeFruit(basket) {
basket = [ "Apple" ];
}
var fruits = [];
makeFruit(fruits);
console.log( fruits[0] ); // "undefined"
函数表达式
JavaScript 中的函数是对象,特别是函数对象。因此,它们可以被赋给变量并传递给其他函数,就像任何其他对象一样。当一个函数以这种方式使用时,它被称为函数表达式,而不是函数声明。可以使用正常的赋值语法(包括分号)将函数表达式赋给变量。
var say = function foo(message)
{
console.log("Hello " + message);
};
当调用一个函数对象时,引用的是变量名而不是函数名。
say("World"); // "Hello World"
可以在函数内部使用函数名来引用它自己,但是函数名在其他情况下是不必要的。因此,该名称通常被省略。然后,函数表达式被称为匿名函数。
var say = function(message)
{
console.log("Hello " + message);
};
函数表达式通常被用作回调函数,或者被传递给其他函数,或者从其他函数返回。它们允许非常简洁地编写代码,因为函数可以内联,而不必在其他地方定义它。为了说明这一点,下面的例子使用了window.setTimeout
函数,它采用另一个函数和一个数字作为参数。该数字指定在调用函数参数之前等待的毫秒数。
// Call anonymous function after one second
window.setTimeout(function() { console.log("Hello") }, 1000);
范围和寿命
变量的作用域指的是可以使用该变量的代码区域。JavaScript 中的变量既可以全局声明,也可以局部声明。全局变量是在任何函数之外声明的,可以从文档中的任何地方访问。另一方面,局部变量是在函数内部声明的,并且只能在该函数内部访问。
var globalVar = 0; // global variable
function foo() {
var localVar = 0; // local variable
}
局部变量的生存期是有限的。全局变量将在脚本运行期间保持分配状态,而局部变量将在其函数执行完毕后被销毁。
console.log(globalVar); // "0"
foo();
console.log(localVar); // throws a ReferenceError
当作用域中的两个变量同名时,就会出现名称冲突。更多的内部作用域具有优先权,因此最里面的作用域具有最高的优先权,而最外面的作用域具有最低的优先权。
var a = "global";
function foo() {
var a = "local"; // overshaddows global variable
console.log(a);
}
foo(); // "local"
console.log(a); // "global"
与许多其他语言不同,JavaScript 中的代码块没有自己的作用域。因此,在控制结构代码块(如循环或条件语句)中定义的变量在代码块结束时不会被销毁。
if(true) {
var x = 10; // global variable
}
console.log(x); // "10"
还有一种创建变量的替代方法,即在不使用 var 关键字的情况下为未声明的变量赋值。这将隐式地将变量声明为全局变量,即使它是在函数中声明的。
function foo() {
a = 5; // global variable
}
foo();
console.log(a); // "5"
以这种方式错误地引入或覆盖全局变量是常见的错误来源。因此,建议始终使用 var 关键字显式声明变量。
var a = 10;
foo(); // replaces value of global variable
console.log(a); // "5"
像函数声明一样,显式变量声明也在脚本执行之前进行处理。因此,变量可以在声明之前在代码中引用。
console.log(a); // "undefined"
var a = "defined";
console.log(a); // "defined"
对于隐式声明的变量来说,这种行为是不同的,因为在为变量赋值的代码运行之前,这些变量是不存在的。
console.log(b); // throws a ReferenceError
b = "defined"; // never executes
九、对象
对象是被称为属性的命名值的集合。属性可以是保存对象状态的变量,也可以是定义对象功能的函数。对象的吸引力在于它们在提供功能的同时隐藏了它们的内部工作。你需要知道的只是一个对象能为你做什么,而不是它是如何做的。
对象属性
可以使用 new 指令以下面的显式方式创建一个空对象。
var box = new Object();
使用点符号或数组符号将对象的属性赋给时,会自动创建这些属性。
box.x = 2;
box["y"] = 3;
同样,属性可以用这两种方式之一引用。
console.log(box.x); // "2"
console.log(box["y"]); // "3"
可以使用 delete 指令从对象中删除属性。这不能用常规变量来完成,只能用对象属性来完成。
box.z = 1; // add property
delete box.z; // delete property
要检查对象是否包含属性,可以使用 in 运算符。然后,属性名被指定为字符串。
console.log("x" in box); // "true"
console.log("z" in box); // "false"
对象方法
函数可以以属性的形式添加到对象中。这种功能被称为方法。当引用函数声明时,括号被省略。
box.getArea = myArea;
function myArea() { return this.x * this.y; }
这里使用的this
关键字是对当前拥有该函数的对象的引用。如果在对象上下文之外调用函数,关键字将改为引用window
对象。为了防止这种脱离上下文的调用,最好使用函数表达式内联函数。这也防止了函数名不必要地弄乱了全局名称空间。
box.getArea = function() { return this.x * this.y; };
一旦绑定到对象,就可以以熟悉的方式调用该方法。this
关键字在这里指的是 box 对象,它具有前面定义的属性x
和y
。
console.log( box.getArea() ); // "6"
对象文字
创建对象的一种更简单的方法是使用对象文字,它由一组花括号分隔。创建空对象时,括号是空的。
var box = {};
使用对象文字的优点是,可以在创建对象时设置属性,方法是将它们包含在花括号中。属性的每个名称-值对由冒号分隔,每个属性依次由逗号分隔。
var box = {
x: 2,
y: 3,
getArea: function() { return this.x * this.y; }
};
如果只需要一个对象实例,那么对象文字就很有用。如果需要多个实例,可以使用函数构造函数。
构造函数
可以从构造函数中创建对象。通过允许从一组定义中创建一个对象的多个实例,它们提供了其他语言中的类所提供的功能。按照惯例,打算用作对象构造函数的函数以大写字母开头,以提醒它们的用途。
function Box(x, y) {
this.x = x;
this.y = y;
this.getArea = function() { return this.x * this.y; };
}
为了从这个构造函数中实例化一个或多个对象,用new
指令调用这个函数。就像任何其他函数一样,构造函数可以接受参数,如下例所示。
var b1 = new Box(1, 2);
var b2 = new Box(3, 4);
每个对象实例都包含自己的一组属性,这些属性可以保存与其他实例不同的值。
console.log(b1.x); // "1"
console.log(b2.x); // "3"
先前定义的构造函数包括在另一个函数中声明的函数。这种嵌套函数可以访问其父函数中定义的变量,因为它形成了一个包含外部函数范围的所谓闭包。这种对象创建模式的一个优点是它提供了信息隐藏。例如,下面的示例有一个方法,它使用一个局部变量来记录该方法被调用的次数。这个特性类似于基于类的语言中的私有属性,因为局部变量只在构造函数中可见。
function Counter(x, y) {
var count = 0;
this.printCount = function() { console.log(count++); };
}
var c = new Counter();
c.printCount(); // "0";
c.printCount(); // "1";
这种对象创建模式的一个小缺点是每个实例都有自己的printCount
方法,这会增加每个对象消耗的内存。避免这种情况的另一种模式是利用继承将方法添加到对象的原型中。
遗产
一个对象可以从另一个对象继承属性。这为对象重用其他对象的代码提供了一种方式。专门化的对象通常称为子对象,更一般的对象称为父对象。
在 JavaScript 中,继承是通过原型继承模型实现的。在这个模型中,每个对象都有一个内部属性,作为到另一个对象的链接,称为它的原型。考虑下面的对象。
var myObj = new Object();
这个对象从内置的Object
构造函数继承属性。当请求一个对象不直接包含的属性时,JavaScript 将自动搜索继承链,直到找到请求的属性或到达链的末端。
// Add a property to myObj
myObj.x = 5;
// Call a method from Object
myObj.hasOwnProperty("x"); // true
链中的最后一个链接总是内置的Object
构造函数,它的原型链接又是 null,这标志着链的结束。可以使用__proto__
属性检索对对象原型链接的引用。这不要与构造函数的 prototype 属性混淆,它是函数的文字对象表示,当创建该函数的新实例时,它被分配给__proto__
。
myObj.__proto__ === Object.prototype; // true
myObj.__proto__.__proto__ === null; // true
通过扩展原型链来实现继承。这可以在构造函数中完成,方法是将prototype
属性设置为要继承的对象。
function Parent() { this.a = "Parent"; }
function Child() { }
Child.prototype = new Parent();
var child = new Child();
console.log(child.a); // "Parent"
类型检查
您可以通过比较对象的__proto__
链接和构造函数的prototype
属性来手动确认对象的类型。
child.__proto__ === Child.prototype; //
true
JavaScript 还提供了instanceof
操作符。该操作符在原型链中导航,如果左侧对象指向原型链中右侧的构造函数,则返回 true。
child instanceof Child; // true
child instanceof Parent; // true
child instanceof Object; // true
对象创建
执行继承的另一种方式是通过Object.create
方法。此方法提供了一种更简单的实现继承的方法,它允许一个对象直接从另一个对象继承,而无需使用额外的构造函数。
var p = new Parent();
var c1 = Object.create(p); // inherit from p
方法的第二个可选参数允许您使用特殊的表示法初始化对象的属性。
var c2 = Object.create(p, { name: { value: "Child 2" } } );
console.log(c2.name); // "Child 2"
对象的prototype
属性可以用来动态地向原型添加属性。这将导致链接到该原型的所有对象都继承新的属性。
Parent.prototype.x = "new property";
console.log(c2.x); // "new property"
如本章所示,JavaScript 在创建和使用对象时提供了很大的灵活性。选择使用哪种方法往往归结为个人喜好的问题。
十、文档对象模型
文档对象模型或 DOM 是一个编程接口,它描述了 web 文档的所有元素以及它们之间的相互关系。通过这个接口,JavaScript 代码可以与 web 文档进行交互。
DOM 节点
在 DOM 模型中,web 文档的内容以由节点组成的树状结构表示。通过了解这些节点是如何组织的,就有可能动态地更改文档的任何部分。考虑下面的 HTML 标记。
<p>My paragraph</p>
这个标记在 DOM 树中创建了两个节点:一个元素节点和一个文本节点。元素节点可以有子节点,这里的文本节点是段落元素的子节点。相反,段落节点是文本节点的父节点。转到下一个示例,有四个节点。
<div id="mydiv">
<!-- My comment -->
</div>
div 元素节点包含三个节点:两个文本节点和一个注释节点。额外的文本节点来自本例中注释前后的空格(空格、制表符和换行符)。查看网页时,这四个节点都是不可见的。
每个元素可以有一个可选的 id 属性,在本例中应用于 div 元素。因为这个属性必须是惟一的,所以它提供了一种在 DOM 树中选择特定节点的便捷方式。注意,DOM 规范也认为属性是节点;但是,在导航 DOM 树时,属性节点被视为元素节点的属性。
选择节点
document 对象表示 web 文档,并提供了几种访问 DOM 树和检索节点引用的方法。最常见的方法是使用getElementById
方法。此方法返回对具有指定唯一 id 的元素的引用。使用这个方法,可以检索前面示例中的 div 元素节点。
var mydiv = document.getElementById("mydiv");
也可以用getElementsByTagName
方法通过标签名选择元素。此方法检索该类型的所有元素的类似数组的集合。
var mydivs = document.getElementsByTagName("div");
另一种不太常见的选择元素的方式是通过class
属性。由于多个元素可以共享同一个类名,因此该方法也返回一个集合。除 IE < 9 之外,所有现代浏览器都支持它。
var myclasses = document.getElementsByClassName("myclass");
getElementById
方法只能从文档对象中调用,但是另外两个方法可以从 DOM 树中的特定节点调用,以便只搜索该节点的子节点。例如,下面的代码检索 body 元素下的所有元素节点。
var myelements = document.body.getElementsByTagName("*");
除了 body 节点,document 对象还具有检索对html
和head
节点的引用的属性。
var htmlnode = document.documentElement;
var headnode = document.head;
var bodynode = document.body;
也可以使用querySelector
方法定位元素节点。该方法返回匹配指定 CSS 选择器的第一个元素,在本例中是 class 属性设置为myclass
的第一个元素。
var mynode = document.querySelector(".myclass");
类似地,querySelectorAll
方法检索匹配给定 CSS 查询的所有元素节点的集合。除了 IE < 9,所有现代浏览器都支持这两种查询方式。
var mynodes = document.querySelectorAll(".myclass");
如果找不到元素,则返回 null。这种行为在所有 DOM 方法中都是一致的。
遍历 DOM 树
一旦选择了一个节点,就有许多属性允许相对于该节点遍历 DOM 树。下面的列表可以用来说明。
<ul id="mylist">
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
对这个无序列表节点的引用是通过它的 id 来检索的。
var mylist = document.getElementById("mylist");
此节点下的元素节点可通过 children 集合获得。第一个列表项也可以使用firstElementChild
属性来检索。
var first = mylist.children[0];
first = mylist.firstElementChild;
同样,可以通过 children 集合或lastElementChild
属性访问最后一个节点。
var third = mylist.children[2];
third = mylist.lastElementChild;
可以使用previousElementSibling
和nextElementSibling
属性将 DOM 树左右导航到相邻节点。在这种情况下,兄弟指的是共享同一个父节点的节点。
var second = first.nextElementSibling;
second = third.previousElementSibling;
这四个属性——firstElementChild
、lastElementChild
、nextElementSibling
和previousElementSibling
——除了 IE < 9,所有现代浏览器都支持。对于完整的浏览器支持,可以使用以下属性来代替:firstChild
、lastChild
、nextSibling
和previousSibling
。请记住,这些属性也考虑文本和注释节点,而不仅仅是元素节点。
parentNode
属性引用父节点。与其他属性一起,它们允许在所有四个方向上遍历 DOM 树:上、下、左、右。
mylist = first.parentNode;
子集合只包含元素节点。一个例外是在 9 之前的 IE 版本中,这个集合也包括注释节点。如果需要所有节点类型,则使用childNodes
集合。该集合包含所有子节点,包括元素、文本和注释节点。再一次,IE < 9 的行为与其他浏览器不同,在childNodes
集合中没有包含纯空白的文本节点。
mylist.childNodes[0]; // whitespace text node
mylist.childNodes[1]; // li node
mylist.childNodes[2]; // whitespace text node
对于元素节点,nodeName
和tagName
属性都包含大写字母的标签名称。它们可以用来测试元素节点的标记名。
if (mydiv.nodeName == "DIV")
console.log(mydiv.tagName); // "DIV"
虽然tagName
属性专门用于元素节点,但是nodeName
属性对于任何节点类型都是有用的。例如,注释节点的计算结果为“#comment”,文本节点的计算结果为“#text”。
mylist.childNodes[0].nodeName; // #text
mylist.childNodes[1].nodeName; // LI
mylist.childNodes[2].nodeName; // #text
创建节点
DOM 中的节点可以动态地添加、删除或更改。举例来说,一个新的列表项将被添加到先前的列表中。第一步是分别使用createElement
和createTextNode
方法创建元素和文本节点。
var myitem = document.createElement('li');
var mytext = document.createTextNode("New list item");
然后,使用 list item 节点上的appendChild
方法,将文本节点添加到元素节点。
myitem.appendChild(mytext);
接下来,使用相同的方法将列表项节点添加到无序列表中。这将导致新列表项出现在页面上。
mylist.appendChild(myitem);
appendChild
方法添加它的节点参数作为调用它的元素节点的最后一个子节点。为了在其他地方插入节点,使用了insertBefore
方法。此方法采用第二个节点参数,在该参数之前插入新节点。
mylist.insertBefore(myitem, mylist.children[0]);
一个节点不能同时出现在两个地方,因此该操作会删除之前添加到列表末尾的节点,而将其放在列表的开头。要添加另一个类似的节点,可以首先使用cloneNode
方法复制元素。此方法采用一个布尔参数,该参数指定元素的后代节点是否也将被复制。
var newitem = myitem.cloneNode(false);
接下来,这个节点被添加到列表的末尾,但是因为它是在没有后代的情况下克隆的,所以它没有文本节点,所以在文档中显示为空列表元素。
mylist.appendChild(newitem);
如前所示,可以使用createTextNode
和appendChild
方法创建和链接文本节点。一个更短的替代方法是修改innerHTML
属性,它代表元素的 HTML 内容。
newitem.innerHTML = "<b>Another</b> new list item";
该属性允许自动创建元素和文本节点。另一个有用的类似属性是textContent
。此属性表示去除了任何 HTML 标记的元素内容。
newitem.textContent; // "Another new list item"
newitem.innerHTML; // "<b>Another</b> new list item"
删除节点
可以使用removeChild
方法删除一个节点。该方法返回对已移除节点的引用:在本例中,是列表中的最后一个子节点。请记住,在 JavaScript 中捕捉返回值是可选的。
var removedNode = mylist.removeChild(mylist.lastElementChild);
另一种删除节点的方法是用不同的节点替换它。这是通过replaceChild
方法完成的,该方法也返回被替换的节点。下面的代码用以前移除的节点替换列表的第一个子节点。
mylist.replaceChild(removedNode, mylist.firstElementChild);
属性节点
属性节点可以通过其包含元素来访问,而不是作为该元素的子节点。为了便于说明,这里有一个带有id
属性的段落,以便于选择。
<p id="myid">My paragraph</p>
它的元素节点以熟悉的方式选择。
var mypara = document.getElementById("myid");
setAttribute
方法向被引用的元素添加一个属性,或者替换它的值,如果它已经存在的话。它有两个参数:属性和值。
mypara.setAttribute("class","myclass");
为了检索属性的值,使用了getAttribute
方法。在检索属性之前,这里使用hasAttribute
方法执行检查以确保它存在。
if (mypara.hasAttribute("class"))
console.log(mypara.getAttribute("class")); // "myclass"
属性自动与元素节点的属性同步。这为设置和获取属性提供了一种不太冗长的方式。属性和它们对应的属性共享相同的名称,除了 class 属性的属性名为className
。这是因为 class 是 JavaScript 中的保留关键字。
console.log(mypara.id); // "myid"
console.log(mypara.className); // "myclass"
使用style
属性可以动态改变元素的 CSS 属性。一种方法是直接修改属性。这将覆盖以前通过该属性设置的任何内联样式。
mypara.setAttribute("style", "background-color: yellow;");
相反,要给元素添加新的样式,可以改变style
对象的属性。这些属性与其对应的 CSS 属性具有相同的名称,只是删除了所有连字符,并且第一个单词后面的每个单词都大写。
mypara.style.backgroundColor = "yellow";
要恢复样式更改,您只需通过将该属性设置为空字符串来清除它。
mypara.style.backgroundColor = "";
十一、事件
事件是在用户、网页和浏览器之间的交互中发生的事件。事件处理使脚本能够检测这些事件并做出反应,从而使网页变得具有交互性。
事件处理
处理事件有三个步骤。首先,您需要定位将接收事件的元素。事件总是发生在 DOM 树中元素节点的上下文中。在此示例中,当单击以下超链接时,将发生该事件。
<a href="
http://www.google.com
下一步是创建事件处理程序,这是事件发生时将执行的代码。该事件处理程序通常由一个函数组成,在这种情况下,该函数为用户显示一个带有消息的警告框。
function myEventHandler() {
alert("Event triggered");
}
最后,最后一步是为要处理的特定事件注册事件处理程序。这里选择了 link 元素节点,事件处理程序以下面的方式注册到它的onclick
事件中。
var mylink = document.getElementById("mylink");
mylink.onclick = myEventHandler;
当用户点击这个链接时,调用事件处理程序,弹出警告框。一旦盒子被关闭,默认的事件处理程序就会接管,链接就会像平常一样被跟踪。若要移除事件处理程序,请将属性设置为 null。
mylink.onclick = null;
这被称为事件注册的传统模型。另一种更短的注册方法是内联模型,它使用事件属性将事件处理程序直接附加到产生事件的 HTML 元素上。
<a href="
http://www.google.com
请注意,事件处理程序的括号包含在内联模型中,而不是传统模型中。使用这个模型的一个更简单的方法是内联这个函数。
<a href="
http://www.google.com
内联事件处理程序可以包含多个语句。在下面的示例中,return false 语句被添加到事件处理程序中。这将阻止默认的浏览器操作发生,在这种情况下,这意味着该链接将不再被跟踪。
<a href="
http://www.google.com
所有现代浏览器都支持传统模型和内联模型。传统模型通常更可取,因为它允许通过代码添加、更改和删除事件处理程序,并且它完全将 JavaScript 与 HTML 分开。
W3C 在 DOM level 2 规范中标准化了第三种注册事件的模型。在这个 W3C 模型中,使用addEventListener
方法添加了一个事件处理程序。这个方法有三个参数:事件类型、事件处理程序和一个布尔值,我们将在后面看到。
mylink.addEventListener("click", myEventHandler, false);
注意,在 W3C 模型中,事件的前缀“on”被省略了,所以“onclick”就变成了“click”要删除一个事件处理程序,removeEventListener
与同样的三个参数一起使用。
mylink.removeEventListener("click", myEventHandler, false);
W3C 模型的主要优点是可以为同一个事件和同一个元素节点注册多个事件处理程序。一个缺点是 IE<9 不支持,这使得它比其他两种方法的跨浏览器兼容性差。
事件对象
当事件被触发时,浏览器向事件处理程序传递一个参数,将事件表示为一个对象。可以通过向事件处理程序添加参数来访问该对象。
function myEventHandler(e) { }
这个对象是访问事件信息的 W3C 方法。IE<9 没有传递事件对象参数,而是有一个全局的window.event
对象,代表最后触发的事件。为了跨浏览器的兼容性,可以将下面一行添加到处理程序的开头,以确保在所有浏览器中都能检索到正确的事件对象。
function myEventHandler(e) {
if (!e) var e = window.event;
}
事件对象通过其属性提供关于事件的附加信息。不同的事件有不同的属性,但是所有的事件对象都有type
属性。该属性保存一个标识事件的字符串,例如 onclick 事件的“click”。
console.log(e.type); // "click"
大多数事件也有一个目标,它是对触发事件的元素节点的引用。在前面的例子中,这指的是锚元素。
console.log(e.target.tagName); // "A"
在 IE<9 上,事件对象有一个srcElement
属性,而不是target
属性。这里可以看到跨浏览器兼容的目标检索方式。
var target = e.target || e.srcElement;
事件传播
大多数 DOM 事件都有事件传播,这意味着内部元素触发的事件也会触发外部元素。举例来说,下面是一个嵌套在div
元素中的段落元素。
<div id="outer">Outer element
<p id="inner">Inner element</div>
</div>
下面的代码使用传统模型为这两个元素注册 click 事件。
var inner = document.getElementById("inner");
inner.onclick = function() { alert("Inner"); }
var outer = document.getElementById("outer");
outer.onclick = function() { alert("Outer"); }
在内部元素上触发一个事件后,它继续触发以嵌套顺序附加到父元素的任何事件处理程序。因此,单击内部元素将首先显示“内部”消息,然后是“外部”消息。这种默认的事件顺序称为冒泡。
相反的事件顺序称为捕获。IE<9 只有冒泡顺序,但其他所有现代浏览器都是先捕获再冒泡的方式处理事件处理程序。为了注册捕获阶段的事件处理程序,addEventListener
的最后一个参数被设置为 true。在下面的示例中,当单击段落元素时,数字将按顺序打印。
outer.addEventListener("click", function() { console.log("1"); }, true);
inner.addEventListener("click", function() { console.log("2"); }, true);
inner.addEventListener("click", function() { console.log("3"); }, false);
outer.addEventListener("click", function() { console.log("4"); }, false);
当事件处理程序被触发时,它有机会通过调用事件对象上的stopPropagation
方法来防止事件进一步传播。在 IE < 9 中,事件的cancelBubble
属性需要改为设置为 true。下面是一种跨浏览器兼容的阻止事件传播的方法。
function cancelEvent(e) {
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
}
DOM 事件
现代浏览器支持很多 DOM 事件。以下是您可能会遇到的最常见事件的列表。
| 事件名称 | 描述 | | --- | --- | | `onClick` | 当用户单击一个元素时触发。可以应用于任何可见的元素。 | | `onLoad` | 当页面完成加载时在窗口上触发。需要外部对象的元素,比如`