本文是对JavaScript编程基础的一次系统的总结。全文一共分为2个部分。第一部分先介绍最基本的语言基础,比如条件语句,循环语句,函数,基本的数据结构等等。第二步介绍jQuery,掌握了jQuery基本技术就能够随心所欲地对页面元素进行各种操作。
一. JavaScript入门
JavaScript语言诞生于1995年,Brendan Eich仅用了10天时间就创造了它,这门语言最初的目的是为网页添加交互式和动态特性。现在这门语言已经可以用在后端开发甚至嵌入式领域了。现代JavaScript语言的发展已经加入ECMA标准化,按照年份来命名,比如ES2016和ES2017等等。
所有的浏览器都内置了JavaScript引擎。我们可以通过各大浏览器的开发者工具中的控制台(console)来运行JS程序,可以通过console.log()
在控制台打印信息。
1.1 不同浏览器上的开发工具
1.1.1 Google Chrome
Chrome 开发者工具是 Google Chrome 中内置的网页编辑和调试工具。这篇文章是介绍开发者工具的官方文档。
要打开 Chrome 开发者工具,右键单击任意页面元素,并选择检查(Inspect),或者打开浏览器窗口右上角的 Chrome 菜单并选择"更多工具 (More Tools)" --"开发者工具(Developer Tools)"。或者你可以使用快捷方式 ⌘ + ⌥ + I
(Mac) 或 Ctrl + ⇧ + I
(Linux)。
1.1.2 Mozilla Firefox
Firefox 开发者工具让你可以在台式机和手机上检查、编辑及调试 HTML、CSS 和 JavaScript。你也可以下载 Firefox Developer Edition 版的 Firefox,它专为开发者定制,具有最新的 Firefox 功能和开发者试验工具。
要学习更多内容,请查看官方文档。要打开 Firefox 开发者工具,右键单击任意页面 元素并选择“检查元素(Inspect Element)”,或者打开浏览器窗口右上角的 Firefox 菜单并选择“开发者(Developer)”。 或者,你可以使用快捷方式 ⌘ + ⌥ + I
(Mac)或 Ctrl + ⇧ + I
(Windows/Linux)。
1.1.3 Safari
对于 Mac 用户,Safari 自带有 Web Inspector,这是一个强大的工具,能够轻松地修改、调试和优化网站,以便在两个平台上同时获得最佳的性能和兼容性。要学习更多内容,请查看官方文档。
要访问 Safari 的网页开发工具,请在 Safari 的高级首选项中启用"开发(Develop)菜单"。启用后,可右键单击任意页面元素并选择"检查元素(Inspect Element)"以打开 Web 开发工具,或者使用快捷方式 ⌘ + ⌥ + I
。
1.2 数据类型与变量
JavaScript中的数据类型包含数值,字符串,布尔类型和特殊类型,有时候类型之间还会有隐式类型转换。
1.2.1 基本数据类型
JavaScript中的数值类型包含各种正负整数和小数,可以进行各种常见的加减乘除算术运算:
运算符 | 含义 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 求余数(保留整数) |
++ | 累加 |
-- | 递减 |
可以对数字进行各种大于,小于或等于的比较:
运算符 | 含义 |
---|---|
< | 小于 |
> | 大于 |
<= | 小于或等于 |
>= | 大于或等于 |
== | 等于 |
!= | 不等于 |
JS中的注释语句有两种:单行注释和多行注释。
// 这是一条单行注释
/*
这是
一条多行
注释
*/
JS中的字符串要用单引号或者双引号引起来。很多开发指南都建议字符串使用单引号。字符串有很多运算,比如加法运算符+
表示字符串连接,如果字符串和数值数据之间用+
号连接,那么得到的结果会自动转换成字符串。
字符串还可以像数组一样被索引,比如James[0]
返回字符"J",这种索引是从0开始的,如果字符串的长度为n,那么最后一个字符的索引为n-1。可以使用length来获得字符串的长度,可以使用replace函数替换其中一部分字符。
和其他编程语言一样,有一些特殊字符在使用时需要转义,比如在字符串中带入双引号,就需要使用反斜杠\进行转义:
console.log("The man whispered, \"please speak to me.\"");
一些需要转义的特殊字符:
代码 | 字符 |
---|---|
\ | \ (反斜杠) |
" | '' (双引号) |
' | ' (单引号) |
\n | newline |
\t | tab |
字符串之间是可以进行比较的,这种比较是区分大小写的:
"Yes" == "yes" // false
'Y' != 'y' // true
比较的结果只能是true或者false,这也是一种重要的基本数据类型:布尔型。
有三种特殊的类型是需要注意的:null,undefined和NaN。null是一种表示"value of nothing"的数据类型,即”空值“。undefined是一种表示"absence of value"的数据类型,即"缺少值"。NaN表示"not a number",即"非数字",这在数字运算存在错误时被返回,比如试图给负数求平方根运算。
类型之间可能会有些你看不见的"隐式类型转换":
"1" == 1; // true
0 == false; // true
在其他编程语言中也会有这种现象的发生。JS引擎解析代码的时候会自动转换相应的数据类型,比如上面提到的字符串类型与数值相加,数值就会被自动转换成字符串。使用==或者!=运算时也会发生这样的转换,所以如果要比较值是否相等,最好使用绝对相等运算符===和!==,举例:
"1" === 1 // false
0 === false // false
1.2.2 变量
对于JS这样的弱类型语言而言,它的变量可以被赋值为任何类型,包括基础类型和后面要提到的类类型,甚至也可以是函数。JS的变量命名遵循驼峰规则,并使用var来定义:
var name = 'Richard';
这只是基本的定义变量的方式,然而var定义的变量有一些微妙的地方,现代JavaScript语言推荐使用let来定义具有块级作用于的变量。
1.3 条件语句
JavaScript中的条件语句就两种:if和switch,搭配各种逻辑运算符使用更佳。
1.3.1 if语句
if语句可以根据条件是否成立来决定是否执行某段代码:
if (/* 这个表达式为真 */) {
// 运行这段代码
} else {
// 运行这段代码
}
else代码块是可以不要的,可以根据表达式是否为真来决定该运行哪段代码:
var a = 1;
var b = 2;
if (a > b) {
console.log("a大于b");
} else {
console.log("a小于或等于b");
}
输出:"a小于或等于b"。
如果一次判断不够,还可以多写几个else if:
var weather = "sunny";
if (weather === "snow") {
console.log("Bring a coat.");
} else if (weather === "rain") {
console.log("Bring a rain jacket.");
} else {
console.log("Wear what you have on.");
}
输出: Wear what you have on.
1.3.2 逻辑运算符
JavaScript中的基本逻辑运算符有三种:AND,OR和NOT。分别使用&&
,||
和!
表示。其实就是离散数学中的"真值表":
- value1 && value2:如果
value1
和value2
都为true
,则返回true
- value1 || value2:如果
value1
或value2
为true
,则返回true
- !value1:返回
value1
的相反值。如果value1
为true
,则!value1
为false
一定注意逻辑运算符遵循短路求值原则:只要逻辑运算符会尽可能快的返回真值,比如对于value1 || value2而言,如果value1求值为true,那么value2就不会再求值了,因为此时该表达式的值一定是true,不需要再判断了。
另外一个要注意的问题是JS中的有些值放在布尔表达式中求值时会被固定的求值为真或假,下面的值都会被求值为真:
- true
- -42
- "pizza"
- {}
- []
下面的值都会被求值为假:
- false
- null
- undefined
- 0
- NaN
- ""
JS还衍生出了一种运算符叫三元运算符,可以帮助避免写出冗长的if else语句:
conditional ? (if condition is true) : (if condition is false)
如果条件为真则执行冒号前面的语句,否则执行冒号后面的语句。
var isGoing = true;
var color = isGoing ? "green" : "red";
console.log(color);
输出:green
1.3.3 switch语句
如果代码中重复出现大量的if语句,每个条件都给予相同的值,那么用switch语句改写好了:
if (option === 1) {
console.log("You selected option 1.");
} else if (option === 2) {
console.log("You selected option 2.");
} else if (option === 3) {
console.log("You selected option 3.");
} else if (option === 4) {
console.log("You selected option 4.");
} else if (option === 5) {
console.log("You selected option 5.");
} else if (option === 6) {
console.log("You selected option 6.");
}
改写成:
switch (option) {
case 1:
console.log("You selected option 1.");
break;
case 2:
console.log("You selected option 2.");
break;
case 3:
console.log("You selected option 3.");
break;
case 4:
console.log("You selected option 4.");
break;
case 5:
console.log("You selected option 5.");
break;
case 6:
console.log("You selected option 6.");
break; // technically, not needed
}
注意这里每个分支都要有break语句,否则当表达式的值满足某个分支时,该分支下面的语句都会被执行一遍(fall through问题)。当没有任何与switch表达式相符的值时,可以默认执行default分支:
var prize = "";
switch (winner) {
case 1:
prize += "a trip for two to the Bahamas and ";
case 2:
prize += "a four piece furniture set.";
break;
case 3:
prize += "a smartwatch and ";
default:
prize += "tickets to the circus.";
}
console.log("You've won " + prize);
1.4 循环
循环语句重复一定次数地执行特定的操作。JS的循环有两种:while循环和for循环。当你掌握了循环,程序设计的三种基本结构就配齐了:顺序,条件和循环。任何循环都具有以下三大部分:
- 何时开始:设置循环的代码 — 例如定义某个变量的起始值。
- 何时停止:测试循环是否继续的逻辑条件。
-
如何转到下一项: 递增或递减步骤 — 例如,
x = x * 3
或x = x - 1
1.4.1 while循环
举例:
var start = 0; // 何时开始
while (start < 10) { // 何时停止
console.log(start);
start = start + 2; // 如何进入下一项
}
输出:
0
2
4
6
8
循环可以和上面介绍的条件语句一起使用,一个满足以下要求的 while
循环:
- 从数字 1 循环访问到 20
- 如果数字可以被 3 整除,则输出 “Julia”
- 如果可以被 5 整除,则输出 “James”
- 如果可以同时被 3 和 5 整除,则输出 “JuliaJames”
- 如果不能被 3 或 5 整除,则输出该数字
可以这么写:
var x = 1;
while (x <= 20) {
// check divisibility
// print Julia, James, or JuliaJames
// increment x
if (x % 3 === 0 && x % 5 === 0) {
console.log('JuliaJames');
} else if (x % 3 === 0) {
console.log('Julia');
} else if (x % 5 === 0) {
console.log('James');
} else {
console.log(x);
}
x = x + 1;
}
1.4.2 for循环
for循环基本结构:
for ( start; stop; step ) {
// code
}
下面这个 for 循环输出了 0-5 的每个值:
for (var i = 0; i < 6; i = i + 1) {
console.log("Printing out i = " + i);
}
Printing out i = 0
Printing out i = 1
Printing out i = 2
Printing out i = 3
Printing out i = 4
Printing out i = 5
循环还可以嵌套:
for (var x = 0; x < 5; x = x + 1) {
for (var y = 0; y < 3; y = y + 1) {
console.log(x + "," + y);
}
}
输出:
0, 0
0, 1
0, 2
1, 0
1, 1
1, 2
2, 0
2, 1
2, 2
3, 0
3, 1
3, 2
4, 0
4, 1
4, 2
循环中可以使用类似C语言的自增自减运算符:
x++ or ++x // 等同于 x = x + 1
x-- or --x // 等同于 x = x - 1
x += 3 // 等同于 x = x + 3
x -= 6 // 等同于 x = x - 6
x *= 2 // 等同于 x = x * 2
x /= 5 // 等同于 x = x / 5
1.5 函数
函数是用来封装重复代码块的基本结构。随着代码越写越长,有些代码会出现越来越多的重复,这个时候我们就需要把重复的代码抽象出来封装成函数,通过调用函数来实现对应的功能。如果需求有变动,修改一处总比到处修改要好,这也是提高代码健壮性的一种策略。
1.5.1 参数和返回值
函数将一段代码封装起来,并在程序中使用。函数可以具有一个参数,表示该函数的输入:
function reverseString(reverseMe) {
// 反转一个字符串的代码!
}
多个参数用逗号分隔开:
function doubleGreeting(name, otherName) {
// 向两人问好的代码!
}
函数也可以没有任何参数。直接封装代码并执行某项功能,直接把小括号留空就行了:
function sayHello() {
var message = "Hello!";
console.log(message);
}
如果没有返回值,函数的输出结果就是undefined。如果需要返回结果,那么就要使用return语句:
function sayHello() {
var message = "Hello!";
return message; // 返回message而不是打印message
}
注意区分parameter和argument的概念。parameter是形参,出现在函数声明中,相反argument是值,它出现在函数的调用代码中。
1.5.2 作用域
一旦引入函数,就会存在作用域的问题。在任何编程语言中,作用域都是一个容易出现微妙错误,难以debug的基础概念。它的内涵是表示某个标识符(变量或函数名)在部分程序片段中是否可见或可被访问。
JS中有三种不同类型的作用域:全局作用域,函数作用域和ES6要引入的块作用域。在所有函数之外定义的标识符具有全局作用域,可以在程序的任何位置使用,程序中所有函数的内部都能访问到它。如果标识符是在函数内部定义的,那么该变量具有函数作用域,该函数内部任何位置都能访问到它,包括该函数内部定义的其他函数。
JS引擎在查找标识符时遵循从内向外的查找模式。它首先查看该标识符是否在当前函数中定义,如果找到了则到此为止;如果没找到就查看外面一层,一直持续到达全局作用域。如果仍然找不到,就会返回错误。如果函数内部出现了与全局标识符同名的标识符,则会在函数内发生作用域覆盖,只能访问到函数内定义的那个标识符。
在任何编程语言中都是不推荐使用全局变量的,一来全局变量会跟其他相同名字的变量产生 冲突,二来在多线程环境下,全局变量的读写容易产生bug。
JS中海油一个叫"提升(hoisting)"的特性也会导致比较微妙的错误。在执行任何代码前,所有的函数声明都被提升到当前作用域的顶部。比如下面这个函数:
findAverage(5, 9);
function findAverage(x, y) {
var answer = (x + y) / 2;
return answer;
}
JS引擎在解析这行代码时,会将函数的定义提升到当前作用域的顶部,因此先调用函数后定义函数是被允许的。提升还会发生在变量的声明上,比如下面这段代码:
function sayGreeting() {
console.log(greeting);
var greeting = "hello";
}
输出:undefined
输出的结果居然是undefined,怎么回事?原因是提升仅对变量的声明起作用,也就是说上面这段代码相当于:
function sayGreeting() {
var greeting;
console.log(greeting);
greeting = "hello";
}
声明虽然提升到了函数作用域的顶部,但赋值语句还停留在原地,这就是输出undefined的原因。最佳实践是在脚本顶部声明函数和变量,这样代码的外观和执行方式就保持一致了。
1.5.3 函数表达式
函数表达式,顾名思义,将函数作为表达式来使用:
var meow = function(max) {
var catMessage = "";
for (var i = 0; i < max; i++) {
catMessage += "meow";
}
return catMessage;
}
函数可以赋值给一个变量,之后就可以用这个变量来调用函数。函数表达式有三种模式。函数可以作为参数,此时作为参数的函数有一个专门的名字叫回调(callback):
// 函数表达式 catSays
var catSays = function(max) {
var catMessage = "";
for (var i = 0; i < max; i++) {
catMessage += "meow ";
}
return catMessage;
};
// 函数声明 helloCat 接受一个回调
function helloCat(callbackFunc) {
return "Hello " + callbackFunc(3);
}
// catSays 作为回调函数传入
helloCat(catSays);
函数表达式可以有名称,称为命名函数表达式,直接使用变量名而不是函数名来调用它:
var favoriteMovie = function movie() {
return "the fountain";
}
favouriteMovie();
如果尝试使用函数名调用它,会返回引用错误。最后一种模式被称为内联函数表达式,即就地定义函数:
function movies(messageFunction, name) {
messageFunction(name);
}
// 调用 movies 函数,传入一个函数和电影名称
movies(function displayFavorite(movieName) {
console.log("My favorite movie is " + movieName);
}, "Finding Nemo");
当你不需要重复使用该函数时,这么定义是最方便的。
1.6 数组
数组是一种非常常见的顺序存储结构。这节会介绍数组如何创建和访问,并介绍它的一些常用的方法。
1.6.1 数组的创建和访问
定义新数组的方法是列出各个值,用逗号分隔然后放在方括号[]
里。
// 用三个字符串创建一个 `donuts` 数组
var donuts = ["glazed", "powdered", "jelly"];
作为弱类型语言,还可以混搭:
var mixedData = ["abcd", 1, true, undefined, null, "all the things"];
还可以嵌套:
var arraysInArrays = [
[1, 2, 3],
["Julia", "James"],
[true, false, true, false]
];
数组的访问使用索引来实现,索引从0开始,如果数组长度为n,那么最后一个元素的索引为n-1。
var donuts = ["glazed", "powdered", "sprinkled"];
console.log(donuts[0]);
如果访问了不存在的元素,JS将返回undefined,其他语言会报下标越界错误。
// `donuts` 数组中的第四个元素不存在!
console.log(donuts[3]);
1.6.2 数组作为对象
数组作为特殊的对象,提供了大量实用的属性和方法。Array.length属性可用于了解数组长度:
var donuts = ["glazed", "powdered", "sprinkled"];
console.log(donuts.length);
输出结果:3
注意,字符串也有length属性,同样可以"James".length
进行访问。
如果想修改数组,可以使用push()和pop()方法。push()方法在数组的末尾添加元素:
var donuts = ["glazed", "chocolate frosted", "Boston creme", "glazed cruller", "cinnamon sugar", "sprinkled"];
donuts.push("powdered");
返回:7
donuts 数组:["glazed", "chocolate frosted", "Boston creme", "glazed cruller", "cinnamon sugar", "sprinkled", "powdered"]
pop()方法从数组末尾删除元素,并返回已经被删除的元素:
var donuts = ["glazed", "chocolate frosted", "Boston creme", "glazed cruller", "cinnamon sugar", "sprinkled", "powdered"];
donuts.pop(); // 从 "donuts" 数组的末尾弹出 "powdered"
donuts.pop(); // 从 "donuts" 数组的末尾弹出 "sprinkled"
donuts.pop(); // 从 "donuts" 数组的末尾弹出 "cinnamon sugar"
splice()函数可以向数组的任意位置添加和删除元素,第1个参数表示要从哪个索引开始更改数组,第2个参数表示要删除的元素数量 ,剩下的参数表示要添加的元素:
var donuts = ["glazed", "chocolate frosted", "Boston creme", "glazed cruller"];
// 删除索引1处的 "chocolate frosted" ,并从索引1开始添加 "chocolate cruller" 和 "creme de leche"
donuts.splice(1, 1, "chocolate cruller", "creme de leche");
还可以使用负索引倒着修改数组:
var donuts = ["cookies", "cinnamon sugar", "creme de leche"];
// ["cookies", "chocolate frosted", "glazed", "cinnamon sugar", "creme de leche"]
donuts.splice(-2, 0, "chocolate frosted", "glazed");
可以查阅MDN文档来查阅数组的所有内置方法。
可以对数组进行循环,循环的方式有三种:基本for循环,forEach循环和map。基本for循环就是for循环结合length来遍历数组:
var donuts = ["jelly donut", "chocolate donut", "glazed donut"];
// 变量 `i` 用来遍历数组中的每个元素
for (var i = 0; i < donuts.length; i++) {
donuts[i] += " hole";
donuts[i] = donuts[i].toUpperCase();
}
还可以用forEach()方法来遍历数组,它的参数是一个内联函数表达式,该函数表达式接受3个参数:element,index,array。第1个参数表示数组中的元素,第2个参数表示该元素在数组中的索引,第3个参数表示目标数组,但后两个参数是可以省略的:
var donuts = ["jelly donut", "chocolate donut", "glazed donut"];
donuts.forEach(function(donut) {
donut += " hole";
donut = donut.toUpperCase();
console.log(donut);
});
输出:
JELLY DONUT HOLE
CHOCOLATE DONUT HOLE
GLAZED DONUT HOLE
如果需要得到遍历修改过的新数组,那么不能用forEach(),因为它的返回值为undefined,但是可以使用更强大的map()方法 通过现有数组创建一个新数组,该方法是从函数式编程思想中得来的:
var donuts = ["jelly donut", "chocolate donut", "glazed donut"];
var improvedDonuts = donuts.map(function(donut) {
donut += " hole";
donut = donut.toUpperCase();
return donut;
});
donuts 数组:["jelly donut", "chocolate donut", "glazed donut"]
improvedDonuts 数组:["JELLY DONUT HOLE", "CHOCOLATE DONUT HOLE", "GLAZED DONUT HOLE"]
如果在数组中存储了数组,就成了多维数组。
var donutBox = [
["glazed", "chocolate glazed", "cinnamon"],
["powdered", "sprinkled", "glazed cruller"],
["chocolate cruller", "Boston creme", "creme de leche"]
];
使用多重循环来遍历数组:
for (var row = 0; row < donutBox.length; row++) {
// 这里,donutBox[row].length 指的是当前被循环的甜甜圈(donut)数组的长度
for (var column = 0; column < donutBox[row].length; column++) {
console.log(donutBox[row][column]);
}
}
1.7 对象
对象是JS中强大的特性,它允许将相关属性和方法封装到一个被称为"类"的容器中使用。实际上在JS中万物皆对象,函数也不过是一个callable的对象。可以使用typeof运算符来返回后面数据类型的名称:
typeof "hello" // 返回 "string"
typeof true // 返回 "boolean"
typeof [1, 2, 3] // 返回 "object"(数组是一种对象类型)
typeof function hello() { } // 返回 "function"
对象的创建可以采用字面值记法:
var sister = {
name: "Sarah",
age: 23,
parents: [ "alice", "andy" ],
siblings: ["julia"],
favoriteColor: "purple",
pets: true,
paintPicture: function() { return "Sarah paints!"; }
};
注意:
-
“键”(表示属性或方法名称)和它的“值”用冒号隔开了
-
键值对用逗号隔开
-
整个对象包含在花括号
{ }
里。
访问属性可以使用括号记法:sister["parents"]
也可以使用点记法:sister.parents
。点记法更符合面向对象的使用习惯。创建对象的时候要遵守命名约定,否则会出现错误,要避免的做法:
- 不要使用引号包围属性名称;
- 不要使用数字作为属性名称的第1个字符;
- 不要在属性名称中使用空格和连字符;
最好是使用驼峰命名法。
二. jQuery入门
jQuery是JavaScript库,它通过选择器操纵DOM(文档对象模型),jQuery存在的原因是纯JS操作DOM其实并不方便,比如我想给id为parent的元素添加一个子元素div,原生的JS写法如下:
var div = document.createNode('div');
div.innerHTML = "hello udacity";
var parent = document.querySelector('#parent');
parent.appendChild(div);
但如果使用jQuery,一行就搞定了,可以说是"write less, do more":
$('parent').append('<div>hello udacity</div>');
jQuery解决了各种浏览器兼容问题,所以开发人员只需要关注要实现的功能即可。jQuery一大特点就是反复出现的美元符号$
,在jQuery中它代表一个指向与之前相同的JavaScript对象的指针。$
返回一个类数组的对象,称为jQuery collection,通过它调用jQuery各种功能。
2.1 文档对象模型和选择器
DOM是一种树形结构,全称是"文档对象模型(Document Object Model)"。我们编写的HTML代码最终会被浏览器解析成一棵被称为DOM的树型数据结构,jQuery通过选择并操作DOM中的元素来为网页增加动态效果。
2.1.1 jQuery的引入
jQuery的引入通过script标签实现,有三种方式:
<!-- 本地 -->
<script src='js/jquery.min.js'></script>
<!-- jQuery官方 -->
<script src='//code.jquery.com/jquery-1.11.1.min.js'></script>
<!-- 通过CDN(推荐做法) -->
<script src='//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'></script>
2.1.2 jQuery的套路
用jQuery操作网页的套路就是"先选择器选择,然后调用函数实现功能"。只要把握好这个方法,一切都迎刃而解了。如果传递的参数为字符串,jQuery对象就会为我们选择对应的元素,就像CSS那样:
// 标签选择
$('tag')
// 类选择
$('.class')
// id选择
$('#id')
所有能在CSS中使用的选择器都能在jQuery中使用,举个例子:
// select all the li elements
var listElements = $('li');
// select all elements of class green
var articleItems = $('.article-item');
// select an element with id nav
var nav = $('#nav');
// select multiple elements at a time
$("#topContacts, #footerContacts").append(…);
2.2 使用jQuery动态修改网页
jQuery本质上还是一个JavaScript的库,它提供了很多很强大的功能。通过它我们可以动态地操作DOM,添加删除元素,修改元素的属性和内容,在各种特殊点上执行某个函数。都说HTML用来表现网页的信息结构,CSS用来渲染网页的外观,那么JavaScript正是通过动态操作DOM来为网页添加功能,这是它起作用的根本原因。不管是用jQuery也好,还是用React,Vue和AngularJS这些库也好,它们最根本的本质还是通过操作DOM来修改网页的表现形式从而实现交互式网页。
2.2.1 用jQuery遍历DOM
jQuery提供了一系列的函数,可以实现更复杂的操作,比如在DOM中遍历各个元素。学过数据结构的同学对这些函数的名字肯定不陌生。
parent()方法可以选择直接父元素(只向上回溯一层);如果要选择任一父元素,可以使用parents()函数,参数为父元素的名称,就可以遍历所有的父元素;children()方法能够返回所有的直接子元素,只向下遍历了一层;同样地,如果需要选择任一子元素,就需要find()方法;最后,siblings()方法能够返回当前元素的所有兄弟元素。
2.2.2 用jQuery修改类和CSS属性
addClass()函数可以为选中的元素添加一个类属性,比如$('#item').addClass('blue')
为id为item的元素添加类"blue"。
toggleClass()函数可以根据元素的状态决定增加或者移除class属性,比如移除Article #2的featured类属性。
featured = $('.featured');
featured.toggleClass('featured');
next()函数返回元素后面的直接兄弟元素,比如为Article #3添加featured类名。
var article2 = $('.featured');
var article3 = article2.next();
article2.toggleClass('featured');
article3.toggleClass('featured');
可以使用attr()函数来修改元素的属性值,它有两种签名:attr(attributeName)用于读取属性值,attr(attributeName, value)用于设置属性值,比如为第一个a元素增加href属性值。
var navList = $('.nav-list');
var firstItem = navList.children().first();
var link = firstItem.find('a');
link.attr('href', '#1');
还可以使用css()来轻松地获取和修改CSS代码。要注意,使用JS来修改CSS实际上是添加了内嵌的CSS,即修改元素的style属性。实际开发过程中还是要权衡一下是否有必要这么做,还是说可以通过CSS解决问题。css(propertyName)可以获取CSS属性值,css(propertyName, value)可以设置属性值。比如将article-items的 字号设置为20px。
articleItems = $('.article-item');
articleItems.css('font-size', '20px');
2.2.3 用jQuery修改元素内容
除了可以遍历DOM,读写DOM元素的属性,我们还可以使用jQuery来修改元素的内容,有两种方法可以获取标签中的数据:html()和text()函数。html()会返回目标元素下所有子元素的内容,包括标签在内,但text()函数返回的内容会去掉所有的标签。比如想要实现把文本输入框内容设置为h1标题的内容,就可以这么写:
$('#input').on('change', function() {
var val, h1;
val = $('#input').val();
h1 = $('.articles').children('h1');
h1.text(val);
})
当id为input的元素内容发生改变时将回调该匿名内嵌函数,该函数从input元素中取出内容,然后将它设置到h1元素中。val()函数获取所匹配元素集合中第一个元素的值,或者也可以设置所有匹配到的元素的值。
2.2.4 用jQuery修改网页元素
使用jQuery还可以动态地添加和删除DOM中的元素。我们先来看看如何使用remove()函数删除元素,举个例子,将Article #1中的无序列表移除:
var articleItems, ul;
articleItems = $('.article-item');
ul = articleItems.find('ul');
ul.remove();
jQuery还可以一行代码实现创建新的DOM节点并把它们添加到DOM,有4种常见的方法:append(),prepend(),insertBefore()和insertAfter()。
append()和preprend()可以为元素增加子元素,前者将子元素作为最后一个元素添加,后者将子元素作为第一个元素添加:
var firstArticleItem;
firstArticleItem = $('.article-item').first();
firstArticleItem.append('<img src="http://placepuppy.it/200/300"');
firstArticleItem.prepend('<img src="http://placepuppy.it/200/300"');
insertBefore()和insertAfter()为元素添加兄弟元素,也是一个添加到前面,一个添加到后面。
2.2.5 用jQuery实现迭代访问
each()方法能够用来循环访问jQuery集合,对集合中的每个元素执行一个回调函数。在回调函数中如果想对一个传入的DOM元素调用任意的jQuery方法,需要使用$(this)
来获取循环中的当前元素。举个例子,为网页中所有的p元素增加字数统计:
$('p').each(function() {
var text = $(this).text();
$(this).text(text + ' ' + text.length);
});
这种回调函数的使用模式还会在前后端的各种库和框架中看到。比如jQuery还有一个功能是传递一个函数给jQuery对象,也就是美元符号,来实现在DOM构建完成之后的document.ready上立即执行这个回调函数。
这么做有什么意义呢?文档的head标签中外部JS文件会先于body加载并立即执行,如果这段JS语言想要操作页面上的一些DOM元素,会因为这些DOM元素还没出现而不进行任何操作。我们可以在body的底部包含脚本,但这意味着页面的渲染可能会因为下载并加载JS导致延迟。
也就是说在这个场景下我们需要对一个函数进行延迟调用,解决的方法就是向jQuery对象传递一个函数,这个函数会在构建完DOM后调用:
$(function(){
// 做有趣的事情
});
下一节要介绍的内容会大量涉及到回调函数这一主题。
2.3 jQuery的事件监听机制
事件监听机制让我们可以对用户浏览网页时的行为进行自动响应。通过监听事件并作出响应(回调函数)来实现交互式网页。这一节将介绍的浏览器事件对理解jQuery乃至于后面更高级的前端框架有很大帮助。事件是指发生在特定时间的特定动作,在浏览器渲染出来的页面上,你每移动一次鼠标,点击一次页面,按一下按钮或者拖动一个元素,都会产生可能不止一个事件。比如就拿按钮来打个比方,我将鼠标移动到按钮上,按下按钮,再移开,至少会触发以下事件:
- 鼠标进入按钮控件
- 鼠标悬停
- 鼠标按下
- 鼠标单击
- 鼠标释放
- 鼠标离开按钮控件
我们可以给每个事件塞一个回调函数,让这个事件触发时自动执行这个函数来实现我们想要的功能。Chrome浏览器提供了一个只能在控制台中使用的函数monitorEvents(),只要给这个函数传递一个元素,就能在控制台看到这个元素触发了哪些事件。具体可以参考monitorEvents()文档。事件通常可以分为以下几个类别:
- 资源事件
- 网络事件
- Websocket事件
- 会话历史事件
- CSS动画事件
- CSS过渡事件
- 表单事件
- 打印事件
- 文本写作事件
- 视图事件
- ……
你想得到的想不到的各种事件,浏览器已经全部准备好了。这里有一份完整的事件列表可供参考。
2.3.1 jQuery事件监听要素
在jQuery中使用三个要素来监听和响应事件:
- 要监听的目标对象;
- 要响应的事件;
- 要采取的动作;
比如下面这行代码就包含了这三个要素,my-input就是要监听的目标对象,keypress是要响应的事件,而这里定义的回调函数就是我们要采取的动作:
$('#my-input').on('keypress', function() {
$('body').css('background-color', '#2727FF');
});
我们在回调函数中对事件作出响应通常需要对事件本身进行处理,所以这里的回调函数实际上带了一个参数:事件对象。这个对象包括了很多可在函数中使用的信息,常被引用为e,evt或者event。event对象中的target属性就代表了发生该事件的目标元素,它很有用,比如我们设置当artcle元素 被点击时背景色变为红色,就可以这样写了:
$( 'article' ).on( 'click', function( evt ) {
$( evt.target ).css( 'background', 'red' );
});
此时只有那个被点击的元素才会改变颜色。另一个比较有用的是preventDefault()方法。比如我有一个叫myAnchor的链接,我只想在点击时将日志记录到控制台,而不想定向到新的页面,就可以用这个方法阻止默认操作:
$( '#myAnchor' ).on( 'click', function( evt ) {
evt.preventDefault();
console.log( 'You clicked a link!' );
});
其他用途包括:
-
event.keyCode
:了解按下了哪个键; -
event.pageX
和event.pageY
:了解在页面上的哪个位置进行了单击, 用于分析跟踪; -
event.type
:在侦听目标的多个事件时非常有用;
更多有关事件对象的内容,请查阅以下文档:
on()函数是个万精油函数,只要传递进事件名称就能监听特定的事件,但jQuery还提供了大量的便捷方法可供使用:
// 按键事件
$('input').keypress(function() {});
// 单击事件
$('input').click(function() {});
// 内容发生改变事件
$('input').change(function() {});
详细的便捷方法列表,可查阅官方文档了解。
2.3.2 高级用法:事件代理
事件代理的原理是一种事件的向上传播机制,通过父元素监听任意子孙元素事件。在什么时候我们会用到事件代理呢?主要有两种场景:1. 要监听的目标对象在设置监听时还不存在;2. 有大量的监听对象需要设置监听器。
下面这段代码属于第一种情况:
$( 'article' ).on( 'click', function() {
$( 'body' ).addClass( 'selected' );
});
$( 'body' ).append( '<article> <h1>新文章的附加文章</h1> <p>内容 </p> </article>' );
click事件监听器设置在前,body中添加article在后。事件监听器在设置的时候页面上如果还没有article元素,那么这次设置就会扑个空:在还没出现的元素上设置了click监听器,因此是无效的,没有设置上。但如果我们使用事件代理,将监听器设置在目标元素的父元素上,就可以顺利实现了,我们向on()方法额外传递一个参数,表示检查单击事件发生时目标是否为某元素:
$( '.container' ).on( 'click', 'article', function() { … });
该代码使得 jQuery 关注 .container
元素的单击次数,如果单击事件发生,则检查单击事件的目标是否为 article
元素。
另一种情况是,如果页面上有1000个列表项:
<ul id="rooms">
<li>Room 1</li>
<li>Room 2</li>
.
.
.
<li>Room 999</li>
<li>Room 1000</li>
</ul>
如果我们要给这1000个列表项设置监听器,那就会产生1000个事件监听器:
$( '#rooms li' ).on( 'click', function() {
...
});
更好的做法是在一个元素上设置事件代理监听器,当事件发生时检查元素是否为列表项:
$( '#rooms' ).on( 'click', 'li', function() {
...
});
事件代理的优点就在于能响应新创建的元素并能合并监听器数量,更多信息请查阅官方文档。
这篇文章主要讲解JavaScript语言的基础和jQuery库的使用。后来随着不断发展和创新,这门语言逐渐进化成了一门更优秀的编程语言,它的目标不再仅仅是服务于网页操作DOM,它还添加了一系列的新特性和语法糖,让自己看起来更像一门"真正的"编程语言,使用现代JavaScript语言,你可以开发服务器后端程序和嵌入式程序,编写命令行工具,甚至开发虚拟机。在下一篇文章中,我们将着重梳理现代JavaScript语言的一些新特性,让我们来领略这门编程语言的强大吧!
网友评论