本节课我们将开始学习javascript!当然,这也许会花费3-5讲的篇幅才能全部讲明白,但是这些工作是十分必要的。没有学会js就学框架非常容易把自己干懵,然后照样啥都不会写,成为一个出色的cv工程师:-(

和之前一样,本文力求速成;然而对于0基础的朋友来说,看一篇语焉不详的博客来打开代码世界的大门是非常不友好的😅。因此,如果您之前学习过C++这种严谨的编译型语言,那么这篇文章将很快地带您入门js


本文目标

通过本文的学习,我们将:

  • 了解javascript的基础语法,包括其内置的数据结构;
  • 熟悉基础的DOM操作;
  • 完成一个小游戏以熟悉上述内容。(怎么样是不是很激动)

课前准备:

1.希望您在此之前学习过至少一门编程语言的基础语法;

2.推荐边看边做!在讲解示例时可以先暂停思考;就算只是跟着敲一遍,您也会有所收获的。🤗


JS简介与发展情况

什么是JS ?

JavaScript(简称 JS)是一种高效、灵活的脚本语言,最初专为网页开发设计,用于在浏览器中动态更新页面内容。与 HTML 负责页面结构、CSS 负责样式不同,JavaScript 负责页面的交互和动态效果。它可以控制页面中的文本、图像、样式等元素,并响应用户操作,从而使网页变得更加生动和互动。

如果HTML是骨架,CSS是血肉,那JS就是网页负责思维的大脑;

如果HTML是名词,CSS是形容词,那JS就是让它们行动起来的动词短语。

简而言之,JS,负责处理网页的服务与逻辑。

JS能被用在哪儿?

虽然 JavaScript 最初只在浏览器环境中运行,但随着其生态的发展,它的应用领域逐渐扩大,包括但不限于以下几个方面:

  1. 网页开发
    • 主要用于前端开发,负责实现页面的交互效果和动态数据展示。
    • 可通过与 HTML、CSS 的结合,使网页呈现丰富的用户体验。
  2. 服务器(后)端开发
    • 使用 Node.js 等运行环境,JavaScript 也可以在服务器端执行,构建后端 API 服务和服务器应用程序。
    • Node.js 提供高效的 I/O 性能,适合实时性强的应用,例如聊天室、实时数据处理。
  3. 移动端应用
    • 通过 React Native、Weex、UniApp 等框架,可以使用 JavaScript 开发原生移动应用程序。
    • 这种跨平台开发的方式大幅提高了开发效率,一份代码可运行于多端。
  4. 桌面应用
    • 使用 Electron 等工具,可以将 JavaScript 应用程序打包成桌面应用程序(如 VSCode 就是使用 Electron 开发的)。

JS发展历程

本部分主要是为了阐明ES6的重要性,赶时间可以跳过。

参考链接:https://javascript.ruanyifeng.com/introduction/history.html

JavaScript 因为互联网而生,紧随着浏览器的出现而问世。回顾它的历史,就要从浏览器的历史讲起。

1990年底,欧洲核能研究组织(CERN)科学家Tim Berners-Lee,在全世界最大的电脑网络——互联网的基础上,发明了万维网(World Wide Web),从此可以在网上浏览网页文件。最早的网页只能在操作系统的终端里浏览,也就是说只能使用命令行操作,网页都是在字符窗口中显示,这当然非常不方便。

1992年底,美国国家超级电脑应用中心(NCSA)开始开发一个独立的浏览器,叫做Mosaic。这是人类历史上第一个浏览器,从此网页可以在图形界面的窗口浏览。

1994年10月,NCSA的一个主要程序员Marc Andreessen联合风险投资家Jim Clark,成立了Mosaic通信公司(Mosaic Communications),不久后改名为Netscape。这家公司的方向,就是在Mosaic的基础上,开发面向普通用户的新一代的浏览器Netscape Navigator。

1994年12月,Navigator发布了1.0版本,市场份额一举超过90%。

Netscape 公司很快发现,Navigator浏览器需要一种可以嵌入网页的脚本语言,用来控制浏览器行为。当时,网速很慢而且上网费很贵,有些操作不宜在服务器端完成。比如,如果用户忘记填写“用户名”,就点了“发送”按钮,到服务器再发现这一点就有点太晚了,最好能在用户发出数据之前,就告诉用户“请填写用户名”。这就需要在网页中嵌入小程序,让浏览器检查每一栏是否都填写了。也就是说,目前急需一门为网页加入各种即时交互效果的语言出现。

于是,在1995年,Netscape找来了函数式编程领域大神Brendan Eich,而这位老哥仅在短短 10 天里创造出了 JavaScript。

那时的 JavaScript 只是个网页上的”小工具”——它能处理简单的交互,比如按钮点击、表单提交等,但就是这样的一种”小脚本语言”迅速火遍了整个互联网!

为了让 JavaScript 在各种浏览器上都能流畅地运行,1997 年,JavaScript 被送上了“国际舞台”,由 ECMA 组织将其标准化,于是诞生了第一个 ECMAScript 标准版本。这为 JavaScript 的跨平台发展铺平了道路。

JavaScript 的真正”进化”始于 ECMAScript 3,这一版本引入了很多新的特性,使得它可以处理更复杂的逻辑。到了 2009 年的 ECMAScript 5(ES5),JavaScript 彻底迈入成熟期,增加了严格模式和 JSON 支持等关键特性。可以说,ES5 让 JavaScript 摆脱了”小脚本”的形象,正式成为一门可用于大型项目的编程语言。

然而,JavaScript 真正腾飞的转折点要数 2015 年的 ECMAScript 6(ES6)。这次更新不仅带来了让人惊叹的新语法(如箭头函数、模板字符串、let/const 声明等),还为模块化开发、面向对象编程提供了新思路。ES6 的新特性极大提高了开发效率,使得 JavaScript 不再是页面上的辅助工具,而成为构建现代应用的核心语言。JavaScript 在这一年一跃成为业界的“明星语言”。

在 ES6 之后,JavaScript 开发社区也逐渐形成了每年发布一次新版本的习惯。比如 ES7、ES8 等陆续推出的版本都对 JavaScript 进行了小而有效的改进,新增的 async/awaitObject.entries 等功能,让开发更加简洁和现代化。

Javascript 与 Java 到底是什么关系?有人说他们就像老婆和老婆饼,其实也不尽然。

调用方式

  • 内联方式:将 JavaScript 代码直接写在 HTML 文件中 <script> 标签内。
  • 外部文件方式:通过 <script src="file.js"></script> 引入外部 JavaScript 文件。

请将以下代码复制到index.html,并在同一目录下创建一个js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>js01</title>
<style>
body {
height: 100vh;
display: flex;
align-items: center;
background: linear-gradient(to top left, #28b487, #7dd56f);
}
h1 {
font-family: sans-serif;
font-size: 50px;
line-height: 1.3;
width: 100%;
padding: 30px;
text-align: center;
color: white;
}
</style>
</head>
<body>
<h1>JavaScript Fundamentals</h1>

<script src="test.js"></script>
</body>
</html>

打开Live Server,使得电脑调整到如下状态即可开始编程:

使用严格模式

严格模式是在 ES5 引入的,它通过 'use strict'; 语句启用。

启用严格模式之前,Js经常会悄无声息地出现运行错误或bug(没有开玩笑),而这种错误对于开发者来说这是一个调试噩梦。严格模式帮助避免了一些难以追踪的错误,并且使代码更加安全和高效。

举个例子,在装mod之前,Js甚至允许一个没有被声明的变量直接输出:

而使用严格模式之后,Js总算变得聪明了一点:

所以,无论如何,请在Js文件的开头加上'use strict';

变量与运算符

letconst

在 JavaScript 中,letconst 是用来声明变量的关键字。它们的出现(尤其是在 ES6 之后)帮助解决了代码中常见的变量提升和作用域混乱问题。

  • let
    用于声明可以重新赋值的变量。
  • const
    用于声明常量,表示变量在初始化后不能被重新赋值。
1
2
3
4
5
6
7
// 使用 let 声明变量
let age = 25;
age = 30; // 允许重新赋值

// 使用 const 声明常量
const name = "Alice";
name = "Bob"; // 报错:Assignment to constant variable

注意:虽然 const 声明的变量不能被重新赋值,但如果该变量存储的是一个对象或数组,则对象的内容是可以改变的。

另外,虽然也可以使用var命名变量,但是一般我们不这么做。

变量类型

JavaScript 支持多种变量类型,可以分为以下几类:

  1. 基本类型
    • number:表示数字类型,包括整数和浮点数。
    • string:表示字符串类型。
    • boolean:布尔类型,只有 truefalse 两个值。
    • undefined:未定义类型,表示变量未赋值。
    • null:空类型,表示没有对象。
    • symbol:唯一值,用于创建唯一标识符。
  2. 引用类型
    • object:用于存储多个值的集合或复杂的数据结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 基本类型示例
let score = 100; // number
let message = "Hello"; // string
let isActive = true; // boolean
let data; // undefined
let empty = null; // null

// 引用类型示例
let user = { name: "Alice", age: 25 }; // object
let numbers = [1, 2, 3, 4]; // array

//利用console.log()输出
console.log(score);
console.log(message);
console.log(isActive);
console.log(data);
console.log(empty);
console.log(user);
console.log(numbers);

可以在浏览器的控制台查看结果:

运算符

算术运算符

+:加法 -:减法 *:乘法

/:除法 %:取余 **:幂运算(指数)

1
2
3
let a = 10;
let b = 3;
console.log(a ** b); // 1000

赋值运算符

=:赋值

+=-=*=/=%=:复合赋值运算符

*比较运算符

><>=<=:大小比较

=====:判断相等 !=!==:判断不等

请注意:

==(宽松相等)仅比较值,在比较前会进行类型转换。如果不同类型的数据可以转换成相同的值,它会返回 true

===(严格相等):会比较值和类型,只有当值和类型都相同的情况下,结果才为 true

由于双等号会导致一些意料以外的结果,所以实际编程中,请使用三等号。

1
2
3
4
5
6
7
8
console.log(5 === "5");   // false,因为类型不同
console.log(5 == "5"); // true,"5" 被转换成数字 5

console.log(false === 0); // false,因为类型不同
console.log(false == 0); // true,false 被转换成数字 0

console.log(null === undefined); // false,不同类型
console.log(null == undefined); // true,特殊情况下被视为相等

逻辑运算符

&&:与(AND) ||:或(OR) !:非(NOT)

类型转换

在 JavaScript 中,数据类型转换通常发生在以下几种情况:

  1. *自动转换

    • 当涉及不同类型的操作时,JavaScript 会自动将类型转换成适合的类型。例如,字符串和数字相加会将数字转换为字符串。
    1
    2
    console.log("5" + 3);  // 输出 "53" (字符串拼接)
    console.log("5" - 3); // 输出 2 ("5" 自动转换为数字 5)
  2. 手动转换

    • 可以通过 Number()String() 等函数将数据转换成指定类型。
    1
    2
    3
    4
    5
    6
    7
    let str = "123";
    let num = Number(str); // 将字符串转换为数字
    console.log(num); // 123

    let value = 42;
    let text = String(value); // 将数字转换为字符串
    console.log(text); // "42"

输入与输出

输入:

  • 使用 prompt() 获取用户输入。

输出:

  • 使用 alert() 弹出提示框。
  • 使用 console.log() 输出调试信息。
  • 使用 document.write() 动态插入 HTML 内容。
1
2
3
4
5
6
7
8
9
10
11
let name = prompt("请输入你的名字:");
console.log("你输入的名字是:" + name);

// 使用 alert() 弹出提示信息
alert("欢迎访问我们的网站!");

// 使用 console.log() 输出到控制台
console.log("调试信息:页面加载完成");

// 使用 document.write() 将内容插入到 HTML 页面
document.write("<h2>欢迎来到 JavaScript 教程!</h2>");

格式化输出

模板字符串允许你在字符串中嵌入表达式,用反引号 ``` 包裹,支持多行字符串和变量插值。它让格式化输出更加简洁和清晰。

1
2
3
let name = "Bob";
let age = 30;
console.log(`Name: ${name}, Age: ${age}`); // 输出: Name: Bob, Age: 30
  • ${} 是模板字符串的占位符,可以插入任何有效的 JavaScript 表达式。
  • 模板字符串还支持换行,不需要使用 \n 来插入换行符。
1
2
3
4
5
6
let message = `Hello, ${name}!
You are ${age} years old.`;
console.log(message);
// 输出:
// Hello, Bob!
// You are 30 years old.

最常用的就是上面说到的。当然,实际上还有很多别的用法,这里用截图的方式呈现:

条件与循环语句

由于此处与C++语言几乎相同,(事实上这部分我感觉所有语言都差不多)因此本处不再赘述。

数据类型

函数

函数声明

声明的函数可以在定义前被调用:

1
2
3
4
5
console.log(greet("Alice"));  // 输出: Hello, Alice!

function greet(name) {
return "Hello, " + name + "!";
}

函数表达式

函数表达式将函数赋值给变量。与函数声明不同,函数表达式不会被提升,必须在定义后才能调用。

语法:

1
2
3
4
const 函数变量 = function (参数1, 参数2, ...) {
// 函数体
return 返回值; // 可选
};

示例:

1
2
3
4
5
const greet = function(name) {
return "Hello, " + name + "!";
};

console.log(greet("Bob")); // 输出: Hello, Bob!

*箭头函数

箭头函数是 ES6 引入的一种更简洁的函数写法,尤其适合定义简单的匿名函数。它使用 => 语法,**并且不会创建自己的 this,而是从父作用域继承 this**,因此箭头函数通常用于简化回调函数和匿名函数的写法。(this关键字会在后面的对象部分讲到)

语法:

1
2
3
4
const 函数变量 = (参数1, 参数2, ...) => {
// 函数体
return 返回值; // 如果函数体有多条语句
};
  • 当函数只有一个参数时,括号可以省略。

  • 当函数只有一条 return 语句时,大括号和 return 关键字可以省略。

示例:

  1. 简单箭头函数:

    1
    2
    const greet = name => "Hello, " + name + "!";
    console.log(greet("Charlie")); // 输出: Hello, Charlie!
  2. 多参数和多语句的箭头函数:

    1
    2
    3
    4
    5
    const add = (a, b) => {
    const sum = a + b;
    return sum;
    };
    console.log(add(5, 3)); // 输出: 8

补充:作用域与作用域链

在 JS中,作用域(Scope)决定了代码中变量的可见性和生命周期。作用域分为全局作用域、函数作用域和局部作用域,而 作用域链(Scope Chain)则是 JS 查找变量的一种机制,通过层层查找链条来确定变量的值。

实际上这部分内容也和C++中差不多,然而在javascript中似乎能够更加容易理解,因此补充在这儿。

作用域的类型

全局作用域
  • 在代码的任何位置都可以访问的作用域。
  • 任何不在函数或代码块内定义的变量,都会自动成为全局变量。
函数作用域
  • 函数内部定义的变量只能在该函数内部访问,不会影响或污染全局作用域。
  • 使用 var 声明的变量有函数作用域,letconst 则具有块级作用域(见下方代码块作用域)。

然而,也正是因为var的不严谨才使其逐渐被废弃。

块级作用域
  • 块级作用域是由 {} 包裹的代码块,使用 letconst 声明的变量在代码块外不可访问。
  • iffor 等结构内声明的变量会被限制在代码块内。

作用域链

简单来说,一个作用域可以访问他的父级作用域中的变量,但是不能访问兄弟或者儿子作用域中的变量。

数组

与 C++ 等语言中的数组不同,JavaScript 数组的元素可以是不同的数据类型。这意味着我们可以在一个数组中包含数值、字符串、其他数组、对象,甚至函数等。我们可以通过“下标”(索引)来访问数组中的元素。

示例:

1
2
3
4
5
6
7
8
9
let b = [
1, // 数字
"0kr", // 字符串
['a', 'b', 3], // 数组
function () { // 函数
console.log("Hello World");
},
{ name: "0kr", age: 3 } // 对象
];

数组的常用属性和方法

1. length 属性
  • length 属性表示数组的长度,即数组中元素的数量。
  • 注意:length 是属性,不是方法,调用时不需要加括号。

示例:

1
2
let arr = [1, 2, 3, 4, 5];
console.log(arr.length); // 输出:5
2. push() 方法
  • push() 方法用于向数组末尾添加一个或多个元素。
  • 它会直接修改原数组,并返回新的数组长度。

示例:

1
2
3
let arr = [1, 2, 3];
arr.push(4);
console.log(arr); // 输出:[1, 2, 3, 4]
3. pop() 方法
  • pop() 方法用于删除数组的最后一个元素。
  • 它会直接修改原数组,并返回被删除的元素。

示例:

1
2
3
4
let arr = [1, 2, 3];
let removedElement = arr.pop();
console.log(arr); // 输出:[1, 2]
console.log(removedElement); // 输出:3

对象

对象由键(key)和值(value)组成的键值对构成,与 C++ 中的 map 数据结构类似。每个键值对中的“键”是属性的名称(或方法的名称),而“值”则是属性的值(或方法的实现)。

对象能够包含不同类型的数据,包括变量、数组、其他对象,甚至函数。这种灵活性使得对象非常适合表示复杂的数据结构。

对象的基本语法

我们可以使用花括号 {} 创建一个对象,并在其中定义键值对。

示例:

1
2
3
4
5
6
7
8
let person = {
name: "yzl",
age: 18,
money: 0,
addMoney: function(x) {
this.money += x;
}
};

*对象属性与方法的访问方式

在 JavaScript 中,可以通过两种方式访问对象的属性和方法:

  1. 点(.)表示法
    • 适合大多数场景,更加直观简洁。
    • 格式:对象名.属性名对象名.方法名()
  2. 方括号([])表示法
    • 如果属性名称是动态的(例如存储在变量中),或者属性名称包含特殊字符,使用方括号更合适。
    • 格式:对象名["属性名"]对象名["方法名"]()
    • 如果对象的属性名或者方法名也是变量,请使用方括号表示法!!!!!!!!!!!!!!!!!

示例:

1
2
3
4
5
6
7
8
9
// 使用点表示法访问属性和方法
console.log(person.name); // 输出:yzl
person.addMoney(100); // 调用 addMoney 方法
console.log(person.money); // 输出:100

// 使用方括号表示法访问属性和方法
console.log(person["name"]); // 输出:yzl
// person调用 addMoney 方法
console.log(person["money"]); // 输出:150

*this 关键字

在对象的方法中,this 关键字表示当前对象,即调用该方法的对象实例。它允许方法访问对象的其他属性或方法。

示例:

1
2
3
4
5
6
7
8
9
10
let car = {
brand: "Toyota",
fuel: 50,
refuel: function(amount) {
this.fuel += amount;
console.log("Fuel level: " + this.fuel);
}
};

car.refuel(10); // 输出:Fuel level: 60

refuel 方法中,this.fuel 表示当前 car 对象的 fuel 属性。

动态添加和删除对象的属性

JavaScript 对象的灵活性不仅体现在多种类型的键值对上,还体现在动态的属性管理上。我们可以随时为对象添加或删除属性。

添加新属性:
1
2
person.job = "Developer";
console.log(person.job); // 输出:Developer
删除属性:
1
2
delete person.age;
console.log(person.age); // 输出:undefined

综合应用

假设我们需要一个表示书籍信息的对象,可以使用对象来存储书籍的标题、作者、出版年份以及查看详细信息的功能。

1
2
3
4
5
6
7
8
9
10
let book = {
title: "JavaScript Essentials",
author: "John Doe",
year: 2022,
getSummary: function() {
return `${this.title} by ${this.author}, published in ${this.year}.`;
}
};

console.log(book.getSummary()); // 输出:JavaScript Essentials by John Doe, published in 2022.

在上例中,我们定义了 book 对象,其中包含书籍的 titleauthoryear 属性以及一个 getSummary 方法,该方法返回书籍的摘要信息。

补充:this 在箭头函数中的特性

简单来说就是,**箭头函数没有自己的 this**,它将继承离自己最近的(父级)作用域的属性。

一个示例搞明白这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let name = "zhj"
let person = {
name: "yzl",
age: 25,
// 使用普通函数定义方法
sayHello: function() {
console.log("Hello, I am " + this.name);
},
// 使用箭头函数定义方法
sayHelloArrow: () => {
console.log("Hello, I am " + this.name);
}
};

person.sayHello(); // 输出: Hello, I am yzl
person.sayHelloArrow(); // 输出: Hello, I am zhj

到目前为止,我们仍然在用原生的Js编写代码,只不过是利用Chrome浏览器显示我们的编写结果。接下来,我们将学习如何像其他任何网站一样,真正用Js操作网页上的元素。

DOM 操作

DOM 树与 Web APIs 简介

DOM(文档对象模型) ,基本上是html文档的结构化表示。

DOM 通过将网页的各个部分(如标签、属性、文本内容等)映射为对象,允许开发者在 JavaScript 中以对象的方式进行操作。

DOM 树:

在浏览器中加载 HTML 文档后,浏览器会将整个 HTML 文档解析为一个 DOM 树。DOM 树是由多个节点构成的,每个节点代表 HTML 文档中的一个元素、文本或属性。树形结构从 document 节点开始,通过父子关系(父节点、子节点)构成树形层次结构。

Web APIs:

事实上,DOM并不是Js的一部分(并不在Js的内置语法中),而是在不同浏览器提供的Web APIs中。Web APIs是浏览器提供的一组接口,允许 JavaScript 和浏览器之间进行交互,控制网页的内容、样式、事件等,你可以简单理解为一个外部的链接库。而我们要做的就是去调用这些接口中的DOM操作。

为了更好地教学,接下来我将用内联样式呈现代码,注意分辨html语言和Js。

操纵字符或值

获取/修改文本内容:

使用 textContentinnerText 属性可以获取元素的文本内容。

1
2
3
4
5
6
<p class="para">Hello World</p>
<script>
//let text = document.querySelector('.para').textContent;
//console.log(text); // 输出: Hello World
document.querySelector('.para').textContent = "jkl";
</script>

获取/修改数值:

利用value属性完成操作。

1
2
3
4
5
6
<input type="number" class="guess" />

<script>
document.querySelector('.guess').value = 23;
console.log(document.querySelector('.guess').value);
</script>

当然,你也可以不通过类来操纵元素,通过id也是可以的,比如document.querySelector('#myId').

还可以同时操作一个类的所有标签,比如使用querySelectorAll()方法。

还有其他如getElementById()的操纵方法,详见:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/querySelectorAll

操纵CSS样式

CSS 样式通过 style 属性控制。你可以通过 style 属性直接修改元素的样式,或通过修改 classList 操作元素的类名来控制样式。

*修改内联样式

1
2
3
4
5
6
7
8
9
<p id="demo">This is a paragraph.</p>
<button onclick="changeStyle()">Change Style</button>

<script>
function changeStyle() {
document.getElementById("demo").style.color = "blue"; // 设置字体颜色为蓝色
document.getElementById("demo").style.fontSize = "20px"; // 设置字体大小为20px
}
</script>

在这个例子中,当点击按钮时,<p> 元素的字体颜色和大小都会改变。

另外值得指出的是,当style样式的名字超过1个单词,如background color时,此时我们并不能像学习CSS那样在中间加上-。Js只支持识别驼峰命名法,即需要把从第二个单词开始的首字母全部大写。

因此,想要操纵背景颜色的样式就得这么写:

1
document.getElementById("demo").style.backgroundColor = "blue";

修改类名来控制样式

通过 classList 你可以方便地操作元素的类名,从而实现样式的变化。

classList API中实现的方法:

  1. add("className"):向元素添加一个或多个类。
  2. remove("className"):从元素移除一个或多个类。
  3. toggle("className"):在元素上切换指定的类,如果类存在则移除,如果不存在则添加。
  4. contains("className"):检查元素是否包含指定的类,返回 truefalse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<p id="demo" class="normal-text">This is a paragraph.</p>
<button onclick="changeClass()">Change Class</button>

<style>
.normal-text {
font-size: 16px;
color: black;
}
.new-text {
font-size: 20px;
color: red;
}
</style>

<script>
function changeClass() {
document.getElementById("demo").classList.toggle("new-text");
}
</script>

在这个例子中,当点击按钮时,<p> 元素的类名会在 normal-textnew-text 之间切换,从而改变元素的样式。

操纵事件

孩子们,最阴的来了🤣

想要操纵事件,那么就必须了解事件侦听器函数addEventListener(). 在调用时,它需要至少两个参数:

1
element.addEventListener(event, function);
  • event:指定要监听的事件类型(如 "click""mouseover" 等)。
  • function:指定事件触发时执行的函数。

比如说,我们想做一个点击按钮就会触发输出的小功能:

1
2
3
4
5
6
7
8
9
<button id="btn">Click Me</button>
<p id="demo"></p>

<script>
// 获取按钮元素并为其添加事件监听器
document.getElementById("btn").addEventListener("click", function() {
document.getElementById("demo").textContent = "Button was clicked!";
});
</script>

键盘事件

键盘事件用于监听用户的键盘操作。最常见的键盘事件包括 keydown(按下键时触发)和 keyup(松开键时触发)。

常见的键盘事件类型:

  • keydown:在键盘按下某个键时触发。
  • keyup:在键盘松开某个键时触发。
  • keypress:按下并保持按键时触发。
1
2
3
4
5
6
7
8
<input type="text" id="inputField" placeholder="Type something">
<p id="demo"></p>

<script>
document.getElementById("inputField").addEventListener("keydown", function(event) {
document.getElementById("demo").textContent = "You pressed: " + event.key;
});
</script>

在这个例子中,当用户按下键盘的某个键时,输入框下方的 <p> 元素将显示用户按下的键。

其他事件

感兴趣的同学可以一一尝试:

表单事件
  • focus:聚焦某个元素
  • blur:取消聚焦某个元素
  • change:某个元素的内容发生了改变
窗口事件

需要作用到 window 元素上

  • resize:当窗口大小发生变化
  • scroll:滚动指定的元素
  • load:当元素被加载完成

小游戏:猜数字

现在,通过一个小游戏来复习刚才的知识吧!

Step1:项目初始化

请新建一个文件夹,并将已经撰写好的html与css文件放入其中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="stylesheet" href="style.css" />
<title>Guess My Number!</title>
</head>
<body>
<header>
<h1>Guess My Number!</h1>
<p class="between">(Between 1 and 20)</p>
<button class="btn again">Again!</button>
<div class="number">?</div>
</header>
<main>
<section class="left">
<input type="number" class="guess" />
<button class="btn check">Check!</button>
</section>
<section class="right">
<p class="message">Start guessing...</p>
<p class="label-score">💯 Score: <span class="score">20</span></p>
<p class="label-highscore">
🥇 Highscore: <span class="highscore">0</span>
</p>
</section>
</main>
<script src="script.js"></script>
</body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
@import url('https://fonts.googleapis.com/css?family=Press+Start+2P&display=swap');

* {
margin: 0;
padding: 0;
box-sizing: inherit;
}

html {
font-size: 62.5%;
box-sizing: border-box;
}

body {
font-family: 'Press Start 2P', sans-serif;
color: #eee;
background-color: #222;
/* background-color: #60b347; */
}

/* LAYOUT */
header {
position: relative;
height: 35vh;
border-bottom: 7px solid #eee;
}

main {
height: 65vh;
color: #eee;
display: flex;
align-items: center;
justify-content: space-around;
}

.left {
width: 52rem;
display: flex;
flex-direction: column;
align-items: center;
}

.right {
width: 52rem;
font-size: 2rem;
}

/* ELEMENTS STYLE */
h1 {
font-size: 4rem;
text-align: center;
position: absolute;
width: 100%;
top: 52%;
left: 50%;
transform: translate(-50%, -50%);
}

.number {
background: #eee;
color: #333;
font-size: 6rem;
width: 15rem;
padding: 3rem 0rem;
text-align: center;
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 50%);
}

.between {
font-size: 1.4rem;
position: absolute;
top: 2rem;
right: 2rem;
}

.again {
position: absolute;
top: 2rem;
left: 2rem;
}

.guess {
background: none;
border: 4px solid #eee;
font-family: inherit;
color: inherit;
font-size: 5rem;
padding: 2.5rem;
width: 25rem;
text-align: center;
display: block;
margin-bottom: 3rem;
}

.btn {
border: none;
background-color: #eee;
color: #222;
font-size: 2rem;
font-family: inherit;
padding: 2rem 3rem;
cursor: pointer;
}

.btn:hover {
background-color: #ccc;
}

.message {
margin-bottom: 8rem;
height: 3rem;
}

.label-score {
margin-bottom: 2rem;
}

并在相同目录下新建script.js, 即可得到这样的初始化界面:

可能会用到的Js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';
//生成1-20的随机数
let secretNumber = Math.trunc(Math.random() * 20) + 1;

/*
console.log(document.querySelector('.message').textContent);
document.querySelector('.message').textContent = '🎉 Correct Number!';

document.querySelector('.number').textContent = 13;
document.querySelector('.score').textContent = 10;

document.querySelector('.guess').value = 23;
console.log(document.querySelector('.guess').value);
*/

Step2:梳理需求

结合前文中gif的演示,我们把需求分为以下几点:

  1. 在表单中输入数字,点击check按钮后,Js能够获取到数字(此处需要过滤空字符、字母和字符串);
  1. 拿到数字以后能够实现逻辑判断:

​ ·当猜的数字低于正确数字,输出”Too low”相关语句;

​ ·当猜的数字高于正确数字,输出”Too high”相关语句;

​ ·当猜的数字等于正确数字,输出”Correct”相关语句,中间盒子显示正确的数字,盒子宽度变大,背景变成绿色;

  1. 点击”Again”按钮,重新生成随机数。将当前分数调整为20。若上一局的分数比”HighScore”更高,则替换之。
  2. 额外需求:当玩家猜满20次依然没有猜到时,输出”You lose the game!!”相关内容。

Step3:Coding!

根据项目需求和实现逻辑完成该小游戏。事实上,要用到的所有知识点都已经在本文中阐明!希望大家能体验到前端如同搭积木一般的爽感。另外,做完任意一个逻辑或者需求之后不要忘记测试环节(如利用console调试等),这点也很重要。

细究下来,其实只是几个if-else语句罢了,没有看上去那么难。不过在此处,我想再讲讲可能值得优化的地方:

1.低于数字、高于数字=>三元运算符

这段代码有大量的重复之处,只有高和低输出的语句不同。因此,我们可以把他们进行合并:

1
2
3
4
5
6
7
8
9
10
11
if (guess !== secretNumber) {
if (score > 1) {
document.querySelector('.message').textContent =
guess > secretNumber ? '📈 Too high!' : '📉 Too low!';
score--;
document.querySelector('.score').textContent = score;
} else {
document.querySelector('.message').textContent = '💥 You lost the game!';
document.querySelector('.score').textContent = 0;
}
}

非常的优雅,一下子就清爽很多((

2.封装函数

观察到代码里有大量的querySelector的DOM操作方法,又臭又长,不能忍!遂决定对封装一个选择元素的函数:

1
2
3
const displayMessage = function (messages) {
document.querySelector('.message').textContent = messages;
};

于是我们就可以进一步简化代码:

1
2
// document.querySelector('.message').textContent = '⛔️ No number!';
displayMessage('⛔️ No number!');

最终实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
'use strict';

let secretNumber = Math.trunc(Math.random() * 20) + 1;
let score = 20;
let highscore = 0;

const displayMessage = function (messages) {
document.querySelector('.message').textContent = messages;
};

document.querySelector('.check').addEventListener('click', function () {
const guess = Number(document.querySelector('.guess').value);
//console.log(guess, typeof guess);

// 没有输入时
if (!guess) {
displayMessage('⛔️ No number!');

// 当玩家获胜
} else if (guess === secretNumber) {

displayMessage('🎉 Correct Number!');
document.querySelector('.number').textContent = secretNumber;
//将背景修改为绿色 加宽边长
document.querySelector('body').style.backgroundColor = '#60b347';
document.querySelector('.number').style.width = '30rem';

if (score > highscore) {
highscore = score;
document.querySelector('.highscore').textContent = highscore;
}

// 猜错了
} else if (guess !== secretNumber) {
if (score > 1) {
displayMessage(guess > secretNumber ? '📈 Too high!' : '📉 Too low!');
score--;
document.querySelector('.score').textContent = score;
} else {
document.querySelector('.score').textContent = 0;
}
}
});

//按下按钮重置界面
document.querySelector('.again').addEventListener('click', function () {
score = 20;
secretNumber = Math.trunc(Math.random() * 20) + 1;
displayMessage('Start guessing...');
document.querySelector('.score').textContent = score;
document.querySelector('.number').textContent = '?';
document.querySelector('.guess').value = '';
//恢复背景与边宽
document.querySelector('body').style.backgroundColor = '#222';
document.querySelector('.number').style.width = '15rem';
});