从0到1:JavaScript实战秘籍大放送


一、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 包。

安装步骤

  1. 下载安装包:访问 Node.js 官方网站(https://nodejs.org/),根据自己的操作系统下载对应的安装包。
  2. 运行安装程序:下载完成后,双击安装包,按照安装向导的提示进行安装。在安装过程中,可以选择安装路径和其他相关设置,一般保持默认设置即可。
  3. 配置环境变量(可选):在大多数情况下,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 创建并配置项目文件夹

在开始项目开发之前,需要创建一个项目文件夹,并在其中创建和配置相关的文件。

  1. 创建项目文件夹:在本地磁盘上选择一个合适的位置,创建一个新的文件夹,例如my - js - project。
  2. 初始化项目:打开命令行工具,进入项目文件夹,执行以下命令初始化项目:
npm init -y

这个命令会在项目文件夹中生成一个package.json文件,它用于管理项目的依赖包和脚本等信息。-y参数表示使用默认配置,快速初始化项目。

  1. 创建 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 代码。

  1. 创建 JavaScript 文件:在项目文件夹中创建一个script.js文件,用于编写 JavaScript 代码。例如,在script.js中输入以下代码:
console.log('Hello, JavaScript!');

这段代码会在浏览器的控制台输出Hello, JavaScript!。

2.4 基本的版本控制(Git)

Git 是一种分布式版本控制系统,它可以帮助开发者有效地管理代码的版本,追踪文件的变化,方便多人协作开发。以下是 Git 的基本使用方法:

  1. 安装 Git:访问 Git 官方网站(https://git - scm.com/),根据自己的操作系统下载并安装 Git。安装过程中可以选择默认设置,安装完成后,在命令行中输入git --version验证是否安装成功。
  2. 初始化仓库:在项目文件夹中,打开命令行工具,执行以下命令初始化 Git 仓库:
git init

这个命令会在项目文件夹中创建一个隐藏的.git文件夹,用于存储版本控制信息。

  1. 添加文件到暂存区:使用git add命令将文件添加到暂存区。例如,要将index.html和script.js文件添加到暂存区,可以执行以下命令:
git add index.html script.js

也可以使用git add.命令将当前目录下的所有文件添加到暂存区。

  1. 提交更改:将文件添加到暂存区后,使用git commit命令提交更改,并添加提交信息。例如:
git commit -m "Initial commit"

-m参数后面的内容是提交信息,用于描述本次提交的内容,应尽量简洁明了。

  1. 查看提交历史:使用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 访问权限的步骤如下:

  1. 注册账号:访问 OpenWeatherMap 官方网站(https://openweathermap.org/),点击注册链接,填写相关信息完成注册。
  2. 获取 API 密钥:注册成功后,登录到 OpenWeatherMap 账户,在个人资料页面中找到 “API keys” 选项,点击进入后即可看到生成的 API 密钥。这个密钥是访问 API 的凭证,务必妥善保管,不要泄露给他人。
  3. 保存 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 编程领域取得更大的进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑吧邓邓子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值