前端开发

JavaScript编程基础

2019-05-03  本文已影响92人  Leesper

本文是对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。分别使用&&||!表示。其实就是离散数学中的"真值表":

  1. value1 && value2:如果 value1 value2 都为 true,则返回 true
  2. value1 || value2:如果 value1 value2true,则返回 true
  3. !value1:返回 value1相反值。如果 value1true,则 !value1false

一定注意逻辑运算符遵循短路求值原则:只要逻辑运算符会尽可能快的返回真值,比如对于value1 || value2而言,如果value1求值为true,那么value2就不会再求值了,因为此时该表达式的值一定是true,不需要再判断了。

另外一个要注意的问题是JS中的有些值放在布尔表达式中求值时会被固定的求值为真或假,下面的值都会被求值为真:

下面的值都会被求值为假:

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循环。当你掌握了循环,程序设计的三种基本结构就配齐了:顺序,条件和循环。任何循环都具有以下三大部分:

  1. 何时开始:设置循环的代码 — 例如定义某个变量的起始值。
  2. 何时停止:测试循环是否继续的逻辑条件。
  3. 如何转到下一项: 递增或递减步骤 — 例如,x = x * 3x = x - 1

1.4.1 while循环

举例:

var start = 0; // 何时开始
while (start < 10) { // 何时停止
  console.log(start);
  start = start + 2; // 如何进入下一项
}

输出:
0
2
4
6
8

循环可以和上面介绍的条件语句一起使用,一个满足以下要求的 while 循环:

可以这么写:

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. 不要使用引号包围属性名称;
  2. 不要使用数字作为属性名称的第1个字符;
  3. 不要在属性名称中使用空格和连字符;

最好是使用驼峰命名法。

二. 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乃至于后面更高级的前端框架有很大帮助。事件是指发生在特定时间的特定动作,在浏览器渲染出来的页面上,你每移动一次鼠标,点击一次页面,按一下按钮或者拖动一个元素,都会产生可能不止一个事件。比如就拿按钮来打个比方,我将鼠标移动到按钮上,按下按钮,再移开,至少会触发以下事件:

  1. 鼠标进入按钮控件
  2. 鼠标悬停
  3. 鼠标按下
  4. 鼠标单击
  5. 鼠标释放
  6. 鼠标离开按钮控件

我们可以给每个事件塞一个回调函数,让这个事件触发时自动执行这个函数来实现我们想要的功能。Chrome浏览器提供了一个只能在控制台中使用的函数monitorEvents(),只要给这个函数传递一个元素,就能在控制台看到这个元素触发了哪些事件。具体可以参考monitorEvents()文档。事件通常可以分为以下几个类别:

  1. 资源事件
  2. 网络事件
  3. Websocket事件
  4. 会话历史事件
  5. CSS动画事件
  6. CSS过渡事件
  7. 表单事件
  8. 打印事件
  9. 文本写作事件
  10. 视图事件
  11. ……

你想得到的想不到的各种事件,浏览器已经全部准备好了。这里有一份完整的事件列表可供参考。

2.3.1 jQuery事件监听要素

在jQuery中使用三个要素来监听和响应事件:

  1. 要监听的目标对象;
  2. 要响应的事件;
  3. 要采取的动作;

比如下面这行代码就包含了这三个要素,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!' );
});

其他用途包括:

更多有关事件对象的内容,请查阅以下文档:

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语言的一些新特性,让我们来领略这门编程语言的强大吧!

上一篇下一篇

猜你喜欢

热点阅读