一、JavaScript 基础回顾
1.1 变量与数据类型
在 JavaScript 中,变量用于存储数据值。声明变量有多种方式,ES6 之前常用var关键字,例如:
var message = 'Hello, World!';
ES6 引入了let和const关键字,let声明的变量具有块级作用域,而const声明的是常量,一旦声明,值就不能被改变:
let count = 10;
const PI = 3.14159;
JavaScript 拥有丰富的数据类型,主要包括以下几种:
- 数值(Number):表示整数和浮点数,如10、3.14。JavaScript 内部,所有数字都是以 64 位双精度浮点数形式存储的。除了常规数字,还有一些特殊的数值,如Infinity(无穷大)、-Infinity(负无穷大)和NaN(非数字)。例如:
let num1 = 5;
let num2 = -10.5;
let infinityNum = Infinity;
let nanNum = NaN;
- 字符串(String):用于表示文本数据,由一系列字符组成,可以使用单引号(')或双引号(")包裹。字符串是不可变的,一旦创建,其内容不能被直接修改。例如:
let str1 = 'Hello';
let str2 = "JavaScript";
- 布尔值(Boolean):只有两个值,true和false,常用于逻辑判断和条件分支语句中。例如:
let isDone = true;
let hasError = false;
- null:表示一个空值或 “不存在” 的对象指针,通常用于初始化变量,表示该变量目前没有指向任何有效的对象。例如:
let emptyObject = null;
- undefined:表示变量被声明但未赋值的状态。如果一个变量只声明而没有初始化,它的值就是undefined。例如:
let unassignedVar;
console.log(unassignedVar); // 输出: undefined
- 对象(Object):是一种复杂的数据类型,可以包含多个属性,每个属性都是一个键值对。对象用于存储和组织相关的数据和功能。例如:
let person = {
name: 'John',
age: 30,
isStudent: false
};
- 数组(Array):是一种特殊的对象,用于存储一系列有序的元素,元素可以是任意数据类型。数组通过索引来访问元素,索引从 0 开始。例如:
let numbers = [1, 2, 3, 4, 5];
let mixedArray = ['apple', 10, true, null];
- 函数(Function):是一种特殊的数据类型,用于定义可执行的代码块。函数可以接受参数,并返回一个值。在 JavaScript 中,函数是一等公民,这意味着函数可以像其他数据类型一样被赋值给变量、作为参数传递给其他函数或从函数中返回。例如:
function add(a, b) {
return a + b;
}
let result = add(3, 5);
1.2 条件语句与循环
条件语句和循环语句是控制程序流程的重要工具,它们允许根据不同的条件执行不同的代码块,或者重复执行一段代码。
条件语句
- if…else 语句:根据条件判断结果执行不同的代码块。基本语法如下:
let num = 10;
if (num > 5) {
console.log('num大于5');
} else {
console.log('num小于或等于5');
}
还可以使用else if来处理多个条件:
let score = 85;
if (score >= 90) {
console.log('优秀');
} else if (score >= 80) {
console.log('良好');
} else if (score >= 60) {
console.log('及格');
} else {
console.log('不及格');
}
- switch 语句:用于基于多个条件执行不同的代码块,适用于判断一个变量与多个值中的某一个匹配的情况。基本语法如下:
let day = 'Monday';
switch (day) {
case 'Monday':
console.log('今天是星期一');
break;
case 'Tuesday':
console.log('今天是星期二');
break;
default:
console.log('未知的日期');
}
循环语句
- for 循环:常用于已知循环次数的情况。基本语法如下:
for (let i = 0; i < 5; i++) {
console.log(i);
}
这里,let i = 0是初始化表达式,i < 5是条件表达式,i++是每次循环结束后执行的表达式。
- while 循环:只要条件为真,就会重复执行循环体。基本语法如下:
let count = 0;
while (count < 3) {
console.log(count);
count++;
}
- do…while 循环:先执行一次循环体,然后再检查条件。无论条件是否为真,循环体至少会执行一次。基本语法如下:
let num = 0;
do {
console.log(num);
num++;
} while (num < 3);
1.3 函数与作用域
函数是 JavaScript 中重要的组成部分,它允许将一段可重复使用的代码封装起来,通过函数名进行调用。作用域则决定了变量和函数的可访问范围。
函数
- 函数定义:使用function关键字定义函数,语法如下:
function addNumbers(a, b) {
return a + b;
}
这里,addNumbers是函数名,a和b是参数,函数体内的return语句用于返回计算结果。
也可以使用函数表达式来定义函数:
let subtractNumbers = function (a, b) {
return a - b;
};
- 函数调用:通过函数名加括号的方式调用函数,并传递相应的参数:
let sum = addNumbers(3, 5);
let difference = subtractNumbers(7, 2);
作用域
- 全局作用域:在任何函数外部定义的变量具有全局作用域,它们可以在整个脚本中被访问。例如:
let globalVar = '我是全局变量';
function printGlobalVar() {
console.log(globalVar);
}
printGlobalVar();
- 局部作用域:在函数内部定义的变量具有局部作用域,只能在该函数内部被访问。例如:
function localScope() {
let localVar = '我是局部变量';
console.log(localVar);
}
localScope();
console.log(localVar); // 报错,localVar超出作用域,无法访问
此外,ES6 引入的let和const关键字具有块级作用域,它们在一对花括号{}内有效,这使得代码的逻辑更加清晰,减少了变量提升带来的问题。例如:
{
let blockVar = '我是块级作用域变量';
console.log(blockVar);
}
console.log(blockVar); // 报错,blockVar超出作用域,无法访问
二、开发环境搭建
在开始 JavaScript 实战之前,搭建一个合适的开发环境是至关重要的。一个良好的开发环境可以提高开发效率,减少错误,并使代码的管理和维护更加容易。以下是搭建 JavaScript 开发环境的详细步骤。
2.1 选择合适的编辑器
选择一款适合自己的代码编辑器能够极大地提升开发效率。以下是几款常用的编辑器及其特点:
- Visual Studio Code(VS Code):这是一款由微软开发的免费开源跨平台代码编辑器,具有丰富的插件生态系统,支持各种编程语言,包括 JavaScript。它提供了强大的代码智能提示、语法高亮、代码导航、调试功能,还集成了 Git 版本控制,方便进行代码管理。此外,VS Code 还支持实时协作功能(Live Share),可以让团队成员远程协作编程,非常适合前端开发和全栈开发。
- Sublime Text:以其简洁的界面、快速的响应速度和强大的自定义功能而受到开发者的喜爱。它支持多种编程语言,拥有丰富的插件资源,可以通过插件扩展其功能。Sublime Text 还具备多行编辑、代码片段等实用功能,适合快速编写代码和处理小型项目。
- Atom:同样是一款开源的跨平台代码编辑器,具有高度可定制性,用户可以根据自己的需求安装各种插件来扩展功能。Atom 提供了直观的界面和丰富的特性,如智能补全、语法检查、代码格式化等,非常适合 JavaScript 开发,尤其对于喜欢个性化开发环境的开发者来说是一个不错的选择。
这些编辑器都有各自的优势,读者可以根据自己的喜好和项目需求来选择。如果是初学者,推荐使用 VS Code,它的功能全面且易于上手,丰富的插件资源也能满足各种开发需求。
2.2 安装 Node.js 和 npm
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它允许 JavaScript 在服务器端运行,实现了 JavaScript 的全栈开发。npm(Node Package Manager)是 Node.js 的包管理器,用于安装、管理和分享 JavaScript 包。
安装步骤
- 下载安装包:访问 Node.js 官方网站(https://nodejs.org/),根据自己的操作系统下载对应的安装包。
- 运行安装程序:下载完成后,双击安装包,按照安装向导的提示进行安装。在安装过程中,可以选择安装路径和其他相关设置,一般保持默认设置即可。
- 配置环境变量(可选):在大多数情况下,Node.js 安装程序会自动将相关路径添加到系统环境变量中。如果安装后无法在命令行中直接使用node和npm命令,可以手动将 Node.js 的安装路径添加到系统环境变量的Path中。例如,Node.js 安装在C:\Program Files\nodejs,则将C:\Program Files\nodejs添加到Path变量中。
验证安装
安装完成后,打开命令行工具(Windows 下为命令提示符或 PowerShell,Mac 和 Linux 下为终端),输入以下命令来验证 Node.js 和 npm 是否安装成功:
node -v
npm -v
如果安装成功,会分别输出版本号,例如:
v18.12.1
8.19.2
2.3 创建并配置项目文件夹
在开始项目开发之前,需要创建一个项目文件夹,并在其中创建和配置相关的文件。
- 创建项目文件夹:在本地磁盘上选择一个合适的位置,创建一个新的文件夹,例如my - js - project。
- 初始化项目:打开命令行工具,进入项目文件夹,执行以下命令初始化项目:
npm init -y
这个命令会在项目文件夹中生成一个package.json文件,它用于管理项目的依赖包和脚本等信息。-y参数表示使用默认配置,快速初始化项目。
- 创建 HTML 文件:在项目文件夹中创建一个index.html文件,用于展示 JavaScript 的运行效果。以下是一个简单的 HTML 模板:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript实战</title>
</head>
<body>
<script src="script.js"></script>
</body>
</html>
在这个模板中,通过<script>标签引入了一个名为script.js的 JavaScript 文件,后续会在这个文件中编写 JavaScript 代码。
- 创建 JavaScript 文件:在项目文件夹中创建一个script.js文件,用于编写 JavaScript 代码。例如,在script.js中输入以下代码:
console.log('Hello, JavaScript!');
这段代码会在浏览器的控制台输出Hello, JavaScript!。
2.4 基本的版本控制(Git)
Git 是一种分布式版本控制系统,它可以帮助开发者有效地管理代码的版本,追踪文件的变化,方便多人协作开发。以下是 Git 的基本使用方法:
- 安装 Git:访问 Git 官方网站(https://git - scm.com/),根据自己的操作系统下载并安装 Git。安装过程中可以选择默认设置,安装完成后,在命令行中输入git --version验证是否安装成功。
- 初始化仓库:在项目文件夹中,打开命令行工具,执行以下命令初始化 Git 仓库:
git init
这个命令会在项目文件夹中创建一个隐藏的.git文件夹,用于存储版本控制信息。
- 添加文件到暂存区:使用git add命令将文件添加到暂存区。例如,要将index.html和script.js文件添加到暂存区,可以执行以下命令:
git add index.html script.js
也可以使用git add.命令将当前目录下的所有文件添加到暂存区。
- 提交更改:将文件添加到暂存区后,使用git commit命令提交更改,并添加提交信息。例如:
git commit -m "Initial commit"
-m参数后面的内容是提交信息,用于描述本次提交的内容,应尽量简洁明了。
- 查看提交历史:使用git log命令查看提交历史,它会显示每次提交的作者、日期、提交信息等内容。
git log
通过以上步骤,就完成了开发环境的搭建,包括选择编辑器、安装 Node.js 和 npm、创建并配置项目文件夹以及进行基本的版本控制。这些准备工作为后续的 JavaScript 实战开发奠定了基础。
三、JavaScript 实战技巧
3.1 解构赋值
解构赋值是一种在 JavaScript 中非常实用的语法,它可以让我们从数组或对象中提取值,并将其赋给变量,从而大大简化代码。
数组解构
数组解构允许我们按照顺序提取数组中的元素并赋值给变量。例如:
let [a, b, c] = [1, 2, 3];
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
这里,变量a、b、c分别被赋值为数组[1, 2, 3]中的第一个、第二个和第三个元素。即使数组元素的数量与变量数量不一致,解构也能正确工作。如果数组元素少于变量数量,多余的变量将被赋值为undefined;如果数组元素多于变量数量,多余的元素将被忽略。例如:
let [x, y] = [1, 2, 3];
console.log(x); // 1
console.log(y); // 2
let [m, ...rest] = [1, 2, 3, 4];
console.log(m); // 1
console.log(rest); // [2, 3, 4]
在第二个例子中,…rest表示剩余元素,它会将数组中除了m之外的其他元素收集到一个新的数组中。
对象解构
对象解构是从对象中提取属性值并赋给变量,与数组解构不同,对象解构是根据属性名来匹配的,而不是位置。例如:
let person = { name: 'John', age: 30, city: 'New York' };
let { name, age } = person;
console.log(name); // John
console.log(age); // 30
这里,变量name和age分别被赋值为对象person中的name属性和age属性。如果变量名与属性名不一致,可以使用别名来指定:
let { name: personName, age: personAge } = person;
console.log(personName); // John
console.log(personAge); // 30
此外,对象解构也支持默认值。当对象中不存在对应的属性时,变量会被赋值为默认值:
let { gender = 'Male' } = person;
console.log(gender); // Male
3.2 模板字符串
在 JavaScript 中,传统的字符串拼接方式使用+操作符,当拼接的字符串中包含变量时,代码会变得繁琐且不易读。例如:
let name = 'John';
let age = 30;
let message = 'My name is'+ name +'and I am'+ age +'years old.';
console.log(message);
模板字符串则提供了一种更简洁、直观的方式来拼接字符串。模板字符串使用反引号(`)包裹,并且可以在其中嵌入变量和表达式,通过 ${} 来引用。例如:
let name = 'John';
let age = 30;
let message = `My name is ${name} and I am ${age} years old.`;
console.log(message);
模板字符串不仅可以嵌入变量,还可以嵌入函数调用、对象属性访问等表达式,极大地增强了字符串拼接的灵活性。例如:
function getFullName(firstName, lastName) {
return firstName +'' + lastName;
}
let person = { firstName: 'John', lastName: 'Doe' };
let greeting = `Hello, ${getFullName(person.firstName, person.lastName)}!`;
console.log(greeting);
3.3 箭头函数
箭头函数是 ES6 引入的一种新的函数定义方式,它具有更简洁的语法,并且在某些场景下使用起来更加方便。
语法对比
普通函数使用function关键字定义,例如:
function add(a, b) {
return a + b;
}
箭头函数使用=>符号定义,语法更加简洁:
let add = (a, b) => a + b;
当箭头函数只有一个参数时,可以省略参数的括号:
let square = num => num * num;
当箭头函数没有参数时,需要使用空括号:
let getMessage = () => 'Hello, World!';
箭头函数在回调函数中的优势
在使用回调函数时,箭头函数可以使代码更加简洁易读。例如,在数组的map方法中,使用普通函数和箭头函数的对比:
// 使用普通函数
let numbers = [1, 2, 3, 4];
let squaredNumbers1 = numbers.map(function (num) {
return num * num;
});
// 使用箭头函数
let squaredNumbers2 = numbers.map(num => num * num);
可以看到,使用箭头函数后,代码的行数减少了,逻辑更加清晰。此外,箭头函数还有一个重要的特性,它没有自己的this值,而是继承自外层作用域的this,这在一些需要保持this一致性的场景中非常有用。
3.4 Promise
在 JavaScript 中,异步操作是非常常见的,例如网络请求、读取文件等。传统的异步操作使用回调函数来处理,当异步操作嵌套过多时,会导致代码变得难以阅读和维护,这种情况被称为 “回调地狱”。例如:
getData((data1) => {
processData1(data1, (result1) => {
getMoreData(result1, (data2) => {
processData2(data2, (result2) => {
// 更多嵌套...
});
});
});
});
Promise 的出现就是为了解决这个问题,它提供了一种更优雅的方式来处理异步操作。Promise 表示一个异步操作的最终完成(或失败)及其结果值。
创建 Promise
可以通过new Promise()来创建一个 Promise 对象,它接受一个执行器函数作为参数,该函数包含两个参数:resolve和reject。resolve用于处理成功的情况,reject用于处理失败的情况。例如:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
let success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});
处理 Promise
使用then()方法来处理 Promise 的成功结果,使用catch()方法来处理 Promise 的失败情况。例如:
promise.then((result) => {
console.log(result); // 操作成功
}).catch((error) => {
console.error(error); // 操作失败
});
Promise 还支持链式调用,可以将多个异步操作按顺序连接起来,使代码更加清晰。例如:
function step1() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Step 1 result');
}, 1000);
});
}
function step2(result1) {
return new Promise((resolve) => {
setTimeout(() => {
let combinedResult = result1 +'and Step 2';
resolve(combinedResult);
}, 1000);
});
}
step1()
.then(step2)
.then((finalResult) => {
console.log(finalResult); // Step 1 result and Step 2
})
.catch((error) => {
console.error(error);
});
3.5 async/await
async/await是基于 Promise 的语法糖,它使得异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性。
async 函数
async函数是一种特殊的函数,它返回一个 Promise 对象。在async函数内部,可以使用await关键字来暂停函数的执行,直到 Promise 被解决(resolved)或被拒绝(rejected)。例如:
async function getData() {
let response = await fetch('https://example.com/api/data');
let data = await response.json();
return data;
}
在这个例子中,fetch函数返回一个 Promise,await关键字会暂停getData函数的执行,直到fetch操作完成并返回响应。然后,await又会暂停执行,直到响应数据被解析为 JSON 格式。最后,返回解析后的数据。
使用 try/catch 处理错误
由于await只能在async函数内部使用,并且await会抛出错误,因此通常使用try/catch块来捕获和处理错误。例如:
async function processData() {
try {
let data = await getData();
console.log(data);
} catch (error) {
console.error(error);
}
}
3.6 Object.freeze()
在 JavaScript 中,对象默认是可变的,这意味着可以随时添加、修改或删除对象的属性。然而,在某些情况下,我们希望对象是不可变的,以防止意外的修改。Object.freeze()方法就可以实现这一目的。
使对象不可变
Object.freeze()方法会冻结一个对象,被冻结的对象不能添加新属性、不能删除已有属性、不能修改属性的可枚举性、可配置性、可写性,也不能修改已有属性的值。例如:
let person = { name: 'John', age: 30 };
Object.freeze(person);
person.age = 31; // 不会生效,严格模式下会报错
person.gender = 'Male'; // 不会生效,严格模式下会报错
delete person.name; // 不会生效,严格模式下会报错
console.log(person); // { name: 'John', age: 30 }
可以看到,对被冻结的对象进行修改操作都不会生效,这样可以确保对象的状态在某些关键场景下保持不变。
应用场景
Object.freeze()常用于一些需要保持数据一致性和稳定性的场景,比如定义常量对象、防止配置对象被意外修改等。在大型项目中,使用Object.freeze()可以避免因误操作导致的数据错误,提高代码的健壮性。
3.7 Array.prototype.some () 和 Array.prototype.every ()
在处理数组时,经常需要判断数组中的元素是否满足某些条件。Array.prototype.some()和Array.prototype.every()方法提供了便捷的方式来实现这一功能。
some () 方法
some()方法用于检测数组中的元素是否至少有一个满足指定条件。它接受一个回调函数作为参数,回调函数会依次作用于数组中的每个元素,只要有一个元素使回调函数返回true,some()方法就会返回true,否则返回false。例如:
let numbers = [1, 2, 3, 4, 5];
let hasEvenNumber = numbers.some((num) => num % 2 === 0);
console.log(hasEvenNumber); // true
在这个例子中,some()方法检查数组numbers中是否存在偶数,只要有一个偶数,就返回true。
every () 方法
every()方法则用于检测数组中的所有元素是否都满足指定条件。它同样接受一个回调函数作为参数,只有当数组中的所有元素都使回调函数返回true时,every()方法才会返回true,否则返回false。例如:
let allEven = numbers.every((num) => num % 2 === 0);
console.log(allEven); // false
这里,every()方法检查数组numbers中的所有元素是否都是偶数,由于存在奇数,所以返回false。
通过使用some()和every()方法,可以使数组条件判断的代码更加简洁和易读,提高开发效率。
四、实战项目案例
4.1 简易待办事项列表应用
待办事项列表应用是一个非常实用且基础的前端应用,通过它可以很好地练习 HTML、CSS 和 JavaScript 的基础知识以及它们之间的协同工作。接下来,我们将逐步实现一个简单的待办事项列表应用。
4.1.1 设计用户界面
首先,创建一个 HTML 文件,例如index.html,构建应用的基本结构。代码如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>待办事项列表</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>待办事项列表</h1>
<input type="text" id="todo-input" placeholder="添加新的待办事项...">
<button id="add-btn">添加</button>
<ul id="todo-list"></ul>
</div>
<script src="script.js"></script>
</body>
</html>
在上述代码中:
- <input>元素用于接收用户输入的待办事项内容。
- <button>元素用于触发添加待办事项的操作。
- <ul>元素作为待办事项列表的容器,每个待办事项将以<li>元素的形式添加到其中。
接下来,为应用添加一些基本的样式,创建一个styles.css文件,代码如下:
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
.container {
max-width: 400px;
margin: auto;
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
}
input {
width: calc(100% - 100px);
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
padding: 10px;
border: none;
background-color: #28a745;
color: white;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #218838;
}
ul {
list-style-type: none;
padding: 0;
}
li {
padding: 10px;
border-bottom: 1px solid #ccc;
display: flex;
justify-content: space-between;
align-items: center;
}
li.completed {
text-decoration: line-through;
color: #999;
}
这些 CSS 样式实现了以下效果:
- 整体页面背景为淡灰色,待办事项列表容器居中显示,并且有一个白色背景和阴影效果,使其看起来更加突出。
- 输入框和按钮都有适当的样式,输入框占据大部分宽度,按钮在输入框旁边,并且在鼠标悬停时有颜色变化效果,增加交互性。
- 待办事项列表中的每一项都有一定的内边距和底部边框,完成的事项会有一条删除线效果,以区分未完成和已完成的事项。
4.1.2 实现基础功能:添加、删除待办事项
接下来,编写 JavaScript 代码来实现待办事项的添加和删除功能。创建一个script.js文件,代码如下:
document.addEventListener('DOMContentLoaded', function () {
const todoInput = document.getElementById('todo-input');
const addBtn = document.getElementById('add-btn');
const todoList = document.getElementById('todo-list');
// 从本地存储加载待办事项
let todos = JSON.parse(localStorage.getItem('todos')) || [];
// 渲染待办事项列表
function renderTodos() {
todoList.innerHTML = '';
todos.forEach((todo, index) => {
const li = document.createElement('li');
li.textContent = todo.text;
// 添加完成按钮
const completeBtn = document.createElement('button');
completeBtn.textContent = '完成';
completeBtn.addEventListener('click', function () {
todos[index].completed =!todos[index].completed;
renderTodos();
saveTodosToLocalStorage();
});
// 添加删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.addEventListener('click', function () {
todos.splice(index, 1);
renderTodos();
saveTodosToLocalStorage();
});
if (todo.completed) {
li.classList.add('completed');
}
li.appendChild(completeBtn);
li.appendChild(deleteBtn);
todoList.appendChild(li);
});
}
// 保存待办事项到本地存储
function saveTodosToLocalStorage() {
localStorage.setItem('todos', JSON.stringify(todos));
}
// 添加新的待办事项
addBtn.addEventListener('click', function () {
const todoText = todoInput.value.trim();
if (todoText === '') {
alert('请输入待办事项!');
return;
}
todos.push({ text: todoText, completed: false });
todoInput.value = '';
renderTodos();
saveTodosToLocalStorage();
});
// 初始渲染
renderTodos();
});
在这段 JavaScript 代码中:
- 使用document.addEventListener(‘DOMContentLoaded’, function() {})确保页面 DOM 加载完成后再执行后续代码。
- 通过localStorage.getItem(‘todos’)从本地存储中获取已有的待办事项列表,如果没有则初始化为空数组。
- renderTodos函数负责将待办事项列表渲染到页面上,为每个待办事项创建<li>元素,并添加 “完成” 和 “删除” 按钮,同时根据事项的完成状态添加相应的样式类。
- saveTodosToLocalStorage函数将待办事项列表保存到本地存储中,以便在页面刷新后数据不会丢失。
- 为 “添加” 按钮添加点击事件监听器,当用户点击按钮时,获取输入框的值并添加到待办事项列表中,然后清空输入框并重新渲染列表和保存数据。
- 为 “完成” 按钮添加点击事件监听器,点击时切换该事项的完成状态,并重新渲染列表和保存数据。
- 为 “删除” 按钮添加点击事件监听器,点击时从待办事项列表中删除该事项,并重新渲染列表和保存数据。
- 最后,在页面加载完成后,调用renderTodos函数进行初始渲染。
通过以上 HTML、CSS 和 JavaScript 代码的协同工作,我们实现了一个简单的待办事项列表应用,用户可以添加、删除待办事项,并且在页面刷新后数据依然保留。
4.2 天气查询应用
在日常生活中,我们经常需要了解天气情况。通过使用 JavaScript 结合天气 API,我们可以创建一个简单的天气查询应用,方便地获取不同城市的天气信息。下面将详细介绍如何实现这个应用。
4.2.1 获取 API 访问权限
以 OpenWeatherMap API 为例,获取 API 访问权限的步骤如下:
- 注册账号:访问 OpenWeatherMap 官方网站(https://openweathermap.org/),点击注册链接,填写相关信息完成注册。
- 获取 API 密钥:注册成功后,登录到 OpenWeatherMap 账户,在个人资料页面中找到 “API keys” 选项,点击进入后即可看到生成的 API 密钥。这个密钥是访问 API 的凭证,务必妥善保管,不要泄露给他人。
- 保存 API 密钥:为了安全起见,最好将 API 密钥保存在一个环境变量中,或者在项目的配置文件中进行管理。如果在本地开发,可以创建一个.env文件(需确保该文件在版本控制系统中被忽略,防止密钥泄露),并在其中添加如下内容:
API_KEY=your_api_key
然后在 JavaScript 代码中,可以使用一些工具(如dotenv库)来加载环境变量。如果在服务器端部署,可以将 API 密钥设置为服务器的环境变量。
4.2.2 使用 fetch 获取天气数据
首先,在 HTML 文件中添加输入框和显示区域,用于用户输入城市名称和展示天气信息。假设 HTML 文件为weather.html,代码如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天气查询</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 20px;
}
input {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
padding: 10px 20px;
border: none;
background-color: #007BFF;
color: white;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
#weather-info {
margin-top: 20px;
font-size: 18px;
}
</style>
</head>
<body>
<h1>天气查询</h1>
<input type="text" id="city-input" placeholder="请输入城市名称">
<button id="query-btn">查询</button>
<div id="weather-info"></div>
<script src="weather.js"></script>
</body>
</html>
在上述 HTML 代码中:
- <input>元素用于接收用户输入的城市名称。
- <button>元素用于触发天气查询操作。
- <div id=“weather-info”></div>用于显示查询到的天气信息。
接下来,编写 JavaScript 代码来实现天气数据的获取和展示。创建一个weather.js文件,代码如下:
document.addEventListener('DOMContentLoaded', function () {
const cityInput = document.getElementById('city-input');
const queryBtn = document.getElementById('query-btn');
const weatherInfo = document.getElementById('weather-info');
// 从环境变量中获取API密钥(假设已通过dotenv等工具加载)
const apiKey = process.env.API_KEY;
queryBtn.addEventListener('click', function () {
const city = cityInput.value.trim();
if (city === '') {
alert('请输入城市名称');
return;
}
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`;
fetch(apiUrl)
.then(response => {
if (!response.ok) {
throw new Error('网络请求失败:'+ response.statusText);
}
return response.json();
})
.then(data => {
const temperature = data.main.temp;
const humidity = data.main.humidity;
const description = data.weather[0].description;
const weatherText = `城市: ${city}<br>温度: ${temperature}°C<br>湿度: ${humidity}%<br>天气状况: ${description}`;
weatherInfo.innerHTML = weatherText;
})
.catch(error => {
weatherInfo.innerHTML = '获取天气信息失败:'+ error.message;
});
});
});
在这段 JavaScript 代码中:
- 使用document.addEventListener(‘DOMContentLoaded’, function() {})确保页面 DOM 加载完成后再执行后续代码。
- 为查询按钮添加点击事件监听器,当用户点击按钮时,获取输入框中的城市名称。
- 构建 OpenWeatherMap API 的请求 URL,其中包含城市名称、API 密钥和单位设置(这里使用公制单位,温度以摄氏度为单位)。
- 使用fetch函数发送 HTTP 请求到 API,fetch返回一个 Promise 对象。
- 在then回调中,首先检查响应状态是否成功,如果不成功则抛出错误。
- 如果响应成功,将响应数据解析为 JSON 格式。
- 从解析后的数据中提取温度、湿度和天气描述等信息,并将其格式化为 HTML 字符串,更新到页面的weather-info区域进行展示。
- 如果在请求或解析过程中发生错误,将错误信息显示在weather-info区域。
通过以上步骤,我们就实现了一个简单的天气查询应用,用户输入城市名称后,点击查询按钮即可获取该城市的天气信息。
五、常见问题及解决方法
在 JavaScript 开发过程中,经常会遇到一些问题,下面将介绍几个常见问题及其解决方法。
5.1 关于 scrollTop 的问题
在操作页面滚动时,scrollTop是一个常用的属性,用于获取或设置元素的垂直滚动条位置。然而,在使用scrollTop时,有时会遇到设置不生效的情况。这通常是因为只有在容器处于显示状态时,重设scrollTop才会生效,否则将无效。
例如,当一个元素被隐藏(display: none)时,对其设置scrollTop是不会有任何效果的。即使后续将该元素显示出来,之前设置的scrollTop也不会生效。解决这个问题的方法是确保在设置scrollTop之前,元素已经处于显示状态。如果元素需要动态显示并设置scrollTop,可以在元素显示后再进行scrollTop的设置。例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<button id="showButton">显示元素并设置scrollTop</button>
<div id="scrollableDiv" style="height: 200px; overflow-y: auto; display: none;">
<p>这是一段很长的文本,用于测试scrollTop...</p>
<!-- 此处添加更多文本以确保可滚动 -->
</div>
<script>
document.getElementById('showButton').addEventListener('click', function () {
const scrollableDiv = document.getElementById('scrollableDiv');
scrollableDiv.style.display = 'block';
// 使用 setTimeout 确保元素已经显示出来
setTimeout(() => {
scrollableDiv.scrollTop = 100;
}, 0);
});
</script>
</body>
</html>
在上述代码中,通过setTimeout将设置scrollTop的操作延迟到下一个事件循环,确保元素已经显示出来,从而使scrollTop的设置生效。
5.2 关于 cookie 路径的问题
在使用 JavaScript 操作 cookie 时,路径是一个容易被忽视但非常重要的因素。当使用jquery.cookie插件(虽然现在可能更多使用原生的document.cookie或者其他现代的库来处理 cookie,但jquery.cookie在一些老项目中仍有使用)时,一定要注意设置第三个参数,即路径参数。
cookie 的路径决定了哪些页面可以访问该 cookie。如果不设置路径,默认情况下,cookie 的路径是当前页面的路径。这意味着如果在一个子目录下的页面设置了 cookie,而在根目录下的页面尝试访问该 cookie,可能会访问不到。
例如,在/sub - directory/路径下设置了一个 cookie:
$.cookie('username', 'John', { path: '/sub - directory/' });
如果在根目录下的页面尝试读取这个 cookie:
let username = $.cookie('username');
console.log(username); // 可能为 null
由于路径不匹配,可能无法读取到该 cookie。为了使 cookie 在整个网站都能被访问,应该将路径设置为根路径/:
$.cookie('username', 'John', { path: '/' });
这样,无论在网站的哪个页面,都可以正确地读取和设置这个 cookie。
5.3 关于 IE6 之 Fixed 定位问题
IE6 浏览器不支持 CSS2 的fixed定位属性,这给前端开发带来了一定的困扰。不过,有多种方法可以解决这个问题:
- 常规 js 解决之道:通过 JavaScript 不断监听页面的滚动事件,然后动态地调整元素的位置,使其达到类似fixed定位的效果。例如,使用window.onscroll事件来监听滚动,通过element.style.top和element.style.left来调整元素的位置。这种方法应用十分广泛,很多跟着滚动条走的对联广告就是使用此方案。但是,它的缺点是拖动滚动条时元素抖动很厉害,虽然通过平滑处理可以改善一些,但效果仍然不理想。不过,此方案的稳定性与可控性还是比较好的。
- 动用 HTML 结构与布局模拟法:曾经被 163 博客应用,将所有的内容放在一个高度 100% 且滚动条设置为自动的容器中,然后在下面设置一个绝对定位的层。原理是用户拖动的滚动条并不是整个页面的滚动条,而是那个模拟整页的容器的滚动条,所以容器外的地方看起来都是 “静止” 的。这种方法视觉效果达到完美,但存在一些问题:一是需要改变 HTML 结构;二是破坏了用户体验,刷新页面之后滚动条不会停留在原处;三是破坏了一些 JavaScript 事件,如window的scroll事件。
- expression 加 fixed 背景方案:利用expression(CSS 表达式,在 IE5 以上版本支持,但 IE8 的标准模式已不再支持)来模拟fixed定位。通过设置元素的position为absolute,并使用expression来计算元素的位置,使其跟随滚动条的位置变化而变化,同时设置一个固定背景来解决元素抖动的问题。这种方法能够视觉上完美地实现静止定位,但也存在一些问题,用expression模拟fixed外包裹元素实际是设置了absolute,并且遮盖了页面,IE6 可能导致其下页面一些元素无法响应事件,如div、td、span等,只有a、button、input等元素可以响应,那些无法响应事件的元素如果包含了文字,这个问题又会消失。而且,CSS 表达式会进行频繁的求值,在改变窗口大小、滚动页面甚至移动鼠标时都会触发表达式求值,严重影响浏览器的性能。
在实际开发中,需要根据项目的具体需求和兼容性要求来选择合适的解决方案。如果项目不需要兼容 IE6,可以直接使用fixed定位;如果必须兼容 IE6,则需要权衡各种解决方案的优缺点,选择最适合的方法。
六、总结与展望
通过本次 JavaScript 实战,我们回顾了基础语法,掌握了开发环境搭建和诸多实战技巧,还完成了待办事项列表和天气查询应用两个实战项目,同时解决了开发中常见的问题。JavaScript 作为一门强大且灵活的编程语言,在前端开发、后端开发以及移动端开发等领域都有着广泛的应用。
随着技术的不断发展,ES6 及更新版本引入了许多强大的新特性,如解构赋值、模板字符串、箭头函数、Promise、async/await 等,这些特性不仅提高了代码的简洁性和可读性,还大大提升了开发效率。未来,JavaScript 还将持续演进,不断适应新的需求和场景。
希望读者在掌握本文内容的基础上,继续深入探索 JavaScript 的世界,不断学习 ES6 及更新版本的特性,并将其应用到实际项目中。同时,积极参与开源项目,与其他开发者交流经验,不断提升自己的编程能力。相信通过持续的学习和实践,你将在 JavaScript 编程领域取得更大的进步。