揭开 JavaScript 的神秘面纱
JavaScript,常缩写为 JS,是一种广泛应用于网页开发的高级脚本语言。它诞生于 1995 年,由网景公司的 Brendan Eich 在短短 10 天内设计完成,最初名为 Mocha,后改名为 LiveScript,同年 12 月,为了搭上 Java 语言的热潮,最终定名为 JavaScript。尽管名字里有 “Java”,但 JavaScript 与 Java 并没有太多直接关联,它有自己独特的语法和特性。
1996 年,微软推出了类似的 JScript,随着 JavaScript 的迅速发展,为了统一标准,1997 年,JavaScript 被提交给欧洲计算机制造商协会(ECMA),ECMA 制定了 ECMAScript 标准,成为 JavaScript 的语言规范。此后,JavaScript 不断进化,每年都会有新的版本发布,如 2015 年发布的 ES6 带来了众多强大的新特性,让 JavaScript 的编程更加高效和便捷。
如今,JavaScript 无处不在,不仅在前端开发中扮演着至关重要的角色,用于创建动态、交互性强的用户界面,响应用户操作,如表单验证、菜单切换、图片轮播等;还通过 Node.js 实现了服务器端编程,能够处理 HTTP 请求、连接数据库、执行后台任务等,成为全栈开发的重要工具。此外,在桌面应用开发(如 Electron 框架)、移动应用开发(React Native 等)、游戏开发(通过 WebGL 和 Three.js 等库实现 3D 游戏开发)、物联网(IoT)设备通信与数据处理、自动化脚本编写、实时通讯(WebSocket 和 Socket.IO 技术)、数据可视化(结合 D3.js、Chart.js 等库)、机器学习(如 TensorFlow.js)、区块链开发(编写智能合约、构建去中心化应用 DApps)等领域都有广泛应用 。可以说,JavaScript 已经成为现代互联网不可或缺的一部分,掌握 JavaScript 是进入 Web 开发领域的关键一步。
工欲善其事,必先利其器:开发环境搭建
在正式开启 JavaScript 学习之旅前,我们需要搭建好开发环境。JavaScript 的运行环境主要有两种:浏览器和 Node.js。
- 浏览器:浏览器是 JavaScript 最常用的运行环境,像 Chrome、Firefox、Safari 等都内置了 JavaScript 引擎,能解析和执行 JavaScript 代码。在浏览器中,我们可以通过开发者工具(如 Chrome DevTools)来调试 JavaScript 代码。比如在 Chrome 浏览器中,按下 F12 键就能打开开发者工具,在 “Sources” 标签页里可以查看和编辑网页的 JavaScript 代码,在 “Console” 标签页能执行 JavaScript 代码片段并查看输出结果 。
- Node.js:Node.js 是基于 Chrome V8 引擎的 JavaScript 运行环境,它让 JavaScript 可以在服务器端运行,极大地扩展了 JavaScript 的应用范围。Node.js 还提供了丰富的内置模块,如文件系统(fs)模块用于文件操作,HTTP 模块可创建 Web 服务器等 。要安装 Node.js,只需前往Node.js 官方网站,下载对应操作系统的安装包,按照提示完成安装即可。安装完成后,在命令行中输入node -v,若能显示版本号,就说明安装成功。
对于代码编辑器,这里推荐 Visual Studio Code(简称 VS Code),它是一款免费开源、功能强大且扩展性极强的编辑器,非常适合初学者。以下是在 VS Code 中搭建 JavaScript 开发环境的步骤:
- 下载安装 VS Code:从VS Code 官网下载对应操作系统的安装包并安装。
- 安装 JavaScript 扩展:打开 VS Code,点击左侧边栏的扩展图标(或使用快捷键 Ctrl+Shift+X),在搜索框中输入 “JavaScript”,安装官方提供的 “JavaScript” 扩展,它能提供代码智能提示、语法检查等功能 。此外,还可以安装 “ESLint” 扩展进行代码规范检查,安装 “Prettier - Code formatter” 扩展来格式化代码,让代码风格更统一、更易读。
- 创建 JavaScript 项目:打开 VS Code,点击 “文件”->“新建文件夹”,创建一个项目文件夹。然后在文件夹中右键点击,选择 “在集成终端中打开”,在终端中输入npm init -y,这会生成一个package.json文件,用于管理项目的依赖和配置 。
- 编写和运行 JavaScript 代码:在项目文件夹中新建一个.js文件,比如main.js,输入 JavaScript 代码,如console.log('Hello, JavaScript!');。要运行代码,在终端中输入node main.js,就能看到输出结果。如果想在浏览器中运行 JavaScript 代码,可以创建一个 HTML 文件,在<script>标签中引入.js文件,然后在浏览器中打开 HTML 文件 。
语法基础:构建代码大厦的基石
掌握 JavaScript 的语法基础是编写正确、高效代码的关键,就像打好地基才能建造稳固的大厦一样。接下来我们一起深入学习 JavaScript 的语法基础。
(一)变量与数据类型
变量是编程中的一个重要概念,它就像是一个容器,可以用来存储各种数据。在 JavaScript 中,变量可以存储数字、文本、布尔值(true 或 false)等不同类型的数据 。比如,我们可以用一个变量来存储用户的姓名,或者用另一个变量来存储商品的价格。
在 JavaScript 中,有三种声明变量的方式:var、let和const。
- var:使用var声明的变量作用域是函数作用域或全局作用域。例如:
function example() {
var x = 1; // 定义
if (true) {
var x = 2; // 重新定义
console.log(x); // 输出 2
x = 3;
console.log(x); // 输出 3
}
console.log(x); // 输出 3
}
example();
在这个例子中,var声明的x变量在函数内是共享的,即使在if语句块内重新声明,也不会创建新的作用域。
- let:let声明的变量是块级作用域,即它的作用域在当前代码块内部,比如if、for循环和函数内部 。并且let变量不能被重复声明,但可以重新赋值。例如:
function example() {
let x = 1; // 定义
// let x = 2; // 报错,不能重复声明
x = 2;
console.log(x);
if (true) {
let x = 3;
console.log(x); // 输出 3
}
console.log(x); // 输出 2
}
example();
这里if语句块内用let声明的x变量是一个新的变量,与外部的x变量不同。
- const:const声明的变量也是块级作用域,同样不能被重复声明,且一旦声明,就不能重新赋值,所以const声明的变量必须在声明时进行初始化。例如:
function example() {
// const x; // 报错,必须初始化
const x = 2;
// x = 3; // 报错,不能重新赋值
console.log(x);
if (true) {
const x = 3;
console.log(x); // 输出 3
}
console.log(x); // 输出 2
}
example();
JavaScript 有七种基本数据类型,分别是:
- Number:表示数字,包括整数和浮点数。例如:let num1 = 10;,let num2 = 3.14;。需要注意的是,JavaScript 在进行浮点数运算时可能会出现精度问题,比如0.1 + 0.2的结果不是0.3,而是0.30000000000000004。
- String:用于表示文本,字符串需要用单引号、双引号或反引号包裹 。例如:let str1 = 'Hello';,let str2 = "World";,let str3 = JavaScript is awesome;。其中,反引号还支持模板字符串,可以在字符串中嵌入变量和表达式,如let name = 'John'; let greeting = Hello, ${name}!;。
- Boolean:只有两个值,true和false,常用于逻辑判断。例如:let isDone = true;,let isValid = false;。
- Null:表示一个空值,只有一个值null。例如:let empty = null;。
- Undefined:当一个变量声明了但没有赋值时,它的值就是undefined。例如:let value; console.log(value); // 输出 undefined。
- Symbol:表示独一无二的值,常用于对象的属性键,防止属性名冲突 。例如:let sym = Symbol('unique'); let obj = { [sym]: 'This is a symbol property' };。
- BigInt:用于表示任意精度的整数,通过在整数末尾附加n来创建。例如:let bigNum = 1234567890123456789012345678901234567890n;,这在处理大数时非常有用,比如加密或高精度时间戳。
除了基本数据类型,还有一种引用数据类型 ——Object,它是一种复杂的数据类型,可以用来存储多个键值对,数组(Array)、函数(Function)、日期(Date)等都属于Object类型的子类型 。例如,数组可以用方括号[]创建:let arr = [1, 2, 3, 'four'];,对象可以用花括号{}创建:let person = { name: 'Alice', age: 30, hobbies: ['reading','swimming'] };。
(二)运算符与表达式
运算符是用来执行各种运算的符号,JavaScript 中有多种运算符,常见的有:
- 算术运算符:用于数学运算,如加法(+)、减法(-)、乘法(*)、除法(/)、取余(%)、求幂(**) 。例如:let result1 = 5 + 3;,let result2 = 10 % 3;,let result3 = 2 ** 3;。在进行算术运算时,要注意浮点数的精度问题,如前面提到的0.1 + 0.2的情况。
- 比较运算符:用于比较两个值的大小或是否相等,返回一个布尔值。常见的比较运算符有大于(>)、小于(<)、大于等于(>=)、小于等于(<=)、等于(==)、不等于(!=)、全等(===)、不全等(!==) 。其中,==在比较时会进行类型转换,而===不仅比较值,还比较数据类型。例如:let isGreater = 5 > 3;,let isEqual = 5 == '5';(结果为true,因为进行了类型转换),let isStrictEqual = 5 === '5';(结果为false,因为类型不同)。
- 逻辑运算符:用于进行布尔值运算,返回值也是布尔值,包括逻辑与(&&)、逻辑或(||)、逻辑非(!) 。逻辑与运算符只有当两个操作数都为true时,结果才为true;逻辑或运算符只要有一个操作数为true,结果就为true;逻辑非运算符用于取反,即true变为false,false变为true 。例如:let result4 = true && false;(结果为false),let result5 = true || false;(结果为true),let result6 =!true;(结果为false)。逻辑运算符还有短路运算的特性,对于&&运算,当第一个表达式为false时,就不再计算第二个表达式;对于||运算,当第一个表达式为true时,就不再计算第二个表达式 。比如:let a = 10; let b = 20; (a > 5) && (b < 15);,这里因为a > 5为true,所以会继续计算b < 15;而(a < 5) && (b < 15);,因为a < 5为false,所以不会再计算b < 15。
- 赋值运算符:用于给变量赋值,常见的有=、+=、-=、*=、/=、%=等 。例如:let num = 5; num += 3;(相当于num = num + 3;),let str = 'Hello'; str += 'World';。
- 其他运算符:还有一些其他运算符,如自增(++)、自减(--)运算符,它们可以对变量进行加 1 或减 1 操作 。自增、自减运算符有前置和后置两种形式,前置形式(如++x)会先对变量进行加 1 或减 1 操作,然后返回操作后的结果;后置形式(如x++)会先返回变量原来的值,然后再对变量进行加 1 或减 1 操作 。例如:let x = 5; let y = ++x;(此时x和y的值都为6),let m = 5; let n = m++;(此时m的值为6,n的值为5)。
运算符具有优先级和结合性,优先级决定了表达式中不同运算符的计算顺序,结合性决定了相同优先级运算符的计算方向 。例如,乘法和除法的优先级高于加法和减法,所以2 + 3 * 4的结果是14(先计算3 * 4,再加上2) 。对于同一优先级的运算符,大部分是从左到右计算,如加法和减法;但赋值运算符是从右到左计算 。比如:let a = b = c = 5;,实际上是先将5赋值给c,然后将c的值(也就是5)赋值给b,最后将b的值(还是5)赋值给a。
表达式是由运算符和操作数组成的式子,会返回一个值 。例如,3 + 5是一个表达式,它返回值8;(a > 5) && (b < 10)也是一个表达式,它根据a和b的值返回true或false 。在 JavaScript 中,我们可以将表达式的值赋给变量,或者在函数调用中使用表达式作为参数 。比如:let sum = 3 + 5;,console.log(sum);(输出8);function multiply(a, b) { return a * b; } let product = multiply(2, 3 + 1);,这里3 + 1是一个表达式,作为multiply函数的第二个参数,最终product的值为8。
(三)流程控制语句
流程控制语句用于控制程序的执行顺序,使程序可以根据不同的条件执行不同的代码块,或者重复执行某些代码。JavaScript 中的流程控制语句主要有:
- 条件语句:
- if语句:用于单分支条件判断,只有当指定条件为true时,才会执行相应的代码块 。语法如下:
if (条件表达式) {
// 条件为true时执行的代码
}
例如:
let num = 10;
if (num > 5) {
console.log('The number is greater than 5');
}
- if...else语句:用于双分支条件判断,当条件为true时执行if代码块,否则执行else代码块 。语法如下:
if (条件表达式) {
// 条件为true时执行的代码
} else {
// 条件为false时执行的代码
}
例如:
let num = 3;
if (num > 5) {
console.log('The number is greater than 5');
} else {
console.log('The number is less than or equal to 5');
}
- if...else if...else语句:用于多分支条件判断,可以根据多个条件执行不同的代码块 。语法如下:
if (条件表达式1) {
// 条件1为true时执行的代码
} else if (条件表达式2) {
// 条件2为true时执行的代码
} else {
// 以上条件都为false时执行的代码
}
例如,根据学生的成绩判断等级:
let score = 85;
if (score >= 90) {
console.log('A');
} else if (score >= 80) {
console.log('B');
} else if (score >= 70) {
console.log('C');
} else {
console.log('D');
}
- switch语句:用于基于一个表达式的值来选择执行不同的代码块,通常用于等值判断 。语法如下:
switch (表达式) {
case 值1:
// 表达式的值等于值1时执行的代码
break;
case 值2:
// 表达式的值等于值2时执行的代码
break;
default:
// 表达式的值不等于任何case的值时执行的代码
}
例如,根据用户输入的数字输出对应的星期:
let day = 3;
switch (day) {
case 1:
console.log('Monday');
break;
case 2:
console.log('Tuesday');
break;
case 3:
console.log('Wednesday');
break;
case 4:
console.log('Thursday');
break;
case 5:
console.log('Friday');
break;
case 6:
console.log('Saturday');
break;
case 7:
console.log('Sunday');
break;
default:
console.log('Invalid day');
}
在使用switch语句时,要注意每个case后面的break语句,它用于防止代码继续执行下一个case,如果省略break,就会出现 “穿透” 现象,导致执行多个case的代码 。比如:
let num = 2;
switch (num) {
case 1:
console.log('One');
case 2:
console.log('Two');
case 3:
console.log('Three');
default:
console.log('Other');
}
这里因为num的值为2,会先执行case 2的代码,输出Two,由于没有break语句,会继续执行case 3和default的代码,最终输出Two、Three、Other。
- 循环语句:
-
- for循环:常用于已知循环次数的情况,它有三个表达式:初始化表达式、循环条件表达式、循环后的操作表达式 。语法如下:
for (初始化表达式; 循环条件表达式; 循环后的操作表达式) {
// 循环体,重复执行的代码
}
例如,输出 1 到 10 的数字:
for (let i = 1; i <= 10; i++) {
console.log(i);
}
在这个例子中,let i = 1是初始化表达式,用于初始化循环变量i;i <= 10是循环条件表达式,只要这个条件为true,就会继续执行循环体;i++是循环后的操作表达式,每次循环结束后会执行,用于更新循环变量i。
- while循环:先判断条件,当条件为true时执行循环体,适用于不确定循环次数的情况 。语法如下:
while (循环条件表达式) {
// 循环体,重复执行的代码
}
例如,计算 1 到 100 的和:
let sum = 0;
let i = 1;
while (i <= 100) {
sum += i;
i++;
}
console.log(sum);
这里i <= 100是循环条件表达式,只要i小于等于 100,就会执行循环体,在循环体中,先将i加到sum中,然后i自增 1。
- do...while循环:先执行一次循环体,然后再判断条件,当条件为true时继续执行循环体 。语法如下:
do {
// 循环体,重复执行的代码
} while (循环条件表达式);
例如,要求用户输入一个大于 0 的数字,直到输入正确为止:
let num;
do {
num = parseInt(prompt('Please enter a number greater than 0'));
} while (num <= 0);
console.log('You entered a valid number:', num);
在这个例子中,无论用户第一次输入什么,都会先执行一次do中的代码,然后判断num是否大于 0,如果不大于 0,就会继续循环,要求用户重新输入。
- for...in循环:用于遍历对象的属性,语法如下:
for (变量 in 对象) {
// 循环体,变量会依次取对象的每个属性名
}
例如,遍历一个对象:
```
## 函数:代码复用的魔法
在JavaScript中,函数是一等公民,它不仅可以像普通变量一样被传递、赋值,还能极大地提高代码的复用性和可维护性,让编程更加高效和优雅。掌握函数的使用是进阶JavaScript编程的关键一步,下面我们就来深入学习函数的相关知识。
### (一)函数的定义与调用
函数的定义语法如下:
```javascript
function 函数名(参数列表) {
// 函数体,这里是执行的代码
return 返回值; // 可选,用于返回函数执行的结果
}
例如,我们定义一个简单的加法函数:
function add(num1, num2) {
return num1 + num2;
}
在这个例子中,add是函数名,num1和num2是参数列表,函数体中执行了两个数相加的操作,并通过return语句返回结果 。
函数调用的方式很简单,只需使用函数名加上括号,并在括号中传入相应的参数(如果有参数的话):
let result = add(3, 5);
console.log(result); // 输出 8
这里add(3, 5)就是函数调用,将3和5作为参数传递给add函数,函数执行后返回结果8,并赋值给result变量 。
(二)函数的参数与返回值
- 函数参数:
- 必选参数:在函数定义时列出的参数,调用函数时必须传入对应的参数值,否则参数会被赋值为undefined 。比如前面的add函数中的num1和num2就是必选参数。
- 可选参数:在 ES6 之前,可选参数通常通过判断参数是否为undefined来处理;ES6 之后,可以使用默认参数来实现可选参数的效果 。例如:
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(); // 输出 Hello, Guest!
greet('Alice'); // 输出 Hello, Alice!
这里name参数有默认值'Guest',所以在调用greet函数时,如果不传入参数,就会使用默认值;如果传入参数,就会使用传入的值 。
- 默认参数:除了上述简单的默认参数设置,默认参数还可以是一个表达式或函数调用 。例如:
function multiply(a, b = a * 2) {
return a * b;
}
console.log(multiply(3)); // 输出 18,因为b的默认值是a * 2,即3 * 2 = 6,3 * 6 = 18
console.log(multiply(3, 5)); // 输出 15,使用传入的b值5
这里b的默认值是a * 2,是一个基于a的表达式 。
- 函数返回值:函数通过return语句返回值,返回值可以是任何数据类型,包括基本数据类型和引用数据类型 。如果函数没有return语句,或者return后面没有值,函数将返回undefined 。例如:
function getMessage() {
return 'Hello, JavaScript!';
}
let message = getMessage();
console.log(message); // 输出 Hello, JavaScript!
function doSomething() {
// 没有return语句
}
let result2 = doSomething();
console.log(result2); // 输出 undefined
(三)匿名函数与箭头函数
- 匿名函数:没有函数名的函数,通常用于一次性使用的场景,比如作为回调函数 。定义方式如下:
function(参数列表) {
// 函数体
return 返回值;
}
匿名函数不能单独使用,一般需要将其赋值给一个变量,或者直接在其他函数调用中作为参数使用 。例如:
// 作为变量赋值
let sum = function(num1, num2) {
return num1 + num2;
};
let result3 = sum(2, 3);
console.log(result3); // 输出 5
// 作为回调函数
setTimeout(function() {
console.log('This is a callback function');
}, 1000);
在这个例子中,第一个匿名函数被赋值给sum变量,然后通过sum变量来调用;第二个匿名函数作为setTimeout函数的回调函数,在 1 秒后执行 。
- 箭头函数:ES6 引入的一种更简洁的函数定义方式,语法如下:
(参数列表) => {
// 函数体
return 返回值;
}
如果函数体只有一行语句,并且要返回该语句的值,可以省略花括号{}和return关键字 。例如:
// 完整语法
let multiply2 = (num1, num2) => {
return num1 * num2;
};
console.log(multiply2(3, 4)); // 输出 12
// 省略语法
let multiply3 = (num1, num2) => num1 * num2;
console.log(multiply3(3, 4)); // 输出 12
箭头函数与普通函数的区别主要有以下几点:
- this指向:普通函数的this指向调用它的对象;而箭头函数没有自己的this,它的this继承自外层作用域 。例如:
const obj = {
name: 'Alice',
sayHello: function() {
setTimeout(function() {
console.log(`Hello, ${this.name}`); // 这里的this指向window,输出Hello, undefined
}, 1000);
},
sayHelloArrow: function() {
setTimeout(() => {
console.log(`Hello, ${this.name}`); // 这里的this继承自外层的obj,输出Hello, Alice
}, 1000);
}
};
obj.sayHello();
obj.sayHelloArrow();
- arguments对象:普通函数内部有arguments对象,用于获取所有传入的参数;箭头函数没有自己的arguments对象,它会继承外层作用域的arguments对象 。如果需要在箭头函数中获取参数,可以使用剩余参数语法... 。例如:
function normalFunction() {
console.log(arguments); // 可以访问arguments对象
}
normalFunction(1, 2, 3);
const arrowFunction = (...args) => {
console.log(args); // 使用剩余参数获取参数
};
arrowFunction(1, 2, 3);
- 构造函数:箭头函数不能作为构造函数,不能使用new关键字来创建实例 。例如:
// 会报错
// const MyClass = () => {};
// const instance = new MyClass();
操作 DOM:掌控网页元素
在网页开发中,操作 DOM(文档对象模型)是一项核心技能,它让我们能够通过 JavaScript 动态地改变网页的内容、样式和行为,实现各种交互效果。下面我们来深入学习如何操作 DOM。
(一)认识 DOM
DOM(Document Object Model)即文档对象模型,是 W3C 制定的标准接口规范,它将 HTML 或 XML 文档表示为一个树形结构,树的每个节点表示文档中的一个元素、属性或文本内容等 。在 DOM 树中,最顶层的节点是文档节点,每个 HTML 标签是一个元素节点,包含在 HTML 元素中的文本是文本节点,元素的属性是属性节点 。例如,对于以下 HTML 结构:
<!DOCTYPE html>
<html>
<head>
<title>DOM示例</title>
</head>
<body>
<h1>欢迎来到DOM世界</h1>
<p>这是一个段落。</p>
<ul>
<li>项目1</li>
<li>项目2</li>
</ul>
</body>
</html>
在 DOM 树中,html元素是根节点,head和body是html的子节点,title是head的子节点,h1、p、ul是body的子节点,li是ul的子节点 。通过 DOM,我们可以使用 JavaScript 来访问和操作这些节点,比如获取某个元素节点,修改其文本内容或样式,添加或删除节点等 。DOM 就像是网页的 “幕后操控者”,让我们能够对网页进行随心所欲的定制和交互。
(二)获取 DOM 元素
在 JavaScript 中,有多种方法可以获取 DOM 元素:
- document.getElementById('id值'):通过元素的id属性获取唯一的元素节点 。id在文档中是唯一的,所以这个方法会返回一个单一的元素。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>获取DOM元素示例</title>
</head>
<body>
<div id="myDiv">这是一个div</div>
<script>
let myDiv = document.getElementById('myDiv');
console.log(myDiv);
</script>
</body>
</html>
在这个例子中,document.getElementById('myDiv')会返回id为myDiv的div元素节点,将其赋值给myDiv变量,然后在控制台输出该元素 。
- document.getElementsByTagName('标签名'):通过标签名获取元素节点列表,返回的是一个类似数组的对象(HTMLCollection),包含了所有匹配标签名的元素 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>获取DOM元素示例</title>
</head>
<body>
<p>段落1</p>
<p>段落2</p>
<script>
let paragraphs = document.getElementsByTagName('p');
for (let i = 0; i < paragraphs.length; i++) {
console.log(paragraphs[i]);
}
</script>
</body>
</html>
这里document.getElementsByTagName('p')获取到所有的p元素,存储在paragraphs中,通过循环可以遍历并输出每个p元素 。
- document.getElementsByClassName('类名'):通过元素的类名获取元素节点列表,返回的也是一个类似数组的对象(HTMLCollection),包含了所有具有该类名的元素 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>获取DOM元素示例</title>
</head>
<body>
<div class="box">盒子1</div>
<div class="box">盒子2</div>
<script>
let boxes = document.getElementsByClassName('box');
for (let i = 0; i < boxes.length; i++) {
console.log(boxes[i]);
}
</script>
</body>
</html>
document.getElementsByClassName('box')获取到所有类名为box的div元素,存储在boxes中,通过循环遍历输出每个元素 。
- document.querySelector('选择器'):通过 CSS 选择器获取第一个匹配的元素节点 。选择器可以是标签名、类名、id、属性选择器等 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>获取DOM元素示例</title>
</head>
<body>
<div id="myDiv">这是一个div</div>
<p class="myParagraph">这是一个段落</p>
<script>
let div = document.querySelector('#myDiv');
let paragraph = document.querySelector('.myParagraph');
console.log(div);
console.log(paragraph);
</script>
</body>
</html>
document.querySelector('#myDiv')通过id选择器获取到id为myDiv的div元素,document.querySelector('.myParagraph')通过类选择器获取到类名为myParagraph的p元素,并分别输出 。
- document.querySelectorAll('选择器'):通过 CSS 选择器获取所有匹配的元素节点列表,返回的是一个 NodeList 对象 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>获取DOM元素示例</title>
</head>
<body>
<p class="item">项目1</p>
<p class="item">项目2</p>
<script>
let items = document.querySelectorAll('.item');
for (let i = 0; i < items.length; i++) {
console.log(items[i]);
}
</script>
</body>
</html>
document.querySelectorAll('.item')获取到所有类名为item的p元素,存储在items中,通过循环遍历输出每个元素 。与document.getElementsByTagName和document.getElementsByClassName返回的 HTMLCollection 不同,NodeList 是一个静态的集合,不会自动更新,而 HTMLCollection 是动态的,会随着文档的变化而更新 。
(三)修改 DOM 元素
获取到 DOM 元素后,我们就可以对其进行各种修改操作:
- 修改属性:可以使用element.setAttribute('属性名', '属性值')方法来设置元素的属性,也可以直接通过element.属性名 = '属性值'来设置(对于标准 HTML 属性) 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>修改DOM元素示例</title>
</head>
<body>
<img id="myImage" src="image1.jpg" alt="图片">
<script>
let myImage = document.getElementById('myImage');
// 使用setAttribute方法
myImage.setAttribute('src', 'image2.jpg');
// 直接设置属性
myImage.alt = '新的图片描述';
</script>
</body>
</html>
这里通过setAttribute方法修改了img元素的src属性,通过直接设置alt属性修改了图片的描述 。对于一些特殊属性,如class,在 JavaScript 中需要使用className来操作 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>修改DOM元素示例</title>
</head>
<body>
<div id="myDiv" class="box">这是一个div</div>
<script>
let myDiv = document.getElementById('myDiv');
// 添加类名
myDiv.className += 'highlight';
// 移除类名
myDiv.className = myDiv.className.replace('highlight', '').trim();
</script>
</body>
</html>
这里通过修改className属性来添加和移除div元素的类名 。
- 修改内容:可以使用element.innerHTML来获取或设置元素的 HTML 内容,包括标签和文本;使用element.textContent来获取或设置元素的纯文本内容 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>修改DOM元素示例</title>
</head>
<body>
<div id="myDiv">
<p>原始内容</p>
</div>
<script>
let myDiv = document.getElementById('myDiv');
// 修改HTML内容
myDiv.innerHTML = '<h2>新的标题</h2><p>新的内容</p>';
// 修改纯文本内容
myDiv.textContent = '这是纯文本内容';
</script>
</body>
</html>
这里先使用innerHTML将div元素的内容替换为包含新标题和内容的 HTML 代码,然后使用textContent将其内容替换为纯文本 。需要注意的是,innerHTML会解析 HTML 标签,而textContent不会 。
- 修改样式:可以通过element.style来设置元素的行内样式,也可以通过修改classList属性来添加、移除或切换 CSS 类 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>修改DOM元素示例</title>
<style>
.highlight {
color: red;
font-weight: bold;
}
</style>
</head>
<body>
<p id="myParagraph">这是一个段落</p>
<script>
let myParagraph = document.getElementById('myParagraph');
// 设置行内样式
myParagraph.style.color = 'blue';
myParagraph.style.fontSize = '20px';
// 添加CSS类
myParagraph.classList.add('highlight');
// 移除CSS类
myParagraph.classList.remove('highlight');
// 切换CSS类
myParagraph.classList.toggle('highlight');
</script>
</body>
</html>
这里先通过element.style设置了p元素的文本颜色和字体大小,然后通过classList属性添加、移除和切换了highlight类,从而改变了元素的样式 。使用classList属性比直接修改className属性更方便和安全,因为它不会覆盖原有的类名 。
除了修改现有元素,我们还可以添加和删除 DOM 元素:
- 添加元素:
-
- 创建元素:使用document.createElement('标签名')方法创建一个新的元素节点 。例如:let newDiv = document.createElement('div');,这会创建一个空的div元素 。
-
- 设置属性和内容:创建元素后,可以设置其属性和内容 。例如:newDiv.setAttribute('class', 'newClass'); newDiv.textContent = '这是新创建的div内容';。
-
- 添加到 DOM 树:使用parentElement.appendChild(newElement)方法将新元素添加到指定的父元素下,作为父元素的最后一个子元素;也可以使用parentElement.insertBefore(newElement, referenceElement)方法将新元素插入到指定参考元素之前 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>添加DOM元素示例</title>
</head>
<body>
<ul id="myList">
<li>项目1</li>
<li>项目2</li>
</ul>
<script>
let myList = document.getElementById('myList');
let newItem = document.createElement('li');
newItem.textContent = '新项目';
myList.appendChild(newItem);
// 或者插入到指定位置
let referenceItem = myList.children[0];
myList.insertBefore(newItem, referenceItem);
</script>
</body>
</html>
这里先创建了一个新的li元素,设置其文本内容为 “新项目”,然后使用appendChild方法将其添加到ul元素的末尾,又使用insertBefore方法将其插入到ul元素的第一个子元素之前 。
- 删除元素:使用element.remove()方法可以直接删除当前元素;也可以使用parentElement.removeChild(childElement)方法,由父元素删除指定的子元素 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>删除DOM元素示例</title>
</head>
<body>
<div id="myDiv">
<p id="myParagraph">这是要删除的段落</p>
</div>
<script>
let myParagraph = document.getElementById('myParagraph');
// 直接删除
myParagraph.remove();
// 或者通过父元素删除
let myDiv = document.getElementById('myDiv');
myDiv.removeChild(myParagraph);
</script>
</body>
</html>
这里展示了两种删除p元素的方法,一种是直接调用myParagraph.remove(),另一种是通过获取p元素的父元素myDiv,然后调用myDiv.removeChild(myParagraph) 。在实际开发中,根据具体需求选择合适的方法来操作 DOM 元素,能够实现丰富多样的网页交互效果 。
事件处理:与用户交互
在网页开发中,实现与用户的交互是至关重要的,而 JavaScript 的事件处理机制让这一切成为可能。通过事件处理,我们可以捕捉用户的各种操作,如点击按钮、输入文本、移动鼠标等,并做出相应的响应,从而打造出动态、交互性强的网页。
(一)事件的概念
事件是指在网页中发生的各种操作或行为,比如用户点击按钮、鼠标移动到某个元素上、按下键盘上的某个键等 。事件就像是网页与用户之间沟通的桥梁,它让网页能够感知用户的操作,并根据这些操作执行相应的代码 。例如,当用户点击一个 “提交” 按钮时,我们可以通过事件处理来验证表单数据的有效性,然后将数据发送到服务器;当鼠标移动到图片上时,我们可以改变图片的样式,如放大、添加阴影等,以提供更好的用户体验 。
常见的事件类型有很多,主要包括:
- 点击事件(click):当用户用鼠标左键点击一个元素时触发 。比如网页上的按钮,当用户点击它时,就会触发按钮的点击事件,我们可以在事件处理函数中执行相应的操作,如提交表单、切换页面内容等 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>点击事件示例</title>
</head>
<body>
<button id="myButton">点击我</button>
<script>
let myButton = document.getElementById('myButton');
myButton.addEventListener('click', function () {
alert('按钮被点击了!');
});
</script>
</body>
</html>
在这个例子中,获取到id为myButton的按钮元素,为其添加了click事件监听器,当按钮被点击时,会弹出一个提示框。
- 鼠标移动事件(mousemove):当用户在一个元素上移动鼠标时触发 。利用这个事件,我们可以实现很多有趣的交互效果,比如让元素跟随鼠标移动,或者根据鼠标的位置动态改变元素的样式 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>鼠标移动事件示例</title>
<style>
#myDiv {
width: 200px;
height: 200px;
background-color: red;
}
</style>
</head>
<body>
<div id="myDiv"></div>
<script>
let myDiv = document.getElementById('myDiv');
myDiv.addEventListener('mousemove', function (event) {
let x = event.clientX;
let y = event.clientY;
myDiv.style.backgroundColor = `rgb(${x}, ${y}, 100)`;
});
</script>
</body>
</html>
这里当鼠标在id为myDiv的div元素上移动时,会根据鼠标的坐标动态改变div的背景颜色。
- 键盘事件(keydown、keyup):keydown事件在用户按下键盘上的某个键时触发,keyup事件在用户松开按键时触发 。通过这两个事件,我们可以捕获用户的键盘输入,执行特定的操作,比如实现快捷键功能、实时搜索等 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>键盘事件示例</title>
</head>
<body>
<input id="myInput" type="text">
<script>
let myInput = document.getElementById('myInput');
myInput.addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
alert('按下了回车键!');
}
});
</script>
</body>
</html>
当用户在id为myInput的输入框中按下回车键时,会弹出提示框。
- 表单事件(submit、change):submit事件在用户提交表单时触发,我们可以在这个事件中对表单数据进行验证,阻止表单的默认提交行为,然后进行自定义的提交操作,如通过 AJAX 将数据发送到服务器 ;change事件在表单元素的值发生改变并且失去焦点时触发,常用于实时验证用户输入的数据 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>表单事件示例</title>
</head>
<body>
<form id="myForm">
<input type="text" name="username">
<input type="submit" value="提交">
</form>
<script>
let myForm = document.getElementById('myForm');
myForm.addEventListener('submit', function (event) {
event.preventDefault(); // 阻止表单的默认提交行为
let username = myForm.elements.username.value;
alert('用户名:' + username);
});
</script>
</body>
</html>
这里当用户点击表单的提交按钮时,会阻止表单的默认提交行为,获取用户名并弹出提示框。
- 页面加载事件(load):当整个网页及其所有资源(如图片、脚本、样式表等)都加载完成后触发 。通常在这个事件中执行一些需要在页面加载完成后进行的初始化操作,如初始化插件、获取数据等 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>页面加载事件示例</title>
</head>
<body>
<script>
window.addEventListener('load', function () {
alert('页面加载完成!');
});
</script>
</body>
</html>
当页面完全加载完成后,会弹出提示框。
- 页面滚动事件(scroll):当用户滚动网页时触发 。利用这个事件,我们可以实现很多与页面滚动相关的效果,比如当用户滚动到页面底部时自动加载更多内容,或者让导航栏在用户滚动到一定位置时固定在页面顶部 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>页面滚动事件示例</title>
<style>
body {
height: 2000px;
}
</style>
</head>
<body>
<script>
window.addEventListener('scroll', function () {
let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
console.log('滚动距离:' + scrollTop);
});
</script>
</body>
</html>
当用户滚动页面时,会在控制台输出当前的滚动距离。
(二)事件监听与响应
在 JavaScript 中,我们使用addEventListener方法为 DOM 元素添加事件监听器,该方法接收三个参数:
- 事件类型:一个字符串,表示要监听的事件,如'click'、'mousemove'、'keydown'等 ,注意不要加'on'前缀 。
- 回调函数:当事件发生时要执行的函数,也称为事件处理函数,它会在事件触发时被调用 。
- useCapture(可选):一个布尔值,指示事件是在捕获阶段还是冒泡阶段触发,默认为false,表示在冒泡阶段触发 。关于捕获阶段和冒泡阶段,后面会详细介绍 。
例如,为一个按钮添加点击事件监听器:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件监听示例</title>
</head>
<body>
<button id="myButton">点击我</button>
<script>
let myButton = document.getElementById('myButton');
myButton.addEventListener('click', function () {
console.log('按钮被点击了');
}, false);
</script>
</body>
</html>
这里获取到按钮元素,为其添加了click事件监听器,当按钮被点击时,会在控制台输出 “按钮被点击了” 。事件处理函数可以是匿名函数,也可以是已经定义好的函数 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件监听示例</title>
</head>
<body>
<button id="myButton">点击我</button>
<script>
function handleClick() {
console.log('按钮被点击了');
}
let myButton = document.getElementById('myButton');
myButton.addEventListener('click', handleClick, false);
</script>
</body>
</html>
这种方式将事件处理逻辑封装在handleClick函数中,使代码结构更加清晰,便于维护和复用 。在事件处理函数中,我们可以通过this关键字来引用触发事件的 DOM 元素 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件监听示例</title>
</head>
<body>
<button id="myButton">点击我</button>
<script>
let myButton = document.getElementById('myButton');
myButton.addEventListener('click', function () {
this.style.backgroundColor ='red';
}, false);
</script>
</body>
</html>
当按钮被点击时,会将按钮的背景颜色设置为红色 。另外,事件处理函数还会接收一个事件对象作为参数,通过这个事件对象,我们可以获取到与事件相关的各种信息,如鼠标点击的坐标、键盘按下的键等 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件监听示例</title>
</head>
<body>
<div id="myDiv">
点击我
</div>
<script>
let myDiv = document.getElementById('myDiv');
myDiv.addEventListener('click', function (event) {
console.log('鼠标点击的X坐标:', event.clientX);
console.log('鼠标点击的Y坐标:', event.clientY);
}, false);
</script>
</body>
</html>
当点击div元素时,会在控制台输出鼠标点击的坐标信息 。
(三)事件冒泡与捕获
在介绍事件冒泡和捕获之前,我们先来了解一下事件流的概念 。事件流指的是事件完整执行过程中的流动路径,也就是网页中元素接受事件的顺序 。事件流分为事件冒泡阶段和事件捕获阶段 。
事件冒泡是指事件从最内层的元素开始,依次向外层元素传递,就像一个气泡从水底向上浮出水面一样 。例如,有一个嵌套的 HTML 结构:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件冒泡示例</title>
</head>
<body>
<div id="outer">
<div id="middle">
<div id="inner">
点击我
</div>
</div>
</div>
<script>
let outer = document.getElementById('outer');
let middle = document.getElementById('middle');
let inner = document.getElementById('inner');
outer.addEventListener('click', function () {
console.log('outer被点击');
}, false);
middle.addEventListener('click', function () {
console.log('middle被点击');
}, false);
inner.addEventListener('click', function () {
console.log('inner被点击');
}, false);
</script>
</body>
</html>
当点击inner元素时,会依次触发inner、middle、outer的点击事件,控制台输出顺序为:inner被点击、middle被点击、outer被点击 。这就是事件冒泡的过程,事件从最内层的inner元素开始,依次向上冒泡到middle和outer元素 。
事件捕获则与事件冒泡相反,事件从最外层的元素开始,依次向内层元素传递,就像从水面往水底传递一样 。在addEventListener方法中,将第三个参数设置为true,就可以使用事件捕获机制 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件捕获示例</title>
</head>
<body>
<div id="outer">
<div id="middle">
<div id="inner">
点击我
</div>
</div>
</div>
<script>
let outer = document.getElementById('outer');
let middle = document.getElementById('middle');
let inner = document.getElementById('inner');
outer.addEventListener('click', function () {
console.log('outer被点击');
}, true);
middle.addEventListener('click', function () {
console.log('middle被点击');
}, true);
inner.addEventListener('click', function () {
console.log('inner被点击');
}, true);
</script>
</body>
</html>
当点击inner元素时,会依次触发outer、middle、inner的点击事件,控制台输出顺序为:outer被点击、middle被点击、inner被点击 。这就是事件捕获的过程,事件从最外层的outer元素开始,依次向内捕获到middle和inner元素 。
在实际开发中,我们可以利用事件冒泡和捕获来实现一些特定的功能 。比如事件委托,就是利用事件冒泡的原理,将事件监听器添加到父元素上,而不是每个子元素都添加,这样可以减少事件监听器的数量,提高代码的性能和可维护性 。例如,有一个列表,我们想为每个列表项添加点击事件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>事件委托示例</title>
</head>
<body>
<ul id="myList">
<li>项目1</li>
<li>项目2</li>
<li>项目3</li>
</ul>
<script>
let myList = document.getElementById('myList');
myList.addEventListener('click', function (event) {
if (event.target.tagName === 'LI') {
console.log(event.target.textContent + '被点击');
}
}, false);
</script>
</body>
</html>
这里将点击事件监听器添加到ul元素上,当点击某个li元素时,由于事件冒泡,ul元素会接收到点击事件,然后通过判断event.target是否是li元素,来确定具体点击的是哪个列表项 。
有时候,我们可能需要阻止事件冒泡或捕获,以避免不必要的事件传播 。在标准浏览器中,可以使用event.stopPropagation()方法来阻止事件的进一步传播;在 IE 低版本中,可以使用event.cancelBubble = true来实现相同的效果 。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>阻止事件冒泡示例</title>
</head>
<body>
<div id="outer">
<div id="middle">
<div id="inner">
点击我
</div>
</div>
</div>
<script>
let outer = document.getElementById('outer');
let middle = document.getElementById('middle');
let inner = document.getElementById('inner');
inner.addEventListener('click', function (event) {
console.log('inner被点击');
event.stopPropagation(); // 阻止事件冒泡
}, false);
middle.addEventListener('click', function () {
console.log('middle被点击');
}, false);
outer.addEventListener('click', function () {
console.log('outer被点击');
}, false);
</script>
</body>
</html>
当点击inner元素时,只会触发inner的点击事件,不会继续冒泡到middle和outer元素,控制台只会输出inner被点击 。同样,在事件捕获阶段也可以使用event.stopPropagation()方法来阻止事件的捕获传播 。通过掌握事件冒泡和捕获的原理,以及如何监听和处理事件,我们能够创建出更加丰富、交互性更强的网页应用 。
结语:开启 JavaScript 之旅
通过本文,我们一起探索了 JavaScript 的基础语法,包括变量、数据类型、运算符、流程控制语句,深入了解了函数的定义、调用、参数与返回值,以及匿名函数和箭头函数的特性;还学习了如何操作 DOM,实现对网页元素的获取、修改、添加和删除,掌握了事件处理的相关知识,能够捕获用户操作并做出响应。
JavaScript 的应用场景十分广泛,从前端交互到后端服务,从桌面应用到移动开发,从游戏制作到物联网,它无处不在。这仅仅是 JavaScript 世界的冰山一角,还有更多强大的特性和库等待你去探索,如 ES6 + 的新特性(类、模块、async/await 等)、前端框架(React、Vue、Angular)、后端框架(Express、Koa)等。
如果你想进一步学习 JavaScript,这里为你推荐一些学习资源:
- 在线教程:MDN Web 文档是学习 JavaScript 的权威资源,涵盖了从基础到高级的全面知识;W3School提供了丰富的 JavaScript 教程和实例,适合初学者入门;菜鸟教程的教程简洁易懂,有大量的代码示例和在线运行环境,方便实践练习 。
- 书籍:《JavaScript 高级程序设计》被誉为 JavaScript 的 “红宝书”,深入讲解了 JavaScript 的核心概念和高级特性,是进阶学习的必备书籍;《你不知道的 JavaScript》系列书籍,深入剖析了 JavaScript 语言中一些容易被误解的知识点,如作用域、闭包、this 等,帮助你深入理解 JavaScript 的运行机制 。
- 视频课程:在哔哩哔哩上搜索 “JavaScript 教程”,能找到许多优质的免费视频教程,如 “尚硅谷 JavaScript 基础教程”“黑马程序员 JavaScript 基础教程” 等,视频讲解生动形象,适合不同学习风格的人 。
学习 JavaScript 需要不断实践,你可以尝试做一些小项目,如个人博客、简单的 Web 应用、小游戏等,在实践中巩固所学知识,提升编程能力。遇到问题时,多查阅文档,利用搜索引擎,积极向社区求助,如 Stack Overflow、SegmentFault 等技术社区,那里有众多开发者分享经验和解决方案 。希望你能在 JavaScript 的学习之旅中不断进步,享受编程带来的乐趣!