html / CSS

主要介绍 html/css 入门知识,用来做 typescript 的前置知识。配套课程文件

html

HTML(Hypertext Markup Language):The language your web browser uses to describe the content and structure of web pages

如果整个网页当作人体,那么 html 就是骨架,决定每一资源放置的位置

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<title>Title!</title>
</head>
<body>
<h1>Heading!</h1>
<p>Paragraph!</p>
</body>
</html>

alt text

文档对象模型(DOM)通过将文档的结构(例如表示网页的 HTML)以对象的形式存储在内存中,将网页与脚本或编程语言连接起来。尽管将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 核心语言的一部分,但它通常与 JavaScript 相关。

DOM 使用逻辑树的形式来表示文档。树的每个分支末端都是一个节点,每个节点都包含对象。DOM 方法允许以编程方式来访问树。借助这些方法,你可以改变文档的结构、样式或内容。

节点还可以附加事件处理器。一旦事件被触发,相应的事件处理器就会被执行。

html 是按照 “DOM” (Document Object Model) 的结构进行表达的。因此,上述代码中包含一个 header 和 一个 body

其中 header 是不可见的,其中包含有

tag

html 中的 tag 是由两部分组成的,分别叫做 Opening Tag 和 Closing Tag

1
<tagName> content </tagName>

除此之外,tag 中还可以加入属性(attribute)

HTML Attribute(HTML 属性) 是写在开始标签内部的额外信息,用于配置或修饰该元素的行为、样式或内容。

1
<tagName ABC = "XYZ"> content </tagName>

此外,还有一类特殊的标签——它们没有内容,因此可以省略闭合标签 ,这类标签称为 Void Elements(空元素 / 自闭合标签)。例如:

标签 作用
<img> 嵌入图片
<br> 换行
<hr> 水平分隔线
<input> 输入框
<meta> 元信息
<link> 引入外部资源(如 CSS)
<area> 图像映射区域
<base> 基准 URL
<col> 表格列属性
<embed> 嵌入外部内容
<param> 对象参数
<source> 媒体资源
<track> 媒体字幕
<wbr> 可选换行点

使用方法如下:

1
2
3
4
5
6
7
8
9
<!-- 简洁写法(推荐) -->
<img src="photo.jpg" alt="描述">
<br>
<hr>

<!-- 自闭合写法(XHTML 风格,也合法) -->
<img src="photo.jpg" alt="描述" />
<br />
<hr />

Common tag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 插入链接 -->
<a href="http://weblab.mit.edu">Link to web.lab!</a>

<!-- 插入图片 -->
<img src="pusheen.gif" >
<img src="pusheen.gif" alt="pic of pusheen"/>

<!-- 序列 -->
<!-- <ol>: ordered list -->
<!-- <ul>: unordered list -->
<!-- <li>: list item -->

<ul>
<li>item 1</li>
<li>item 2</li>
</ul>

其中比较特殊的是 <div><span><div> 本质上是创建了一个 box(块级容器),用于将所有内容封装到一个独立的盒子中;<span> 本质上则是一个 行内标记,用于在文本流中包裹小段内容而不打断布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>

<header></header>

<body>
<!-- div:块级,独占一行,常用于布局 -->
<div style="background: yellow;">
这是一个 div,它会独占一整行
</div>
<div style="background: lightblue;">
这是另一个 div,自动换行
</div>

<!-- span:行内,不独占行,常用于文本修饰 -->
<p>
这是一段文字,<span style="color: red;">这里标红</span>
<span style="font-weight: bold;">这里加粗</span>,其余正常。
</p>

</body>

alt text

但是,在后续的 html5 中引入了语义化标签,可以明显添加可读性,然后简化语法,可以减少使用 <div> 的使用。

CSS

CSS(Cascading Style Sheets):The rules that tell your web browser how stuff looks

如果说 html 是整个网页的骨架,那么 css 就是用来美化这些骨架的衣服。可以让我们的网页变的更加的美观。

基础的语法为

1
2
3
4
selector {
property: value;
property: value;
}

一个基础的示例为:

1
2
3
4
5
6
/* 这是注释 */
div {
color: red;
font-family: Arial;
font-size: 24pt;
}

这部分直接使用 <style> 将 css 嵌入到 html 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>示例</title>
<style>
div {
color: red;
font-family: Arial;
font-size: 24pt;
}
</style>
</head>
<body>
<div>这段文字会显示为红色、Arial 字体、24pt 大小。</div>
</body>
</html>

另外我们还可以使用 . 来进行选择指定的 class 属性,比如下面就直接使用匹配 info

1
2
3
4
5
.info {
color: red;
font-family: Arial;
font-size: 24pt;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>示例</title>
<style>
.info {
color: red;
font-family: Arial;
font-size: 24pt;
}
</style>
</head>
<body>
<div class="info">这段文字会显示为红色、Arial 字体、24pt 大小。</div>
</body>
</html>

或者使用 # 直接匹配对应的 id

1
2
3
4
5
#info {
color: red;
font-family: Arial;
font-size: 24pt;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>示例</title>
<style>
#info {
color: red;
font-family: Arial;
font-size: 24pt;
}
</style>
</head>
<body>
<div id="info">这段文字会显示为红色、Arial 字体、24pt 大小。</div>
</body>
</html>

css 生效优先级

由于每个元素只能拥有一个 id,因此 id 通常用于定义唯一的、完整的样式规则。相比之下,一个元素可以拥有多个 class,每个 class 负责不同的样式维度——例如一个控制布局尺寸,另一个控制颜色字体。此外,class 可以被多个元素复用,从而减少重复代码,提高开发效率。

由于可能存在有多种格式,因此 css 具体要相关的层级:

层级 来源 说明
1 !important 最高优先级,慎用
2 行内样式(Inline) <div style="...">
3 ID 选择器 #header
4 类 / 伪类 / 属性 .btn:hover[type]
5 元素 / 伪元素 div::before
6 通用选择器 *
7 继承(Inherited) 父元素传给子元素
8 浏览器默认样式 User Agent Stylesheet

css 变量

CSS 变量 = 用 --name 定义,用 var() 调用,通常放在 :root 里做全局配色/尺寸配置,方便统一管理和维护。

1
2
3
4
5
6
7
8
9
:root {
--primary-color: #3498db;
--font-size: 16px;
}

button {
background-color: var(--primary-color); /* 调用变量 */
font-size: var(--font-size);
}

Box Model

所有HTML元素可以看作盒子,在CSS中,box model这一术语是用来设计和布局时使用。
CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。
盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。
下面的图片说明了盒子模型(Box Model):

alt text

不同部分的说明:

  • Margin(外边距) - 清除边框外的区域,外边距是透明的。
  • Border(边框) - 围绕在内边距和内容外的边框。
  • Padding(内边距) - 清除内容周围的区域,内边距是透明的。
  • Content(内容) - 盒子的内容,显示文本和图像。

最终元素的总宽度计算公式是这样的:
总元素的宽度=宽度+左填充+右填充+左边框+右边框+左边距+右边距

元素的总高度最终计算公式是这样的:
总元素的高度=高度+顶部填充+底部填充+上边框+下边框+上边距+下边距

html + css

如何将 html 和 css 文件结合起来,最佳做法是在 <head> 元素中添加一个 <link> 元素,并带上 relhref 属性。

rel 是 relationship(关系)的缩写。
另外注意,这看起来可能和之前超链接页面的示例有点像——但那个用的是 <a> 标签,而这里用的是 <link> 标签。<link> 实际上是专门用于链接外部资源(比如 CSS 文件)的场景。

规范

8pt Grid System

8pt Grid System(8 点网格系统) 是一种 UI 设计规范,要求所有元素的尺寸、间距、边距都使用 8 的倍数。

javaScript

该部分内容建立在有 golangpythonC 的基础上进行补充学习。

JavaScript(简称 js) 是一种控制网页的脚本语言,可以完成 html 和 css 之间的互动。

js 是一个大小写敏感,如果一条语句独占一行的话,那么分号是可以省略的。但如果一行中有多条语句,那么这些语句必须用分号进行分隔。

  1. 在一条语句的末尾总是加上分号被认为是最佳实践,即使是在非严格需要的时候。这个习惯可以大大减少代码出问题的可能性。

  2. 你可能也会在一些 JavaScript 文件的开头见到像 #!/usr/bin/env node 这样的第三种注释。它叫做 hashbang 注释语法,是一种用于指定执行脚本的特定 Javascript 引擎路径的特殊注释。

type

js 一共有五种主要的数据类型:

  • Boolean(true,false)
  • Number(0, -12.1, 123.12)
  • String
  • Null
  • undefined
  • BigInt
  • Symbol 其实例是唯一且不可变的数据类型。

operators

常规运算符

符号 名称 示例 结果
+ 5 + 3 8
- 5 - 3 2
* 5 * 3 15
/ 5 / 2 2.5
% 取余 5 % 2 1
** 幂运算 2 ** 3 8
++ 自增 a++ / ++a 先返回再加 / 先加再返回
-- 自减 a-- / --a 先返回再减 / 先减再返回
符号 名称 示例 等价于
= 赋值 a = 5
+= 加等 a += 3 a = a + 3
-= 减等 a -= 3 a = a - 3
*= 乘等 a *= 3 a = a * 3
/= 除等 a /= 3 a = a / 3
%= 余等 a %= 3 a = a % 3
**= 幂等 a **= 3 a = a ** 3
符号 名称 示例 说明
== 相等 5 == "5" true(值相等,类型可不同)
=== 严格相等 5 === "5" false(值和类型都相同)
!= 不等 5 != 3 true
!== 严格不等 5 !== "5" true
> 大于 5 > 3 true
< 小于 5 < 3 false
>= 大于等于 5 >= 5 true
<= 小于等于 5 <= 3 false
符号 名称 示例 说明
&& 逻辑与 a && b 两者都为真才为真
\|\| 逻辑或 a \|\| b 至少一个为真即为真
! 逻辑非 !a 取反
?? 空值合并 a ?? b anullundefined 时返回 b
符号 名称 示例
& 按位与 5 & 31
\| 按位或 5 \| 37
^ 按位异或 5 ^ 36
~ 按位非 ~5-6
<< 左移 5 << 110
>> 右移 5 >> 12
符号 名称 示例 说明
? : 三元运算符 a > b ? "大" : "小" 条件判断简写
typeof 类型检测 typeof 123"number" 返回数据类型
instanceof 实例检测 arr instanceof Array 判断是否某类的实例
... 展开运算符 [...arr] / fn(...args) 展开数组或对象
?. 可选链 obj?.name 避免访问 null 报错
??= 逻辑空赋值 a ??= b a 为空时赋值为 b
  1. 在使用 + 运算符的表达式中涉及数字和字符串,JavaScript 会把数字转换成字符串。在使用其他运算符时,JavaScript 不会把数字转换成字符串。
  2. 将字符串转换为数字的另一种方法是使用 +(一元加)运算符。
1
2
3
"1.1" + "1.1" = "1.11.1"
(+"1.1") + (+"1.1") = 2.2
// 注意:括号是为了清晰起见而添加的,不是必需的。

一元运算符

delete

delete操作符,删除一个对象的属性或者一个数组中某一个键值

1
2
3
delete objectName.property;
delete objectName[index];
delete property; // legal only within a with statement
  1. 当前 with 已经被废弃,他会导致作用域混乱,性能下降。

  2. 在严格模式(“use strict”)下,对不可删除的属性使用 delete 会抛出 TypeError,而非静默返回 false。

  1. 如果 delete 操作成功,属性或者元素会变成 undefined。如果 delete可行会返回true,如果不成功返回false。
  2. 删除数组中的元素时,数组的长度是不变的,例如删除 a[3], a[4],a[4] 和 a[3] 仍然存在变成了 undefined。delete 删除数组中的一个元素,这个元素就不在数组中了。例如,trees[3]被删除,trees[3] 仍然可寻址并返回 undefined。
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
// 正常删除
x = 42;
var y = 43;
myobj = new Number();
myobj.h = 4; // create property h
delete x; // returns true (can delete if declared implicitly)
delete y; // returns false (cannot delete if declared with var)
delete Math.PI; // returns false (cannot delete predefined properties)
delete myobj.h; // returns true (can delete user-defined properties)
delete myobj; // returns true (can delete if declared implicitly)

// 删除数组
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
delete trees[3];
// trees[3] 变成 empty slot
if (3 in trees) {
// 不会被执行
}

var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
trees[3] = undefined;
// 只是值变成 undefined
if (3 in trees) {
// 会被执行
}

typeof

1
2
typeof operand;
typeof (operand);

typeof 操作符返回一个表示 operand 类型的字符串值。operand 可为字符串、变量、关键词或对象,其类型将被返回。operand 两侧的括号为可选。

void

1
2
void expression;
void (expression);

void 运算符,表明一个运算没有返回值。expression 是 javaScript 表达式,括号中的表达式是一个可选项,当然使用该方式是一种好的形式。

你可以使用 void 运算符指明一个超文本链接。该表达式是有效的,但是并不会在当前文档中进行加载。

in

in操作符,如果所指定的属性确实存在于所指定的对象中,则会返回true,语法如下:

1
propNameOrNumber in objectName;

在这里 propNameOrNumber可以是一个代表着属性名的字符串或者是一个代表着数组索引的数值表达式,而objectName则是一个对象名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Arrays
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
0 in trees; // returns true
3 in trees; // returns true
6 in trees; // returns false
"bay" in trees; // returns false (you must specify the index number,
// not the value at that index)
"length" in trees; // returns true (length is an Array property)

// Predefined objects
"PI" in Math; // returns true
var myString = new String("coral");
"length" in myString; // returns true

// Custom objects
var mycar = { make: "Honda", model: "Accord", year: 1998 };
"make" in mycar; // returns true
"model" in mycar; // returns true

instanceof

如果所判别的对象确实是所指定的类型,则返回true。其语法如下:

1
objectName instanceof objectType;

objectName 是需要做判别的对象的名称,而objectType是假定的对象的类型,例如Date或 Array.

当你需要确认一个对象在运行时的类型时,可使用instanceof. 例如,需要 catch 异常时,你可以针对抛出异常的类型,来做不同的异常处理。

this

this关键字被用于指代当前的对象,通常,this指代的是方法中正在被调用的对象。用法如下:

1
2
this["propertyName"];
this.propertyName;

()

分组操作符()控制了表达式中计算的优先级。举例来说,你可以改变先乘除后加减的顺序,转而先计算加法。

new

你可以使用 new 运算符创建一个自定义类型或者是预置类型的对象实例。用法如下:

1
var objectName = new objectType([param1, param2, ..., paramN]);

super

super 关键字可以用来调用一个对象父类的函数,它在用来调用一个类的父类的构造函数时非常有用,比如:

1
super([arguments]); // calls the parent constructor. super.functionOnParent([arguments]);

运算优先级

优先级 运算符类型 结合性 运算符
19 分组 n/a ( … )
18 成员访问 从左到右 … . …
18 需计算的成员访问 从左到右 … [ … ]
18 new(带参数列表) n/a new … ( … )
18 函数调用 从左到右 … ( … )
18 可选链(Optional chaining) 从左到右 ?.
17 new(无参数列表) 从右到左 new …
16 后置递增 n/a … ++
16 后置递减 n/a … --
15 逻辑非 (!) 从右到左 ! …
15 按位非 (~) 从右到左 ~ …
15 一元加法 (+) 从右到左 + …
15 一元减法 (-) 从右到左 - …
15 前置递增 从右到左 ++ …
15 前置递减 从右到左 -- …
15 typeof 从右到左 typeof …
15 void 从右到左 void …
15 delete 从右到左 delete …
15 await 从右到左 await …
14 幂 (**) 从右到左 … ** …
13 乘法 (*) 从左到右 … * …
13 除法 (/) 从左到右 … / …
13 取余 (%) 从左到右 … % …
12 加法 (+) 从左到右 … + …
12 减法 (-) 从左到右 … - …
11 按位左移 (<<) 从左到右 … << …
11 按位右移 (>>) 从左到右 … >> …
11 无符号右移 (>>>) 从左到右 … >>> …
10 小于 (<) 从左到右 … < …
10 小于等于 (<=) 从左到右 … <= …
10 大于 (>) 从左到右 … > …
10 大于等于 (>=) 从左到右 … >= …
10 in 从左到右 … in …
10 instanceof 从左到右 … instanceof …
9 相等 (==) 从左到右 … == …
9 不相等 (!=) 从左到右 … != …
9 严格相等 (===) 从左到右 … === …
9 严格不相等 (!==) 从左到右 … !== …
8 按位与 (&) 从左到右 … & …
7 按位异或 (^) 从左到右 … ^ …
6 按位或 (\|) 从左到右 … \| …
5 逻辑与 (&&) 从左到右 … && …
4 逻辑或 (\|\|) 从左到右 … \|\| …
4 空值合并 (??) 从左到右 … ?? …
3 条件(三元)运算符 从右到左 … ? … : …
2 赋值 从右到左 … = …
2 加等赋值 从右到左 … += …
2 减等赋值 从右到左 … -= …
2 幂等赋值 从右到左 … **= …
2 乘等赋值 从右到左 … *= …
2 除等赋值 从右到左 … /= …
2 余等赋值 从右到左 … %= …
2 左移等赋值 从右到左 … <<= …
2 右移等赋值 从右到左 … >>= …
2 无符号右移等赋值 从右到左 … >>>= …
2 按位与等赋值 从右到左 … &= …
2 按位异或等赋值 从右到左 … ^= …
2 按位或等赋值 从右到左 … \|= …
2 逻辑与等赋值 从右到左 … &&= …
2 逻辑或等赋值 从右到左 … \|\|= …
2 空值合并等赋值 从右到左 … ??= …
1 逗号 / 序列 从左到右 … , …

declaration

  • var:声明一个变量,可选择将其初始化为一个值。可以用来声明局部,或者全局变量
  • let:声明一个块级作用域的局部变量,可选择将其初始化为一个值。用来声明块级作用域的局部变量。
  • const:声明一个块级作用域的只读命名常量。

另外,js 还有解构语法用于解包值

1
2
3
4
5
6
7
8
9
10
11
12
13
let a, b, rest;
[a, b] = [10, 20];

console.log(a);
// 期望输出:10

console.log(b);
// 期望输出:20

[a, b, ...rest] = [10, 20, 30, 40, 50];

console.log(rest);
// 期望输出:Array [30, 40, 50]

array

  • 数组
  • 布尔值数组
  • 数字数组
  • 对象数组
  • 字符串数组
  • RegExp 数组

数组字面量中的多余逗号

1
const fish = ["Lion", , "Angel"];

打印出数组的形式为:

1
2
console.log(fish);
// [ 'Lion', <1 empty item>, 'Angel' ]

注意,第二项是“empty”,与实际的 undefined 值完全不同。当使用数组遍历方法(例如,Array.prototype.map)时,空槽会被跳过。然而,索引访问 fish[1] 仍会返回 undefined。

如果你在元素列表的尾部添加了一个逗号,它将会被忽略。

在书写代码时,应该显式地将缺失的元素声明为 undefined,或者至少插入一个注释以突出元素缺失。这样做能提高代码的清晰度和维护性。

另外,还有一种形式,数组长度为 4:

1
const myList = ["home", , "school", ,];

这个可以省略最后一个逗号

1
const myList = ["home", , "school", ];

尾后逗号在多行数组中能保持 git diff 整洁,因为向末尾添加元素只需要添加一行,而不需要修改前面的行。

对象字面值

对象字面值是由一对花括号 ({}) 括起来的包含零个或者多个属性名和相关值的列表。如果合法,那么使用 . 来访问,如果名字非法,那么使用 [] 来访问。

1
2
3
4
const car = { manyCars: { a: "Saab", b: "Jeep" }, 7: "Mazda" };

console.log(car.manyCars.b); // Jeep
console.log(car[7]); // Mazda

此外,还有增强型对象字面量

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
// 1. 定义原型对象,其 toString() 返回 42
const theProtoObj = {
toString() {
return "42";
}
};

// 2. handler 变量
const handler = "some handler";

// 3. 创建对象
const obj = {
// 设置原型
__proto__: theProtoObj,

// 属性简写:handler: handler
handler,

// 方法简写
toString() {
// super.toString() 调用原型链上的 toString()
return "d " + super.toString();
},

// 计算属性名:["prop_" + 42] → prop_42
// () => 42 是一个立即执行的箭头函数,返回 42
["prop_" + (() => 42)()]: 42,
};

// 4. 调用
console.log(obj.toString()); // "d 42"
console.log(obj.prop_10); // 42
console.log(obj.handler); // "some handler"
console.log(Object.getPrototypeOf(obj) === theProtoObj); // true
console.log(()=> 42())

RegExp 字面值

一个正则表达式字面量是字符被正斜杠围成的表达式。下面是正则表达式字面量的一个示例。

1
const re = /ab+c/;

关键字

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// ========== 控制流 ==========

// break: 跳出循环或 switch
for (let i = 0; i < 10; i++) {
if (i === 3) break;
console.log(i); // 输出 0, 1, 2
}

// break label: 跳出指定标记的语句
outerLoop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) break outerLoop;
console.log(`break label: i=${i}, j=${j}`);
}
}

// case / switch: 多分支选择
const x = 2;
switch (x) {
case 1: console.log("one"); break;
case 2: console.log("two"); break; // 输出 "two"
default: console.log("other");
}

// continue: 跳过当前迭代
for (let i = 0; i < 5; i++) {
if (i === 2) continue;
console.log(i); // 输出 0, 1, 3, 4(跳过 2)
}

// continue label: 跳到指定循环的下一次迭代
let count = 0;
continueLabel: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) continue continueLabel;
count++;
}
}
console.log("continue label count:", count); // 输出 3

// default: switch 默认分支
const day = 7;
switch (day) {
case 1: console.log("Mon"); break;
case 2: console.log("Tue"); break;
default: console.log("Weekend or other"); // 输出
}

// do...while: 先执行后判断
let n = 0;
do {
console.log("do...while:", n); // 至少执行一次
n++;
} while (n < 3);

// else: if 的否定分支
const num = -5;
if (num > 0) {
console.log("positive");
} else {
console.log("non-positive"); // 输出
}

// for: 标准循环
for (let i = 0; i < 3; i++) {
console.log("for loop:", i); // 0, 1, 2
}

// for...in: 遍历对象的可枚举属性(包括继承的)
const person = { name: "Alice", age: 25 };
for (const key in person) {
console.log(`for...in: ${key} = ${person[key]}`);
}

// for...in 在数组上(不推荐,会遍历到非索引属性)
const arr = [3, 5, 7];
arr.foo = "hello";
for (const index in arr) {
console.log(`for...in arr: ${index}`); // "0", "1", "2", "foo"
}

// for...of: 遍历可迭代对象的值
for (const value of arr) {
console.log(`for...of arr: ${value}`); // 3, 5, 7(注意没有 "hello")
}

// if: 条件判断
const score = 85;
if (score >= 90) {
console.log("A");
} else if (score >= 80) {
console.log("B"); // 输出
} else {
console.log("C");
}

// while: 条件循环
let w = 0;
while (w < 3) {
console.log("while:", w);
w++;
}

// label: 为语句提供标识符(配合 break/continue 使用)
let labeledCount = 0;
outPoint: for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
if (i === 5 && j === 5) {
break outPoint; // 跳出两层循环
}
labeledCount++;
}
}
console.log("labeled break count:", labeledCount); // 55

// with: 扩展作用域链(严格模式下禁用,不推荐)
with (Math) {
console.log("with PI:", PI); // 等价于 Math.PI
console.log("with sqrt:", sqrt(16)); // 等价于 Math.sqrt(16)
}

// ========== 异常处理 ==========

// try...catch...finally
function riskyOperation() {
throw new Error("Something went wrong");
}

try {
riskyOperation();
} catch (err) {
console.error("caught:", err.message);
} finally {
console.log("cleanup always runs");
}

// throw: 抛出异常
function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
}

// ========== 函数与返回 ==========

// function: 声明函数
function greet(name) {
return "Hello " + name;
}
console.log(greet("World")); // "Hello World"

// return: 提前返回
function checkAge(age) {
if (age < 0) return "Invalid";
if (age < 18) return "Minor";
return "Adult";
}

// yield: 生成器函数,暂停并返回值
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log("yield:", gen.next().value); // 1
console.log("yield:", gen.next().value); // 2

// ========== 变量声明 ==========

// var: 函数作用域(存在变量提升)
var oldStyle = 1;
function varTest() {
var x = 1;
if (true) {
var x = 2; // 同一个变量
console.log("var inside:", x); // 2
}
console.log("var outside:", x); // 2
}
varTest();

// let: 块级作用域
let count = 0;
if (true) {
let count = 10; // 不同的变量
console.log("let inside:", count); // 10
}
console.log("let outside:", count); // 0

// const: 常量,不能重新赋值
const PI = 3.14159;
// PI = 3; // TypeError
const user = { name: "Bob" };
user.name = "Alice"; // 可以修改对象属性
console.log("const object:", user.name);

// ========== 面向对象 ==========

// class / constructor / extends / super / new
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return this.name + " makes noise";
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
speak() {
return super.speak() + ", and barks"; // 调用父类方法
}
}

const dog = new Dog("Rex", "Husky");
console.log("class:", dog.speak());

// this: 当前执行上下文
const obj = {
value: 42,
getValue() {
return this.value;
}
};
console.log("this:", obj.getValue()); // 42

// instanceof: 检测原型链
console.log("instanceof:", dog instanceof Dog); // true
console.log("instanceof:", dog instanceof Animal); // true
console.log("instanceof:", dog instanceof Object); // true

// in: 检测属性是否存在(包括继承的属性)
console.log("in:", "name" in dog); // true
console.log("in:", "speak" in dog); // true
console.log("in:", "toString" in dog); // true(继承自 Object.prototype)

// ========== 模块(需在模块环境中运行) ==========

// export: 导出
// export const foo = 1;
// export function bar() { return 2; }
// export default class MyClass {}

// import: 导入
// import { foo, bar } from "./module.js";
// import * as utils from "./utils.js";
// import MyClass from "./module.js";

// ========== 运算符相关关键字 ==========

// typeof: 返回类型字符串
console.log("typeof number:", typeof 123); // "number"
console.log("typeof string:", typeof "hello"); // "string"
console.log("typeof boolean:", typeof true); // "boolean"
console.log("typeof undefined:", typeof undefined); // "undefined"
console.log("typeof object:", typeof {}); // "object"
console.log("typeof function:", typeof function(){}); // "function"
console.log("typeof symbol:", typeof Symbol()); // "symbol"
console.log("typeof bigint:", typeof 123n); // "bigint"

// void: 表达式求值后返回 undefined
let voidResult = void (2 + 3); // 5 被计算,但返回 undefined
console.log("void:", voidResult); // undefined

// 常见用法:立即执行函数表达式(避免返回值被利用)
void function() {
console.log("void IIFE runs");
}();

// delete: 删除对象属性
const car = { brand: "Toyota", year: 2020 };
console.log("before delete:", "year" in car); // true
delete car.year;
console.log("after delete:", "year" in car); // false
console.log("car:", car); // { brand: "Toyota" }

// 注意:delete 不能删除变量或内置对象属性
var globalVar = 1;
// delete globalVar; // 严格模式下报错,非严格模式无效

// ========== 调试 ==========

// debugger: 断点(浏览器/Node 调试器会在此暂停)
function debugMe() {
let x = 1;
// debugger; // 取消注释可在调试时暂停
return x;
}

// ========== 字面量(非关键字但保留) ==========

// null: 表示"无值"的对象占位符
const empty = null;
console.log("null:", empty, typeof empty); // null "object"

// true / false: 布尔值
const flag = true;
console.log("boolean:", flag, !flag); // true false

// ========== 严格模式保留字(不能用作标识符) ==========

// 以下在严格模式下使用会报 SyntaxError:
// implements, interface, let, package, private, protected, public, static

// 示例验证:
// "use strict";
// const private = 1; // SyntaxError: Unexpected strict mode reserved word

假值

下面这些值求值为 false(也叫做假值):

  • false
  • undefined
  • null
  • 0
  • NaN
  • 空字符串("")

对面字面量恒为真

布尔值和 Boolean 对象有一定区别,下面代码中 b 为 Boolean 对象,对象值恒为真

1
2
3
4
5
6
7
const b = new Boolean(false);
if (b) {
// 这个条件求值为 true
}
if (b == true) {
// 这个条件求值为 false
}

for in 和 for of 实现逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
const arr = [3, 5, 7];
arr.foo = "hello";

for (const i in arr) {
console.log(i); // 输出 "0", "1", "2", "foo"
}

for (const i of arr) {
console.log(i); // 输出 "3", "5", "7"
}

console.log(arr)
// 注意 for...of 的输出没有出现 "hello"

为什么他们的输出逻辑不一样,这个是 JavaScript 的底层逻辑决定的:

1
2
3
4
5
6
7
8
9
10
11
12
arr = {
// 数组元素区(被 length 管理),如果进行 arr["4"] 那么空余部分就会直接填充 empty item,访问的时候会直接打印出 undefined
[0]: "a"
[1]: "b"
[2]: "c"

// 普通属性区(length 不管理)
foo: "hello"

// 特殊属性
length: 3
}

for…of 不直接遍历对象的属性,而是调用数组的 [Symbol.iterator] 迭代器。这个迭代器的内部逻辑是:

empty slot 不会被跳过,而是返回 undefined(因为迭代器会访问 arr[3],发现没有值,就返回 undefined)

1
2
3
4
5
6
7
8
9
10
11
12
// 伪代码:数组迭代器的内部实现
function arrayIterator() {
let index = 0;
return {
next() {
if (index < this.length) { // 只看 length,不看其他属性
return { value: this[index++], done: false };
}
return { done: true };
}
};
}

for…in 遍历的是对象所有可枚举(enumerable)的属性名,包括继承自原型链上的可枚举属性。其内部逻辑大致为:

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
// 伪代码:for...in 的内部实现
function forIn(obj) {
const keys = [];
// 1. 收集自身可枚举属性
for (const key of Object.getOwnPropertyNames(obj)) {
if (Object.getOwnPropertyDescriptor(obj, key).enumerable) {
keys.push(key);
}
}
// 2. 收集原型链上的可枚举属性(直到 Object.prototype)
let proto = Object.getPrototypeOf(obj);
while (proto) {
for (const key of Object.getOwnPropertyNames(proto)) {
if (Object.getOwnPropertyDescriptor(proto, key).enumerable) {
keys.push(key);
}
}
proto = Object.getPrototypeOf(proto);
}
// 3. 按规范排序输出:数字索引按数值升序,字符串属性按插入顺序
return keys.sort((a, b) => {
const aNum = parseInt(a, 10);
const bNum = parseInt(b, 10);
if (!isNaN(aNum) && !isNaN(bNum)) return aNum - bNum;
return 0; // 字符串属性保持插入顺序
});
}

此外,底层的存储方式是:如果是一个连续的数组,那么就会直接使用一串连续的内存来保存对应的数组值。如果是加入了大量其他的属性、或者数组变得极度稀疏(如 arr[10000] = 1),那么就会退化成 hash 表。但是退化之后,数字索引部分还是会按照数值进行排序存储,字符串属性则按插入顺序排列,以保证 for…in 和 for…of 的输出顺序符合 ECMAScript 规范。

函数

示例

1
2
3
function square(number) {
return number * number;
}

参数本质上是按值传递给函数的——因此,即使函数体的代码为传递给函数的参数赋了新值,这个改变也不会反映到全局或调用该函数的代码中。

但是数组和对象传入到函数中被修改了,因为这部分是引用类型,只有基础类型(string,string,bigint,bigint,undefined,symbol,null)传入才不会反映到全局。

匿名函数

1
2
3
4
5
const square = function (number) {
return number * number;
};

console.log(square(4)); // 16
1
2
3
4
5
const factorial = function fac(n) {
return n < 2 ? 1 : n * fac(n - 1);
};

console.log(factorial(3)); // 6

函数提升

函数定义可以放在任意位置,因为 js 解释器会自动将函数定义放在当前作用域顶部。但是,函数提升仅适用于函数声明,而不适用于函数表达式。以下代码无法运行:

1
2
3
4
console.log(square(5)); // ReferenceError: Cannot access 'square' before initialization
const square = function (n) {
return n * n;
};

嵌套函数 / 闭包

闭包是可以拥有独立变量以及绑定了这些变量的环境(“封闭”了表达式)的表达式(通常是函数)。

闭包形成的三个条件:

  • 存在嵌套函数(函数内部定义了另一个函数)
  • 内部函数引用了外部函数的变量
  • 内部函数被返回或传递到外部作用域执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function outer() {
let count = 0; // 外部函数的局部变量

function inner() { // 内部函数
count++; // 内部函数引用了外部变量
return count;
}

return inner; // 将内部函数返回出去
}

const fn = outer(); // outer 执行完毕,但 count 没有被销毁!

console.log(fn()); // 1
console.log(fn()); // 2
console.log(fn()); // 3

outer() 执行完后,按理说 count 应该被垃圾回收。但因为 inner 函数引用了 count,引擎会保留这个变量,形成闭包。

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createCounter() {
let count = 0; // 外部无法直接访问

return {
increment: () => ++count,
decrement: () => --count,
getValue: () => count
};
}

const counter = createCounter();
console.log(counter.getValue()); // 0
counter.increment();
console.log(counter.getValue()); // 1
// count 无法被外部直接修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createCounter() {
let count = 0; // 外部无法直接访问

return {
increment: () => ++count,
decrement: () => --count,
getValue: () => count
};
}

const counter = createCounter();
console.log(counter.getValue()); // 0
counter.increment();
console.log(counter.getValue()); // 1
// count 无法被外部直接修改

arguments 对象

示例

1
2
3
4
5
6
7
8
function myConcat(separator) {
let result = ""; // 初始化列表
// 迭代 arguments
for (let i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}

默认参数

1
2
3
4
5
function multiply(a, b = 1) {
return a * b;
}

console.log(multiply(5)); // 5

剩余参数

1
2
3
4
5
6
function multiply(multiplier, ...theArgs) {
return theArgs.map((x) => multiplier * x);
}

const arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]

箭头函数

箭头函数总是匿名函数,没有单独的 this

1
2
3
4
5
6
7
8
9
10
11
const a = ["Hydrogen", "Helium", "Lithium", "Beryllium"];

const a2 = a.map(function (s) {
return s.length;
});

console.log(a2); // [8, 6, 7, 9]

const a3 = a.map((s) => s.length);

console.log(a3); // [8, 6, 7, 9]