- flat single-file structure: NN-topic.js per topic (concepts as comments + demo + exercises in one file), replacing the old NN-topic/ folder with separate notes.md + practice.js - 00-concepts/ cross-topic notes (mistakes log, call-forms, value-vs-reference, shortcircuit bash-vs-js) - move quokka_playground.js from repo root into js_notes/ - add project CLAUDE.mdpull/1034/head
parent
e067490424
commit
7b9ddf113c
@ -0,0 +1,69 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## What this repository actually is
|
||||
|
||||
Two layers coexist here:
|
||||
|
||||
1. **The upstream tutorial** — `NN_Day_*` folders (e.g. `01_Day_Introduction`,
|
||||
`23_Day_Event_listeners`) plus language translation folders (`Korea/`, `RU/`,
|
||||
`Spanish/`, …). This is Asabeneh's "30 Days Of JavaScript" course, cloned as
|
||||
read-only reference material. **Do not modify or "fix" these files** — errors in
|
||||
them (e.g. `main.js` relying on variables from a separately-loaded `variable.js`)
|
||||
are intentional teaching artifacts, not bugs.
|
||||
|
||||
2. **`js_notes/`** — the user's own learning journal and the **active workspace**.
|
||||
Nearly all real work happens here. It is organized **by knowledge topic, not by
|
||||
day** (`01-basics`, `02-variables`, … `07-objects`, …), because the numbering
|
||||
reflects the user's learning order, not the tutorial's day numbers.
|
||||
|
||||
The user is a **beginner learning JavaScript** (with some Python/Linux background).
|
||||
Sessions are interactive tutoring: teach a topic, have them practice, review their code.
|
||||
|
||||
## `js_notes/` structure and conventions
|
||||
|
||||
- Each topic is a folder with `notes.md` (concepts, human-read) and `practice.js`
|
||||
(runnable code the user edits). `js_notes/README.md` is the index + progress map.
|
||||
- `js_notes/00-concepts/` holds cross-topic concepts (value-vs-reference, call
|
||||
forms, bash-vs-js short-circuit) and **`my-mistakes.md`** — a running log of
|
||||
mistakes the user has actually made, each with a `📍来源` back-reference to the
|
||||
original exercise.
|
||||
|
||||
**`js_notes/README.md` contains binding tutoring rules — read it before teaching.**
|
||||
The critical ones:
|
||||
|
||||
- **Never write answers for the user.** In practice files, the "轮到你" (your turn)
|
||||
region gets only requirements — no solutions, not even commented-out ones (that
|
||||
spoils it). Hints point at direction only ("these two methods can chain"), never a
|
||||
full line like `raw.trim().toLowerCase()`.
|
||||
- **Let the user fix their own code.** Point out what's wrong and why; don't edit
|
||||
their exercise answers for them.
|
||||
- Demonstration regions (teaching examples) may include expected output as comments;
|
||||
exercise regions must not.
|
||||
- **When the user makes a new mistake, absorb it into `00-concepts/my-mistakes.md`**
|
||||
(错法→现象→正解→教训 format, with a `📍来源` index) so it's recoverable on review.
|
||||
- Update the progress checkboxes in `js_notes/README.md` when a topic is finished.
|
||||
|
||||
## Running code
|
||||
|
||||
There is **no build system, package.json, linter, or test suite** — this is a
|
||||
learning repo, not an application. Code is run one file at a time:
|
||||
|
||||
```bash
|
||||
node js_notes/06-arrays/practice.js # run a single practice file
|
||||
```
|
||||
|
||||
Inside the editor the user also runs files via **Quokka.js** (live `console.log`
|
||||
values inline; free edition — no `//?` live-comments) or **Code Runner** (▷ button).
|
||||
|
||||
**Self-contained vs browser files:** a `.js` file that defines everything it uses and
|
||||
touches no `document` can run under Node/Quokka/Code Runner. Files that depend on
|
||||
variables from another script, or use `document`/DOM (day 21+ and the mini-projects),
|
||||
must be opened via their `index.html` in a browser with the F12 console — running them
|
||||
under Node throws (e.g. `firstName is not defined`, `document is not defined`).
|
||||
|
||||
## Content language
|
||||
|
||||
Prose, notes, and explanations are in **Chinese**; code, identifiers, and commit
|
||||
messages in English. Commit messages follow Angular style (`<type>(scope): description`).
|
||||
@ -0,0 +1,67 @@
|
||||
# 通用概念 · 三种调用形式:`值.方法()` vs `函数()` vs `类.方法()`
|
||||
|
||||
> 跨主题概念,不属于某一天。困惑"什么时候用点、什么时候传参"时看这篇。
|
||||
|
||||
## 核心问题:这个函数"属于"谁?
|
||||
|
||||
### 形式一:`值.方法()` —— 方法(method)
|
||||
函数**属于某个值/对象**,是它"自带的能力",用点 `.` 调用。
|
||||
**点前面那个值 = 被操作的主角**,不用再传进去。
|
||||
|
||||
```js
|
||||
'hello'.toUpperCase() // toUpperCase 是字符串自带的能力
|
||||
;[1, 2, 3].map((x) => x * 2) // map 是数组自带的能力
|
||||
'a,b'.split(',') // split 属于字符串
|
||||
```
|
||||
|
||||
### 形式二:`函数(值)` —— 独立函数(function)
|
||||
函数**不属于任何特定值**,是自由的工具,数据当参数**传进括号**。
|
||||
|
||||
```js
|
||||
Number('42') // 转谁就传谁进去
|
||||
parseInt('42px')
|
||||
isNaN(x)
|
||||
```
|
||||
|
||||
### 形式三:`类.方法(值)` —— 静态方法(static method)
|
||||
函数挂在某个**类/工具箱**下做归类,数据仍然**传进去**。
|
||||
|
||||
```js
|
||||
Array.isArray([1, 2, 3]) // isArray 挂在 Array 名下
|
||||
Math.max(1, 2, 3) // max 挂在 Math 名下
|
||||
Number.isNaN(x) // 挂在 Number 名下
|
||||
Object.keys(obj) // 挂在 Object 名下
|
||||
```
|
||||
|
||||
> ⚠️ 区分:`Array.isArray(arr)` 的 `Array`(大写=类/工具箱)和 `[1,2,3].map()` 点前的
|
||||
> 数组实例(具体的值)是两回事。**大写开头 = 工具箱;点前是具体值 = 实例。**
|
||||
|
||||
## 对照表
|
||||
|
||||
| 形式 | 例子 | 数据在哪 | 感觉 |
|
||||
|---|---|---|---|
|
||||
| `值.方法()` | `'hi'.toUpperCase()` | **点前面** | 值自带的能力 |
|
||||
| `函数(值)` | `Number('42')` | **括号里** | 自由工具 |
|
||||
| `类.方法(值)` | `Array.isArray(x)` | **括号里** | 工具箱里的工具 |
|
||||
|
||||
## "什么时候用哪个"?
|
||||
|
||||
**真相:不是你选的,是语言设计时定死的。** 每个功能造出来就固定了形式,只能记/查。
|
||||
- 转大写只能 `str.toUpperCase()`,不能 `toUpperCase(str)`
|
||||
- 判断数组只能 `Array.isArray(x)`,不能 `x.isArray()`
|
||||
|
||||
但有规律可猜:
|
||||
1. **操作字符串/数组/对象内容的** → 大多 `值.方法()`
|
||||
2. **类型转换/数学/解析类工具** → 大多 `函数()` 或 `类.方法()`(`Number`、`Math.`、`JSON.parse`)
|
||||
3. **`Math.` / `JSON.` / `Object.` / `Array.` 开头** → 几乎都是静态方法,数据传进去
|
||||
|
||||
## 记忆诀窍
|
||||
|
||||
> 问:**"这是某个值天生就会的,还是外部工具对它做的?"**
|
||||
> - 天生就会(大写化、截取、拆分)→ `值.方法()`
|
||||
> - 外部工具处理它(转类型、算最值、解析)→ `函数(值)` / `工具箱.方法(值)`
|
||||
|
||||
## 🔧 实用技巧
|
||||
|
||||
**不确定时,在编辑器里敲 `值.` 看自动补全弹什么** —— 弹出来的就是它自带的方法;
|
||||
没弹出来的,就得找独立函数或静态方法。让 VS Code 帮你查最快。
|
||||
@ -0,0 +1,146 @@
|
||||
# 🕳️ 我的踩坑记录
|
||||
|
||||
> 这里记录我**自己真实犯过**的错(不是泛泛知识点)。复习时先扫这里 —— 我最容易在这些地方翻车。
|
||||
> 每条格式:**错法 → 现象 → 正解 → 教训**,并标注**📍来源**(点开可回到原题)。
|
||||
|
||||
---
|
||||
|
||||
## A. 方法忘加括号
|
||||
|
||||
📍来源:`../04-strings/practice.js` 练习 A(`../04-strings/notes.md` §1)
|
||||
```js
|
||||
city.toUpperCase // ❌ 打印出 [Function: toUpperCase],不是结果
|
||||
city.toUpperCase() // ✅ 加 () 才执行
|
||||
```
|
||||
- **现象**:打印出一堆 `[Function: xxx]` 而不是想要的值
|
||||
- **教训**:**方法是"动作",必须加 `()`**;`.length` 是属性不加括号。分不清就问:这是"数据"还是"动作"?
|
||||
|
||||
## B. 模板字符串用错符号
|
||||
|
||||
📍来源:`../02-variables/practice.js` 第 6 组(`../01-basics/notes.md` §4)
|
||||
```js
|
||||
`我叫 &{myName}` // ❌ 用了 &,原样打印 "我叫 &{myName}"
|
||||
`我叫 ${myName}` // ✅ 是 $ 不是 &
|
||||
```
|
||||
- **教训**:模板插值是 **`$` + `{}`**,不是 `&`。且只在反引号 `` ` `` 里生效。
|
||||
|
||||
## C. 方法名拼写错
|
||||
|
||||
📍来源:`../04-strings/practice.js` 练习 B
|
||||
```js
|
||||
email.spilt('@') // ❌ 报错:email.spilt is not a function
|
||||
email.split('@') // ✅
|
||||
```
|
||||
- **现象**:`X is not a function`
|
||||
- **教训**:看到 `is not a function`,先检查**拼写**。
|
||||
|
||||
## D. "能算 ≠ 能看见"(反复犯)
|
||||
|
||||
📍来源:`../04-strings/practice.js` 练习 E、`../06-arrays/practice.js` 练习 E(`../01-basics/notes.md` §5)
|
||||
```js
|
||||
array.join('-') // ❌ 算出来了但没打印,屏幕啥都没有
|
||||
console.log(array.join('-')) // ✅
|
||||
```
|
||||
- **教训**:文件里**必须 `console.log`** 才看得到结果。裸表达式会算但不显示。
|
||||
|
||||
## E. 方法返回值没接住(反复犯)⭐
|
||||
|
||||
📍来源:`../04-strings/practice.js` 练习 D/E、`../06-arrays/practice.js` 练习 E
|
||||
```js
|
||||
sentence.split(' ') // ❌ 拆了但没接住,等于白拆
|
||||
const words = sentence.split(' ') // ✅ 用变量接住
|
||||
```
|
||||
- **教训**:`slice / split / join / map / toUpperCase...` 都**返回新值、不改原**,必须用变量接住或直接打印。
|
||||
|
||||
## F. 改原 vs 返回新,分不清 ⭐⭐
|
||||
|
||||
📍来源:`../06-arrays/practice.js` 练习 D/E(`../06-arrays/notes.md` §4-5)
|
||||
|
||||
- **改原数组**:`push / pop / shift / unshift / splice`
|
||||
- **返回新值(不改原)**:`slice / split / join / map / filter`
|
||||
- `splice` 尤其坑:**返回的是"被删元素",不是修改后的数组** → 插入后要打印**原数组**,别打印返回值
|
||||
```js
|
||||
q.splice(1, 0, 'x') // 改的是 q 本身
|
||||
console.log(q) // ✅ 打印原数组
|
||||
console.log(q.splice(...)) // ❌ 打印的是被删元素([])
|
||||
```
|
||||
|
||||
## G. slice 用 length-1 丢了最后一个字符
|
||||
|
||||
📍来源:`../04-strings/practice.js` 练习 D(`../04-strings/notes.md` §4)
|
||||
```js
|
||||
name.slice(1, name.length - 1) // ❌ -1 少取一位
|
||||
name.slice(1) // ✅ 省略 end 取到结尾
|
||||
```
|
||||
- **教训**:`slice` **不含 end**;要取到结尾直接省略第二个参数。
|
||||
|
||||
## H. 变量没声明就赋值
|
||||
|
||||
📍来源:`../06-arrays/practice.js` 练习 E(`../02-variables/notes.md` §2-3)
|
||||
```js
|
||||
array = sentence.split(' ') // ❌ 裸奔,泄漏成全局变量(严格模式报错)
|
||||
const array = sentence.split(' ') // ✅ 必须 const/let 声明
|
||||
```
|
||||
- **教训**:变量**一定先声明**。
|
||||
|
||||
## I. 引号不配对
|
||||
|
||||
📍来源:`../07-objects/practice.js` 练习 A(`../01-basics/notes.md` §4)
|
||||
```js
|
||||
title: "JavaScript' // ❌ 双开单关,整个文件从这里崩
|
||||
title: "JavaScript" // ✅ 开关同一种
|
||||
```
|
||||
- **现象**:从这行起后面全报错
|
||||
- **教训**:引号**开和关必须同种**。
|
||||
|
||||
## J. 变量名定义和使用对不上
|
||||
|
||||
📍来源:`../07-objects/practice.js` 练习 C
|
||||
```js
|
||||
const cat = {...}
|
||||
Object.values(car) // ❌ car is not defined(定义 cat 用 car)
|
||||
```
|
||||
- **现象**:`X is not defined`
|
||||
- **教训**:看到 `is not defined`,检查**名字是否一致**(typo)。
|
||||
|
||||
## K. 下标从 0 开始,越界读 undefined ⭐
|
||||
|
||||
📍来源:`../07-objects/practice.js` 练习 E(`../06-arrays/notes.md` §1)
|
||||
```js
|
||||
// 3 个元素:people[0] people[1] people[2]
|
||||
people[3].name // ❌ 越界:Cannot read properties of undefined
|
||||
```
|
||||
- **教训**:下标**从 0 开始**,"第 N 个" = `[N-1]`;最后一个 = `[length-1]`。
|
||||
|
||||
## L. 对象方法用了箭头函数,this 失效 ⭐⭐
|
||||
|
||||
📍来源:`../07-objects/practice.js` 练习 D(`../07-objects/notes.md` §7)
|
||||
```js
|
||||
describe: () => this.name // ❌ 箭头函数没有自己的 this → undefined
|
||||
describe: function () { return this.name } // ✅ 用 function
|
||||
describe: function () => {...} // ❌ 语法错:function 和 => 不能同时写
|
||||
```
|
||||
- **教训**:**对象方法用 `function`,不用箭头 `=>`**(箭头没有自己的 this)。两种写法只能选一种。
|
||||
|
||||
## M. const 同名声明两次
|
||||
|
||||
📍来源:`../07-objects/practice.js` 练习 D
|
||||
```js
|
||||
const student = {...}
|
||||
const student = {...} // ❌ Identifier 'student' has already been declared
|
||||
```
|
||||
- **教训**:同作用域 `const` 不能重名;想留"错误对照"就**注释掉**旧的,别当活代码。
|
||||
|
||||
---
|
||||
|
||||
## 🔁 高频报错 → 秒查表
|
||||
|
||||
| 报错信息 | 最可能的原因 | 看哪条 |
|
||||
|---|---|---|
|
||||
| `X is not a function` | 方法名拼错 | C |
|
||||
| `X is not defined` | 变量名 typo / 没声明 | H、J |
|
||||
| `Cannot read properties of undefined` | 下标越界 / 键不存在 | K |
|
||||
| `has already been declared` | const 重名 | M |
|
||||
| 打印出 `[Function: xxx]` | 方法忘加 `()` | A |
|
||||
| 屏幕啥都没有 | 忘了 console.log / 没接住返回值 | D、E |
|
||||
| 从某行起全报错 | 引号没配对 | I |
|
||||
@ -0,0 +1,63 @@
|
||||
# 通用概念 · `&&` / `||`:Linux(bash) vs JavaScript
|
||||
|
||||
> 跨环境对照。shell 和 JS 来回切时看这篇。相关:`../05-conditionals/notes.md` 第 5 节。
|
||||
|
||||
## 相同点:都是"短路求值"
|
||||
|
||||
"左边决定要不要看右边"—— 这个思路两边完全一致。
|
||||
|
||||
```bash
|
||||
# bash
|
||||
命令A && 命令B # A 成功才执行 B
|
||||
命令A || 命令B # A 失败才执行 B
|
||||
```
|
||||
```js
|
||||
// JS
|
||||
a && b // a 为真才看 b
|
||||
a || b // a 为假才看 b
|
||||
```
|
||||
|
||||
## 不同点:操作数是"命令" vs "值"
|
||||
|
||||
| | Linux (bash) | JavaScript |
|
||||
|---|---|---|
|
||||
| 左右两边是 | **命令**(程序) | **值 / 表达式** |
|
||||
| "真/假"看什么 | **退出码**:`0`=成功=真,非0=失败=假 | **truthy/falsy**:值本身 |
|
||||
| 返回什么 | 最后执行命令的退出码 | **操作数原值** |
|
||||
|
||||
## ⚠️ 最大的坑:`0` 的真假正好相反
|
||||
|
||||
```bash
|
||||
# Linux:退出码 0 = 成功 = 真
|
||||
true && echo yes # 打印 yes(true 的退出码是 0)
|
||||
```
|
||||
```js
|
||||
// JS:数字 0 = falsy = 假
|
||||
0 && console.log('yes') // 不打印(0 是假)
|
||||
```
|
||||
|
||||
- Linux 的 `0`:退出码 0 = "没出错" = **真**
|
||||
- JS 的 `0`:数值零 = "空/无" = **假**
|
||||
|
||||
**同一个 `0`,两个世界真假颠倒。** 这是切换时最容易栽的地方。
|
||||
|
||||
## 相同的惯用法(思路可直接迁移)
|
||||
|
||||
```bash
|
||||
# bash:|| 兜底
|
||||
result=$(cat file) || result="default"
|
||||
# bash:&& 前置守卫
|
||||
[ -f config ] && load_config
|
||||
```
|
||||
```js
|
||||
// JS:|| 设默认值
|
||||
const result = readFile() || 'default'
|
||||
// JS:&& 守卫
|
||||
config && loadConfig()
|
||||
```
|
||||
|
||||
## ✅ 一句话
|
||||
|
||||
> 短路逻辑 Linux 和 JS **一致**,惯用法也像(`||`兜底、`&&`守卫)。
|
||||
> 区别:**bash 操作"命令"看退出码,JS 操作"值"看 truthy/falsy**;
|
||||
> 最大的坑:**`0` 在 bash 是真(成功),在 JS 是假**。
|
||||
@ -0,0 +1,82 @@
|
||||
# 通用概念 · 值 vs 引用(value vs reference)⭐⭐
|
||||
|
||||
> 跨主题核心概念。函数传参、对象、数组章都会反复用到。JS 最重要的底层认知之一。
|
||||
|
||||
## 一、两大阵营
|
||||
|
||||
| | 原始值(primitive) | 引用类型(reference) |
|
||||
|---|---|---|
|
||||
| 有哪些 | `number` `string` `boolean` `undefined` `null` `bigint` `symbol` | `object` `array` `function` |
|
||||
| 变量里存的 | **值本身** | 指向对象的**地址(引用)** |
|
||||
| 赋值给别人时 | **复制一份副本** | **共享同一个对象** |
|
||||
| 能原地改内部吗 | ❌ 不可变 | ✅ 可以 |
|
||||
|
||||
**一句话:原始值"复制",引用类型"共享"。**
|
||||
|
||||
## 二、决定性证据:改一个,另一个变不变?
|
||||
|
||||
### 原始值 → 复制副本,各改各的
|
||||
```js
|
||||
let a = 'hello'
|
||||
let b = a // b 拿到的是【副本】
|
||||
a = 'world' // 改 a
|
||||
console.log(b) // 'hello' ← b 没变!
|
||||
```
|
||||
|
||||
### 引用类型 → 共享同一个,牵一发动全身
|
||||
```js
|
||||
let x = [1, 2, 3]
|
||||
let y = x // x 和 y 指向【同一个】数组
|
||||
x[0] = 99 // 改 x
|
||||
console.log(y) // [99, 2, 3] ← y 跟着变了!
|
||||
```
|
||||
|
||||
> 这就是分水岭:值类型复制、互不影响;引用类型共享、一改都变。
|
||||
|
||||
## 三、字符串不可变:`word[0] = 'Y'` 会怎样
|
||||
|
||||
字符串是**原始值**,不是对象,没有"内部格子供你寻址修改"。
|
||||
|
||||
```js
|
||||
let word = 'hello'
|
||||
word[0] = 'Y' // 静默无效(严格模式下报错)
|
||||
console.log(word) // 'hello' ← 没变!
|
||||
```
|
||||
|
||||
- **能读不能写**:`word[0]` 读字符 OK,但赋值无效
|
||||
- `word[0]` 读出来的是**复制的值** `'h'`,和 word 内部已无关系 —— 不通向本体
|
||||
- 严格模式(`'use strict'`)下会报 `TypeError: Cannot assign to read only property`
|
||||
|
||||
**想"改"字符串 → 造个新的再赋值回去:**
|
||||
```js
|
||||
let word = 'hello'
|
||||
word = 'Y' + word.slice(1) // 'Y' + 'ello'
|
||||
console.log(word) // 'Yello'
|
||||
```
|
||||
(这正是 04-strings D 题"首字母大写"的原理:新字符 + 剩余,再赋值)
|
||||
|
||||
## 四、对比:数组可以原地改
|
||||
|
||||
长得像(都用 `[0]`),行为相反:
|
||||
|
||||
```js
|
||||
let arr = [1, 2, 3]
|
||||
arr[0] = 99 // ✅ 数组是引用类型 → 通向对象内部 → 改得动
|
||||
console.log(arr) // [99, 2, 3]
|
||||
|
||||
let str = 'abc'
|
||||
str[0] = 'X' // ❌ 字符串不可变 → 无效
|
||||
console.log(str) // 'abc'
|
||||
```
|
||||
|
||||
## 五、为什么这很重要(伏笔)
|
||||
|
||||
- **函数传参**:传原始值 = 传副本(函数内改不动外面);传对象 = 传引用(函数内能改到外面的对象)
|
||||
- **复制对象/数组**:`let b = a` 只是共享,不是真复制;要真复制得用别的手段(后面学)
|
||||
- 很多"我明明没改它怎么变了"的 bug,根源都是引用共享
|
||||
|
||||
---
|
||||
|
||||
## ✅ 一句话记死
|
||||
|
||||
> **原始值(含字符串)= 复制的值,不可变,`[0]` 只能读;引用类型(对象/数组)= 共享的地址,可原地改,`[0]` 通向本体。**
|
||||
@ -0,0 +1,82 @@
|
||||
// 01 · 基础
|
||||
// 学练复习一体:概念总结 → 示范 → 轮到你
|
||||
// 跑法:Quokka(Cmd+Shift+P → Quokka: Start on Current File)/ 右上角 ▷ / node 本文件
|
||||
|
||||
// ═════ 概念总结 ═════
|
||||
//
|
||||
// 1. console.log() —— 打印输出
|
||||
// 调试、看结果全靠它:console.log('Hello, World!')
|
||||
// console —— JS 内置的"控制台"对象
|
||||
// .log(...) —— 它的方法,作用是打印
|
||||
// 'Hello' —— 传给它的内容
|
||||
// 可一次打印多个,用逗号隔开:console.log('a =', 1)
|
||||
//
|
||||
// 2. 注释 //
|
||||
// // 后面的内容是给人看的说明,不会被执行。删掉 // 才会"激活"那行代码。
|
||||
//
|
||||
// 3. 表达式:先算再用 ⭐
|
||||
// 引号【外】的算式会先被算成结果,再拿去用(几乎所有语言通用):
|
||||
// console.log('1 + 1 =', 1 + 1) // 1 + 1 = 2 ← 这个 1+1 会先变成 2
|
||||
// - 引号【里】 '1 + 1' → 文字,原样打印
|
||||
// - 引号【外】 1 + 1 → 运算,先算成 2
|
||||
//
|
||||
// 4. 字符串引号:单 / 双 / 反 ⭐
|
||||
// '单引号' → string
|
||||
// "双引号" → string,和单引号【完全等价】
|
||||
// `反引号` → 模板字符串,有特殊能力
|
||||
// - 单引号 = 双引号,功能没区别;社区默认用单引号(Prettier 也统一成单引号)
|
||||
// - 文字里含引号时,用另一种包起来防"打架":
|
||||
// "It's a nice day" ✅ 文字有 ' → 用 " 包
|
||||
// 'It's a nice day' ❌ 报错:中间的 ' 提前结束了字符串
|
||||
// - 反引号最有用:能用 ${变量} 直接把变量嵌进字符串,还能换行:
|
||||
// const name = '小明'; console.log(`我叫${name}`) // 我叫小明
|
||||
// 注意:${} 只在反引号里生效,单双引号里写它只是普通文字。
|
||||
//
|
||||
// 5. 怎么运行一个 .js 文件 ⭐
|
||||
// ┌────────────┬──────────────────────────────────────┬──────────────────────────────┐
|
||||
// │ 文件类型 │ 特征 │ 怎么跑 │
|
||||
// ├────────────┼──────────────────────────────────────┼──────────────────────────────┤
|
||||
// │ 自包含 │ 自己定义自己用,无 document │ ▷ / Quokka / node 文件.js ✅ │
|
||||
// │ 浏览器示例 │ 靠 index.html 串多脚本共享变量, │ 浏览器 + F12 Console ✅ │
|
||||
// │ │ 或含 document │ │
|
||||
// └────────────┴──────────────────────────────────────┴──────────────────────────────┘
|
||||
// 教程第 1 天的 main.js 报 "firstName is not defined",就是因为它依赖 variable.js,
|
||||
// 属于"浏览器示例",不能用 ▷/node 单独跑。这不是 bug,是运行方式用错了。
|
||||
//
|
||||
// ✅ 本主题要点回顾
|
||||
// 1. console.log() 打印
|
||||
// 2. // 注释,不执行
|
||||
// 3. 表达式先算再用(1+1→2)
|
||||
// 4. 单引号=双引号(推荐单引号);反引号能嵌 ${变量}
|
||||
// 5. 自包含文件才能用 ▷/Quokka 跑
|
||||
|
||||
// ═════ 示范 ═════
|
||||
|
||||
// ── 1. console.log 打印 ──────────────────────────
|
||||
console.log('Hello, World!') // Hello, World!
|
||||
|
||||
// ── 2. 一次打印多个,用逗号隔开 ────────────────────
|
||||
console.log('1 + 1 =', 1 + 1) // 1 + 1 = 2
|
||||
|
||||
// ── 3. 表达式先算再用:引号内 vs 引号外 ⭐ ──────────
|
||||
console.log('10 * 5') // 10 * 5 ← 字符串,原样(不算)
|
||||
console.log(10 * 5) // 50 ← 运算,算了
|
||||
console.log('10 * 5 =', 10 * 5) // 10 * 5 = 50
|
||||
|
||||
// ── 4. 单引号 = 双引号 ────────────────────────────
|
||||
console.log('我是单引号') // 我是单引号
|
||||
console.log("我是双引号") // 我是双引号
|
||||
|
||||
// 文字里含撇号 → 用双引号包,避免"打架"
|
||||
console.log("It's a nice day") // It's a nice day
|
||||
|
||||
// ── 5. 反引号:用 ${} 嵌变量 ⭐ ────────────────────
|
||||
const name = '小明'
|
||||
const age = 18
|
||||
console.log('老办法:' + '我叫' + name + ',今年' + age + '岁') // 老办法:我叫小明,今年18岁
|
||||
console.log(`新办法:我叫${name},今年${age}岁`) // 新办法:我叫小明,今年18岁
|
||||
|
||||
// ═════ 轮到你 ═════
|
||||
|
||||
// ── 6. 👇 轮到你:自己写一行,打印 (100 - 20) / 4 的结果 ──
|
||||
console.log('结果 =', (100 - 20) / 4)
|
||||
@ -0,0 +1,168 @@
|
||||
// 02 · 变量与数据类型
|
||||
// 学练复习一体:概念总结 → 示范 → 轮到你
|
||||
// 跑法:Quokka / 右上角 ▷ / node 本文件
|
||||
// 示范区每行后注释是【预期输出】,跑完自己对答案
|
||||
|
||||
// ═════ 概念总结 ═════
|
||||
//
|
||||
// 1. 什么是变量
|
||||
// 变量就是给数据起个名字,方便反复使用和修改。
|
||||
// let age = 18 // 把 18 存进名叫 age 的盒子
|
||||
// age = 19 // 改成 19
|
||||
//
|
||||
// 2. let / const / ~~var~~ ⭐
|
||||
// ┌────────┬──────────┬──────────────────────────────────┐
|
||||
// │ 关键字 │ 能改值吗 │ 什么时候用 │
|
||||
// ├────────┼──────────┼──────────────────────────────────┤
|
||||
// │ const │ ❌ 不能 │ 默认首选,值不变的都用它 │
|
||||
// │ let │ ✅ 能改 │ 需要改变的值(计数器、累加等) │
|
||||
// │ var │ ✅ 能改 │ ⚠️ 老写法,有坑,别用 │
|
||||
// └────────┴──────────┴──────────────────────────────────┘
|
||||
// 原则:优先 const,确实要改再换 let。防手滑改错,代码也更好读。
|
||||
// const PI = 3.14 // 常量,不会变
|
||||
// let count = 0 // 会变的用 let;count = count + 1 ✅
|
||||
// // PI = 3.15 // ❌ 报错:const 不能重新赋值
|
||||
//
|
||||
// 3. 变量命名规则 ⭐
|
||||
// 必须遵守(否则报错):
|
||||
// - 只能用字母、数字、_、$;不能以数字开头
|
||||
// - 不能用关键字(如 let、const)
|
||||
// - 区分大小写(age 和 Age 是两个变量)
|
||||
// 社区惯例(建议遵守):
|
||||
// - 用 camelCase 小驼峰:firstName、isMarried、myAge
|
||||
// - 名字要有意义:age 好过 a
|
||||
//
|
||||
// 4. 八种数据类型
|
||||
// 原始类型(Primitive)—— 7 种,最常用前 3 个:
|
||||
// ┌───────────┬──────────────────┬──────────────────────────┐
|
||||
// │ 类型 │ 例子 │ 说明 │
|
||||
// ├───────────┼──────────────────┼──────────────────────────┤
|
||||
// │ number │ 18, 3.14, -5 │ 数字(整数小数不分家) │
|
||||
// │ string │ 'hi', "abc" │ 字符串 │
|
||||
// │ boolean │ true, false │ 真/假 │
|
||||
// │ undefined │ undefined │ 声明了但没赋值 │
|
||||
// │ null │ null │ 手动表示"空" │
|
||||
// │ bigint │ 10n │ 超大整数(少见) │
|
||||
// │ symbol │ Symbol() │ 唯一标识(少见) │
|
||||
// └───────────┴──────────────────┴──────────────────────────┘
|
||||
// 引用类型(非原始):object —— {}、[]、函数等(后面专门讲)
|
||||
//
|
||||
// 5. typeof —— 查一个值是什么类型 ⭐
|
||||
// typeof 18 // 'number'
|
||||
// typeof 'hi' // 'string'
|
||||
// typeof true // 'boolean'
|
||||
// typeof undefined // 'undefined'
|
||||
// 小坑:typeof null 会返回 'object',这是 JS 历史遗留 bug,记住即可。
|
||||
//
|
||||
// 6. undefined vs null
|
||||
// - undefined:系统给的"还没值"。声明了没赋值就是它。
|
||||
// - null:你主动给的"这里是空的"。
|
||||
// let x; console.log(x) // undefined ← 没赋值,系统默认
|
||||
// let y = null // null ← 你主动清空
|
||||
//
|
||||
// 7. NaN / Infinity 都属于 number ⭐
|
||||
// NaN(Not a Number)名字唬人,但它自己的类型偏偏是 number ——
|
||||
// 它是 number 家族里表示"无效数字"的特殊值,不是独立的第 8 种类型。
|
||||
// typeof NaN // 'number' ← 名叫"不是数字",类型却是数字!
|
||||
// typeof Infinity // 'number'
|
||||
// ┌────────────────┬──────────┬──────────────────────────────────┐
|
||||
// │ 特殊 number 值 │ 含义 │ 怎么产生 │
|
||||
// ├────────────────┼──────────┼──────────────────────────────────┤
|
||||
// │ NaN │ 无效数字 │ 'abc' - 1、Number('hi') 转换失败 │
|
||||
// │ Infinity │ 正无穷 │ 1 / 0 │
|
||||
// │ -Infinity │ 负无穷 │ -1 / 0 │
|
||||
// └────────────────┴──────────┴──────────────────────────────────┘
|
||||
// 大坑:NaN 不等于它自己 ⭐
|
||||
// NaN === NaN // false ← 连自己都不等于!
|
||||
// 所以不能用 === NaN 判断,要用专门函数:
|
||||
// Number.isNaN(x) // 这才是判断"是不是 NaN"的正确方法
|
||||
//
|
||||
// ✅ 本主题要点回顾
|
||||
// 1. 变量 = 给数据起名字
|
||||
// 2. 优先 const,要改才用 let,别用 var ⭐
|
||||
// 3. 命名:小驼峰、有意义、不能数字开头
|
||||
// 4. 常用类型:number / string / boolean,外加 undefined / null
|
||||
// 5. typeof 查类型(注意 typeof null === 'object' 是坑)
|
||||
// 6. NaN / Infinity 都属于 number;NaN !== NaN,判断用 Number.isNaN() ⭐
|
||||
|
||||
// ═════ 示范 ═════
|
||||
|
||||
// ── 1. let 可以改,const 不能改 ⭐ ─────────────────
|
||||
let age = 18
|
||||
console.log(age) // 18
|
||||
age = 19 // let 允许重新赋值
|
||||
console.log(age) // 19
|
||||
|
||||
const PI = 3.14
|
||||
console.log(PI) // 3.14
|
||||
// PI = 3.15 // ← 取消注释会报错:Assignment to constant variable
|
||||
|
||||
// ── 2. const 首选,会变的才用 let ─────────────────
|
||||
let count = 0
|
||||
count = count + 1
|
||||
count = count + 1
|
||||
console.log('count =', count) // count = 2
|
||||
|
||||
// ── 3. 常用数据类型 ───────────────────────────────
|
||||
const myNumber = 100 // number
|
||||
const myString = 'JavaScript' // string
|
||||
const myBool = true // boolean
|
||||
console.log(myNumber, myString, myBool) // 100 JavaScript true
|
||||
|
||||
// ── 4. typeof 查类型 ⭐ ───────────────────────────
|
||||
console.log(typeof 18) // number
|
||||
console.log(typeof 'hi') // string
|
||||
typeof 'hi' // ← 这行会算,但文件里【看不到】任何输出(见第 8 组)
|
||||
console.log(typeof true) // boolean
|
||||
console.log(typeof undefined) // undefined
|
||||
console.log(typeof null) // object ← 历史遗留坑,记住即可
|
||||
|
||||
// ── 5. undefined vs null ─────────────────────────
|
||||
let x // 声明但没赋值
|
||||
console.log(x) // undefined ← 系统默认
|
||||
let y = null // 主动清空
|
||||
console.log(y) // null ← 你给的
|
||||
|
||||
// ── 6. 小驼峰命名 + 反引号拼句子(复习上一章) ──────
|
||||
const firstName = 'Asabeneh'
|
||||
const country = 'Finland'
|
||||
console.log(`${firstName} lives in ${country}`) // Asabeneh lives in Finland
|
||||
|
||||
// ── 7. 数组的 typeof 是 'object' ⭐ ───────────────
|
||||
// 数组不是独立类型,本质是特殊的 object,所以 06-arrays 才专门讲
|
||||
const arr = [1, 2, 3]
|
||||
console.log(typeof arr) // object ← 不是 'array'!
|
||||
console.log(Array.isArray(arr)) // true ← 想确认是不是数组用这个
|
||||
|
||||
// ── 8. "能算" ≠ "能看见":文件里必须 console.log ⭐ ─
|
||||
typeof 'hi' // 这行会算出 'string',但在文件里【看不到】任何东西
|
||||
console.log(typeof 'hi') // string ← 用 console.log 包起来才显示
|
||||
|
||||
// typeof 是运算符不是函数 → 后面加不加括号都行:
|
||||
console.log(typeof 'hi') // string
|
||||
console.log(typeof ('hi')) // string ← 这括号只是分组,不是函数调用
|
||||
|
||||
// ═════ 轮到你 ═════
|
||||
|
||||
// 👇 轮到你(把 ??? 换掉,取消注释,跑一下对答案)
|
||||
|
||||
// 练习 A:声明一个 const 存你的名字,再用反引号打印 "我叫 xxx"
|
||||
// const myName = '???'
|
||||
// console.log(`我叫 ${myName}`)
|
||||
|
||||
// 练习 B:声明一个 let 叫 score = 0,让它加 10,再加 5,打印结果(应为 15)
|
||||
// let score = ???
|
||||
// score = ???
|
||||
// score = ???
|
||||
// console.log('score =', score)
|
||||
|
||||
// 练习 C:用 typeof 查一下 3.14 是什么类型,打印出来(应为 number)
|
||||
// console.log(typeof ???)
|
||||
|
||||
const myName = "Jaydon"
|
||||
console.log(`我叫 ${myName}`) // 我叫 Jaydon ← 是 ${} 不是 &{} !
|
||||
|
||||
let score = 0
|
||||
score += 10
|
||||
score += 5
|
||||
console.log(score)
|
||||
@ -0,0 +1,198 @@
|
||||
// 03 · 运算符
|
||||
// 学练复习一体:概念总结 → 示范 → 轮到你
|
||||
// 跑法:Quokka / 右上角 ▷ / node 本文件
|
||||
// 示范区每行后注释是【预期输出】,跑完自己对答案
|
||||
|
||||
// ═════ 概念总结 ═════
|
||||
//
|
||||
// 1. 算术运算符
|
||||
// ┌──────┬────────────┬──────────┬──────────────────────────┐
|
||||
// │ 符号 │ 含义 │ 例子 │ 结果 │
|
||||
// ├──────┼────────────┼──────────┼──────────────────────────┤
|
||||
// │ + │ 加 │ 2 + 3 │ 5 │
|
||||
// │ - │ 减 │ 5 - 2 │ 3 │
|
||||
// │ * │ 乘 │ 4 * 3 │ 12 │
|
||||
// │ / │ 除 │ 10 / 4 │ 2.5(JS 除法不取整!) │
|
||||
// │ % │ 取余(模) │ 10 % 3 │ 1 │
|
||||
// │ ** │ 幂(次方) │ 2 ** 3 │ 8 │
|
||||
// └──────┴────────────┴──────────┴──────────────────────────┘
|
||||
// 两个初学重点:
|
||||
// - JS 里 10 / 4 = 2.5,不像有些语言整数相除会取整。
|
||||
// - % 取余超常用:判断奇偶(n % 2 === 0 就是偶数)、循环里做周期。
|
||||
//
|
||||
// ⚠️ 负数取余的坑:判奇偶要用 === 0 不要用 === 1
|
||||
// JS 里取余结果的符号跟着"被除数"走,负数取余是负的:
|
||||
// 7 % 2 // 1 -7 % 2 // -1 ← 不是 1!符号跟着 -7
|
||||
// 所以判奇偶:
|
||||
// n % 2 === 0 // ✅ 判偶数,对正负数都成立
|
||||
// n % 2 === 1 // ❌ 判奇数,遇负数会漏:-7%2 是 -1,=== 1 得 false → 误判成偶数
|
||||
// 结论:判奇偶一律用 n % 2 === 0(判偶),再用 else 处理奇数。别用 === 1。
|
||||
//
|
||||
// 2. 赋值运算符
|
||||
// = 是"赋值"(把右边存进左边),下面是简写:
|
||||
// ┌────────┬──────────────┬────────┐
|
||||
// │ 写法 │ 等价于 │ 说明 │
|
||||
// ├────────┼──────────────┼────────┤
|
||||
// │ x += 5 │ x = x + 5 │ 加等于 │
|
||||
// │ x -= 5 │ x = x - 5 │ 减等于 │
|
||||
// │ x *= 2 │ x = x * 2 │ 乘等于 │
|
||||
// │ x /= 2 │ x = x / 2 │ 除等于 │
|
||||
// └────────┴──────────────┴────────┘
|
||||
//
|
||||
// 3. 比较运算符 → 结果是布尔值 ⭐
|
||||
// ┌───────────────┬──────────────────────────────┬──────────┬──────────┐
|
||||
// │ 符号 │ 含义 │ 例子 │ 结果 │
|
||||
// ├───────────────┼──────────────────────────────┼──────────┼──────────┤
|
||||
// │ > < >= <= │ 大小比较 │ 5 > 3 │ true │
|
||||
// │ === │ 严格等于(值和类型都要相等) │ 1 === 1 │ true │
|
||||
// │ !== │ 严格不等 │ 1 !== 2 │ true │
|
||||
// │ == │ 松散等于(会偷偷转类型) │ 1 == '1' │ true ⚠️ │
|
||||
// └───────────────┴──────────────────────────────┴──────────┴──────────┘
|
||||
// ⭐⭐ 最重要的一条规则:永远用 ===,别用 ==
|
||||
// == 会做"隐式类型转换",制造诡异结果:
|
||||
// 1 == '1' // true ← 数字 1 和字符串 '1' 被判相等?!
|
||||
// 1 === '1' // false ← === 看类型,数字 ≠ 字符串,正确
|
||||
// 0 == '' // true ← 更离谱
|
||||
// 记死这条:比较一律用 === 和 !==。这是 JS 最著名的坑之一。
|
||||
//
|
||||
// 4. 逻辑运算符
|
||||
// ┌──────┬───────────┬──────────────────┐
|
||||
// │ 符号 │ 含义 │ 口诀 │
|
||||
// ├──────┼───────────┼──────────────────┤
|
||||
// │ && │ 与(AND) │ 都为真才真 │
|
||||
// │ || │ 或(OR) │ 有一个真就真 │
|
||||
// │ ! │ 非(NOT) │ 真假反转 │
|
||||
// └──────┴───────────┴──────────────────┘
|
||||
// true && false // false true || false // true !true // false
|
||||
// 常用于条件判断:age >= 18 && hasTicket(成年且有票)。
|
||||
//
|
||||
// 5. 一个坑:+ 对字符串是"拼接" ⭐
|
||||
// 1 + 2 // 3 ← 数字相加
|
||||
// '1' + '2' // '12' ← 字符串拼接
|
||||
// '1' + 2 // '12' ← 只要有一个是字符串,就拼接!
|
||||
// 1 + '2' + 3 // '123' ← 从左到右,一旦变字符串后面全拼
|
||||
// 其他运算符(- * /)不会拼接,反而会把字符串转成数字再算:
|
||||
// '6' - 2 === 4、'6' * 2 === 12。只有 + 特殊。
|
||||
//
|
||||
// 6. 类型转换:显式 vs 隐式 ⭐⭐
|
||||
// 上面这些坑的根源,都是 JS 在偷偷"转类型"。
|
||||
// 隐式转换(JS 自作主张,坑的来源)—— 你没让它转,它自己转了:
|
||||
// '5' + 3 // '53' ← 偷偷把 3 转成字符串,拼接
|
||||
// '5' - 3 // 2 ← 偷偷把 '5' 转成数字
|
||||
// 0 == '' // true ← 偷偷都转成数字比较
|
||||
// 'abc' - 1 // NaN ← 转不成数字 → NaN(会像病毒一样传染后续计算)
|
||||
// 显式转换(你主动写,推荐 ✅)—— 意图清楚、无意外:
|
||||
// Number('42') // 42 Number('abc') // NaN ← 转不了就是 NaN
|
||||
// parseInt('42px')// 42 parseFloat('3.14') // 3.14
|
||||
// String(42) // '42'
|
||||
// Boolean('') // false Boolean('hi') // true Boolean(0) // false
|
||||
// 方向性差异(记住):
|
||||
// - 数字 → 字符串:几乎总能成功
|
||||
// - 字符串 → 数字:可能失败,失败得 NaN(处理用户输入时要留心)
|
||||
// 原则:能用显式(Number()/String())就别靠隐式。
|
||||
//
|
||||
// 7. TypeScript 能克服这些坑吗?—— 部分能 ⭐
|
||||
// TS 在写代码时(编译期)检查类型,能挡一大类,但不是全部:
|
||||
// ┌──────────────────────────────────────────────┬───────────────┐
|
||||
// │ 坑 │ TS 能挡吗 │
|
||||
// ├──────────────────────────────────────────────┼───────────────┤
|
||||
// │ '5' - 3(类型不匹配的运算) │ ✅ 编译期报错 │
|
||||
// │ 0 == ''(乱比较) │ ✅ 报错 │
|
||||
// │ '5' + 3(拼接,语法合法) │ ❌ 不管 │
|
||||
// │ 外部数据(输入/请求/JSON.parse)类型错 │ ❌ 只信标注 │
|
||||
// │ 运行时才暴露的问题 │ ❌ 类型已消失 │
|
||||
// └──────────────────────────────────────────────┴───────────────┘
|
||||
// 心智模型:TS 是"写代码时的第一道墙",挡掉大部分手滑;但 + 拼接、外部数据
|
||||
// 这些盲区,仍要靠显式转换 + 校验输入兜底。理解 JS 转换原理,是用好 TS 的前提。
|
||||
//
|
||||
// ✅ 本主题要点回顾
|
||||
// 1. 算术:/ 不取整、% 取余判奇偶、** 次方
|
||||
// 2. 赋值简写:+= -= *= /=
|
||||
// 3. 比较永远用 === / !==,别用 == ⭐⭐
|
||||
// 4. 逻辑:&&(都真)、||(有一真)、!(反转)
|
||||
// 5. + 遇字符串会拼接,其他运算符会转数字 ⭐
|
||||
// 6. 能用显式转换(Number()/String())就别靠隐式 ⭐
|
||||
// 7. TS 能挡"类型不匹配"的坑,但挡不住 + 拼接和外部数据 —— 显式转换的习惯不能丢
|
||||
|
||||
// ═════ 示范 ═════
|
||||
|
||||
// ── 1. 算术运算符 ─────────────────────────────────
|
||||
console.log(2 + 3) // 5
|
||||
console.log(5 - 2) // 3
|
||||
console.log(4 * 3) // 12
|
||||
console.log(10 / 4) // 2.5 ← JS 除法不取整!
|
||||
console.log(10 % 3) // 1 ← 取余
|
||||
console.log(2 ** 3) // 8 ← 2 的 3 次方
|
||||
|
||||
// ── 2. % 取余判断奇偶 ⭐ ──────────────────────────
|
||||
console.log(10 % 2) // 0 ← 余 0 → 偶数
|
||||
console.log(7 % 2) // 1 ← 余 1 → 奇数
|
||||
console.log(10 % 2 === 0) // true ← 判断偶数的标准写法
|
||||
|
||||
// ── 3. 赋值简写 ───────────────────────────────────
|
||||
let x = 10
|
||||
x += 5 // 等价 x = x + 5
|
||||
console.log(x) // 15
|
||||
x *= 2 // 等价 x = x * 2
|
||||
console.log(x) // 30
|
||||
|
||||
// ── 4. 比较运算符 → 布尔值 ────────────────────────
|
||||
console.log(5 > 3) // true
|
||||
console.log(5 <= 4) // false
|
||||
console.log(1 === 1) // true
|
||||
console.log(1 !== 2) // true
|
||||
|
||||
// ── 5. === vs == 的坑 ⭐⭐(最重要)────────────────
|
||||
console.log(1 === '1') // false ← 严格:类型不同 → 不等(正确做法)
|
||||
console.log(1 == '1') // true ← 松散:偷偷转类型 → 相等(危险!)
|
||||
console.log(0 == '') // true ← == 的离谱行为
|
||||
// 结论:永远用 ===,别用 ==
|
||||
|
||||
// ── 6. 逻辑运算符 ─────────────────────────────────
|
||||
console.log(true && false) // false ← 都真才真
|
||||
console.log(true || false) // true ← 有一真就真
|
||||
console.log(!true) // false ← 反转
|
||||
|
||||
const age = 20
|
||||
const hasTicket = true
|
||||
console.log(age >= 18 && hasTicket) // true ← 成年且有票
|
||||
|
||||
// ── 7. + 遇字符串会拼接 ⭐ ────────────────────────
|
||||
console.log(1 + 2) // 3 ← 数字相加
|
||||
console.log('1' + '2') // '12' ← 字符串拼接
|
||||
console.log('1' + 2) // '12' ← 有一个字符串就拼接
|
||||
console.log(1 + '2' + 3) // '123' ← 从左到右,变字符串后全拼
|
||||
|
||||
// 其他运算符反而会把字符串转成数字:
|
||||
console.log('6' - 2) // 4 ← - 让 '6' 变数字 6
|
||||
console.log('6' * 2) // 12 ← * 同理
|
||||
|
||||
// ═════ 轮到你 ═════
|
||||
|
||||
// 👇 轮到你(把 ??? 换掉,取消注释,跑一下对答案)
|
||||
|
||||
// 练习 A:判断 15 是不是偶数(用 % 和 ===),应打印 false
|
||||
// console.log(15 % 2 === 0)
|
||||
|
||||
// 练习 B:一个人 age = 16,判断"成年 且 有票",应打印 false
|
||||
// const myAge = ???
|
||||
// const ticket = true
|
||||
// console.log(??? >= 18 && ticket)
|
||||
|
||||
// 练习 C:猜一下 '5' + 3 和 '5' - 3 分别是什么?先猜再取消注释验证
|
||||
// console.log('5' + 3) // 你猜:___
|
||||
// console.log('5' - 3) // 你猜:___
|
||||
|
||||
// 练习 A:判断 15 是不是偶数 → 要用 === 才是"判断"
|
||||
console.log(15 % 2 === 0) // false ← 15%2 是 1,1===0 为 false
|
||||
console.log(15 % 2) // 1 ← 对比:这只是算余数,不是判断
|
||||
|
||||
// 练习 B:age=16,判断"成年(>=18)且有票"
|
||||
const myAge = 16
|
||||
const ticket = true
|
||||
console.log(myAge >= 18 && ticket) // false ← 16 不够 18,&& 整体为 false
|
||||
|
||||
// 练习 C:先猜后验(你猜的 '53' 和 2 都对!)
|
||||
console.log('5' + 3) // '53' ← + 遇字符串 → 拼接
|
||||
console.log('5' - 3) // 2 ← - 把 '5' 转成数字 → 5-3
|
||||
|
||||
@ -0,0 +1,159 @@
|
||||
// 04 · 字符串常用方法
|
||||
// 学练复习一体:概念总结 → 示范 → 轮到你
|
||||
// 跑法:Quokka / 右上角 ▷ / node 本文件
|
||||
// 示范区带预期输出;轮到你区只给要求,自己写
|
||||
|
||||
// ═════ 概念总结 ═════
|
||||
//
|
||||
// 字符串(string)是一串文字。JS 给它内置了很多"方法"(method),
|
||||
// 可以查长度、截取、替换、大小写转换等。
|
||||
//
|
||||
// 1. 长度:.length(是属性,不是方法)⭐
|
||||
// .length 后面不加括号,因为它是"属性"不是"方法"。
|
||||
// 'hello'.length // 5
|
||||
// 区分:.length 是属性(不加括号);.toUpperCase() 是方法(加括号)。
|
||||
// 判断法:方法是"动作",要加 ();属性是"数据",不加。
|
||||
//
|
||||
// 2. 大小写
|
||||
// ┌────────────────┬────────┬────────────────────────────┐
|
||||
// │ 方法 │ 作用 │ 例子 │
|
||||
// ├────────────────┼────────┼────────────────────────────┤
|
||||
// │ .toUpperCase() │ 转大写 │ 'hi'.toUpperCase() → 'HI' │
|
||||
// │ .toLowerCase() │ 转小写 │ 'HI'.toLowerCase() → 'hi' │
|
||||
// └────────────────┴────────┴────────────────────────────┘
|
||||
//
|
||||
// 3. 按位置取字符
|
||||
// const s = 'hello'
|
||||
// s[0] // 'h' ← 下标从 0 开始!
|
||||
// s[1] // 'e'
|
||||
// s[s.length - 1] // 'o' ← 最后一个字符的通用写法
|
||||
// 关键:下标从 0 开始,第 1 个字符是 s[0],最后一个是 s[length-1]。
|
||||
//
|
||||
// 4. 截取:.slice(start, end) ⭐
|
||||
// 从 start 截到 end(不含 end)。
|
||||
// const s = 'JavaScript'
|
||||
// s.slice(0, 4) // 'Java' ← 取下标 0,1,2,3(不含 4)
|
||||
// s.slice(4) // 'Script' ← 省略 end 则取到结尾
|
||||
// s.slice(-6) // 'Script' ← 负数从右边数
|
||||
// "不含 end"是初学最容易错的点:slice(0, 4) 拿的是 4 个字符,不是 5 个。
|
||||
//
|
||||
// 5. 查找
|
||||
// ┌──────────────────┬──────────────┬───────────────────────────────┐
|
||||
// │ 方法 │ 作用 │ 返回 │
|
||||
// ├──────────────────┼──────────────┼───────────────────────────────┤
|
||||
// │ .includes('x') │ 是否包含 │ true/false │
|
||||
// │ .indexOf('x') │ 首次出现下标 │ 数字;找不到返回 -1 │
|
||||
// │ .startsWith('x') │ 是否以…开头 │ true/false │
|
||||
// │ .endsWith('x') │ 是否以…结尾 │ true/false │
|
||||
// └──────────────────┴──────────────┴───────────────────────────────┘
|
||||
// 'hello'.includes('ell') // true
|
||||
// 'hello'.indexOf('l') // 2
|
||||
// 'hello'.indexOf('z') // -1 ← 找不到是 -1,不是报错
|
||||
//
|
||||
// 6. 替换与去空格
|
||||
// 'a-b-c'.replace('-', '+') // 'a+b-c' ← 只换第一个
|
||||
// 'a-b-c'.replaceAll('-', '+') // 'a+b+c' ← 全换
|
||||
// ' hi '.trim() // 'hi' ← 去掉两端空格
|
||||
//
|
||||
// 7. 拆分:.split() ⭐(字符串 → 数组)
|
||||
// 'a,b,c'.split(',') // ['a', 'b', 'c'] ← 按逗号拆成数组
|
||||
// 'hello'.split('') // ['h','e','l','l','o'] ← 拆成单个字符
|
||||
// split 是字符串通向数组的桥梁,后面数组章会经常一起用。
|
||||
//
|
||||
// ⚠️ 一个重要特性:字符串不可变(immutable)
|
||||
// 字符串方法都不会改原字符串,而是返回一个新字符串。
|
||||
// let s = 'hello'
|
||||
// s.toUpperCase() // 'HELLO'(返回新的)
|
||||
// console.log(s) // 'hello' ← 原来的没变!
|
||||
// s = s.toUpperCase() // 想保留结果,得重新赋值
|
||||
// 记住:想要变化生效,必须把返回值赋回去(或存到新变量)。
|
||||
//
|
||||
// ✅ 本主题要点回顾
|
||||
// 1. .length 是属性(不加括号);方法要加 ()
|
||||
// 2. 下标从 0 开始,最后一个是 [length-1]
|
||||
// 3. .slice(start, end) 截取,不含 end ⭐
|
||||
// 4. .includes / .indexOf(找不到返回 -1)
|
||||
// 5. .split() 把字符串拆成数组 ⭐
|
||||
// 6. 字符串不可变:方法返回新串,想生效要赋值回去 ⭐
|
||||
|
||||
// ═════ 示范 ═════
|
||||
|
||||
// ── 1. 长度 .length(属性,不加括号)──────────────
|
||||
console.log('hello'.length) // 5
|
||||
|
||||
// ── 2. 大小写 ─────────────────────────────────────
|
||||
console.log('hi'.toUpperCase()) // HI
|
||||
console.log('HI'.toLowerCase()) // hi
|
||||
|
||||
// ── 3. 按下标取字符(从 0 开始)────────────────────
|
||||
const s = 'hello'
|
||||
console.log(s[0]) // h
|
||||
console.log(s[s.length - 1]) // o ← 最后一个字符
|
||||
|
||||
// ── 4. 截取 .slice(start, end),不含 end ⭐ ────────
|
||||
const lang = 'JavaScript'
|
||||
console.log(lang.slice(0, 4)) // Java ← 取 0,1,2,3(不含4)
|
||||
console.log(lang.slice(4)) // Script ← 到结尾
|
||||
console.log(lang.slice(-6)) // Script ← 负数从右数
|
||||
|
||||
// ── 5. 查找 ───────────────────────────────────────
|
||||
console.log('hello'.includes('ell')) // true
|
||||
console.log('hello'.indexOf('l')) // 2
|
||||
console.log('hello'.indexOf('z')) // -1 ← 找不到是 -1
|
||||
|
||||
// ── 6. 替换 / 去空格 ──────────────────────────────
|
||||
console.log('a-b-c'.replace('-', '+')) // a+b-c ← 只换第一个
|
||||
console.log('a-b-c'.replaceAll('-', '+')) // a+b+c ← 全换
|
||||
console.log(' hi '.trim()) // hi ← 去两端空格
|
||||
|
||||
// ── 7. 拆分 .split() → 数组 ⭐ ────────────────────
|
||||
console.log('a,b,c'.split(',')) // ['a', 'b', 'c']
|
||||
console.log('hello'.split('')) // ['h','e','l','l','o']
|
||||
|
||||
// ── 8. 字符串不可变 ⭐ ────────────────────────────
|
||||
let word = 'hello'
|
||||
word.toUpperCase() // 返回新串,但没接住
|
||||
console.log(word) // hello ← 原串没变!
|
||||
word = word.toUpperCase() // 赋值回去才生效
|
||||
console.log(word) // HELLO
|
||||
|
||||
let text = 'hello'
|
||||
text[0] = 'x'
|
||||
console.log(text)
|
||||
|
||||
// ═════ 轮到你 ═════
|
||||
|
||||
// 👇 轮到你(只给要求,自己写代码)
|
||||
|
||||
// 练习 A:有字符串 const city = 'Helsinki'
|
||||
// ① 打印它的长度
|
||||
// ② 打印它的大写形式
|
||||
// ③ 打印它的第一个字符
|
||||
|
||||
const city = 'Helsinki'
|
||||
console.log(city.length)
|
||||
console.log(city.toUpperCase())
|
||||
console.log(city[0])
|
||||
|
||||
|
||||
// 练习 B:有 const email = 'user@example.com'
|
||||
// ① 用 includes 判断它是否包含 '@'(应得 true)
|
||||
// ② 用 split 按 '@' 拆开,打印结果(想想会得到什么形状)
|
||||
const email = 'user@example.com'
|
||||
console.log(email.includes('@'))
|
||||
console.log(email.split('@'))
|
||||
|
||||
|
||||
// 练习 C:有 const raw = ' JavaScript '(两端有空格)
|
||||
// 先去掉两端空格,再转成小写,最后打印。
|
||||
// 提示:两个方法可以【链式】连着写:raw.trim().toLowerCase()
|
||||
const raw = ' JavaScript '
|
||||
console.log(raw.trim().toLowerCase())
|
||||
|
||||
|
||||
|
||||
// 练习 D(动脑):const name = 'javascript'
|
||||
// 只用你学过的方法,把它变成首字母大写的 'Javascript' 再打印。
|
||||
// 提示:第一个字符大写 + 剩下的部分,用 + 拼起来。
|
||||
const name = 'javascript'
|
||||
console.log(name[0].toUpperCase() + name.slice(1, name.length))
|
||||
@ -0,0 +1,203 @@
|
||||
// 05 · 条件与布尔
|
||||
// 学练复习一体:概念总结 → 示范 → 轮到你
|
||||
// 跑法:Quokka / 右上角 ▷ / node 本文件
|
||||
// 示范区带预期输出;轮到你区只给要求,自己写
|
||||
|
||||
// ═════ 概念总结 ═════
|
||||
//
|
||||
// 程序会"做决定"—— 根据条件走不同分支。这是代码从"一条直线"变成"有逻辑"的开始。
|
||||
//
|
||||
// 1. if / else if / else
|
||||
// const age = 20
|
||||
// if (age >= 18) {
|
||||
// console.log('成年')
|
||||
// } else if (age >= 13) {
|
||||
// console.log('青少年')
|
||||
// } else {
|
||||
// console.log('儿童')
|
||||
// }
|
||||
// - 条件写在 () 里,结果要是 true/false(或真假值,见第 4 节)
|
||||
// - {} 里是满足时执行的代码
|
||||
// - 从上往下,命中第一个成立的就停,后面不再判断
|
||||
//
|
||||
// 2. 三元运算符 ? : ⭐
|
||||
// if/else 的极简版,一行搞定"二选一",而且有返回值可以赋给变量:
|
||||
// const status = age >= 18 ? '成年' : '未成年'
|
||||
// // 条件 ? 真时的值 : 假时的值
|
||||
// 适合:简单的二选一赋值。
|
||||
// 别滥用:逻辑复杂就用 if,别把三元套三元,可读性差。
|
||||
//
|
||||
// 3. switch(多分支)
|
||||
// 当一个值要和很多固定选项比对时,比一长串 else if 清爽:
|
||||
// switch (day) {
|
||||
// case 1:
|
||||
// console.log('周一')
|
||||
// break // ← 别忘!否则会"穿透"继续执行下面的 case
|
||||
// case 3:
|
||||
// console.log('周三')
|
||||
// break
|
||||
// default:
|
||||
// console.log('其他')
|
||||
// }
|
||||
// 为什么每个 case 都要 break —— "穿透"(fall-through)⭐
|
||||
// 关键:case 不是"独立的盒子",而是"跳转的入口(标签)"。匹配成功后程序从那个
|
||||
// 入口跳进去,然后一路往下执行,撞到 break 才跳出。不写 break 就会"掉进"下一个 case。
|
||||
// day = 1 时若各 case 都不写 break → 输出:周一 周二 周三(全跑了)
|
||||
// 跨语言对比(纠正常见误解):
|
||||
// ┌───────────────────────────┬──────────────────────────────────────┐
|
||||
// │ 语言 │ 要 break 吗 │
|
||||
// ├───────────────────────────┼──────────────────────────────────────┤
|
||||
// │ C / C++ / Java / JS │ ✅ 都要(JS 的 switch 就是抄 C 的) │
|
||||
// │ Python │ 没有传统 switch(match 机制不同,不穿透)│
|
||||
// └───────────────────────────┴──────────────────────────────────────┘
|
||||
// 记反了很常见:C++/Java 和 JS 一样都要写 break,不是自动 break。
|
||||
// 穿透的唯一正当用法:多个 case 共用逻辑(故意省略 break):
|
||||
// case 6:
|
||||
// case 7:
|
||||
// console.log('周末') // 6 和 7 都到这
|
||||
// break
|
||||
// 💡 因为"忘写 break"太容易出 bug,现代 JS 常少用 switch,改用 if/else if
|
||||
// 或对象查表(学对象后会讲)。知道 switch 怎么回事即可,实战非首选。
|
||||
//
|
||||
// 4. 真假值 truthy / falsy ⭐⭐(之前埋的伏笔)
|
||||
// if() 里不一定非要放 true/false —— 任何值都会被 JS 自动判成"真"或"假"。
|
||||
// 假值(falsy)—— 只有这 6 个,记住就行:
|
||||
// false 0 ''(空字符串) null undefined NaN
|
||||
// 真值(truthy)—— 除了上面 6 个,其他全是真:
|
||||
// 包括非零数字、非空字符串、[] 空数组、{} 空对象(注意!空数组空对象是真)
|
||||
// if ('hello') … // 会执行
|
||||
// if (0) … // 不执行
|
||||
// if ([]) … // 会执行(容易错)
|
||||
// 用途:快速判断"有没有值"(空串是假 → 走 else 分支)。
|
||||
//
|
||||
// 5. 短路:&& / || 返回的是"操作数原值",不是布尔 ⭐⭐
|
||||
// 关键认知:JS 的 &&/|| 不返回 true/false,而是返回其中一个操作数本身。
|
||||
// 靠的是短路求值:能确定结果时立刻停下,返回"决定胜负的那个值"。
|
||||
// || :返回第一个真值(找到真就停);全假返回最后一个
|
||||
// '' || '匿名' // '匿名' ← 前面假,继续,返回后面
|
||||
// 'Tom' || '匿名' // 'Tom' ← 前面就是真,立刻返回,'匿名'看都不看
|
||||
// 0 || 100 // 100
|
||||
// && :返回第一个假值(找到假就停);全真返回最后一个
|
||||
// 'Tom' && '有值' // '有值' ← 前面真,继续,返回后面
|
||||
// '' && '有值' // '' ← 前面就是假,立刻返回它
|
||||
// 1 && 2 && 3 // 3 ← 全真,返回最后一个
|
||||
// 跨语言对比(纠正误解):
|
||||
// ┌─────────────────────────────┬───────────────────────────────────┐
|
||||
// │ 语言 │ &&/|| 返回 │
|
||||
// ├─────────────────────────────┼───────────────────────────────────┤
|
||||
// │ JS / Python / Ruby / Lua │ 操作数原值(如 '' || 'x' → 'x') │
|
||||
// │ C / C++ / Java / C# │ 严格 boolean │
|
||||
// └─────────────────────────────┴───────────────────────────────────┘
|
||||
// 记反了很常见:Python 和 JS 一样返回原值;Java/C++ 才返回布尔。
|
||||
// 两个超常用惯用法 ⭐:
|
||||
// const name = userInput || '游客' // ① || 设默认值:没填(空串是假)就用 '游客'
|
||||
// user && user.login() // ② && 做"守卫":user 存在才调用 login,避免报错
|
||||
// // (React 里满地都是: 条件 && <组件/> → 条件成立才渲染)
|
||||
// 想要纯布尔?用 !!:
|
||||
// !!'Tom' // true !!'' // false !!0 // false
|
||||
// ! 取反得布尔,!! 再反回来 → 纯 true/false。
|
||||
// 💡 为什么这样设计:JS 到处用 truthy/falsy 而非死板布尔(见第 4 节),
|
||||
// 所以 &&/|| 返回原值才顺理成章 —— 这两个知识点是一套的。
|
||||
//
|
||||
// ✅ 本主题要点回顾
|
||||
// 1. if / else if / else:命中第一个成立的就停
|
||||
// 2. 三元 条件 ? 真值 : 假值:简单二选一,有返回值 ⭐
|
||||
// 3. switch:多分支,每个 case 别忘 break
|
||||
// 4. falsy 只有 6 个:false 0 '' null undefined NaN;其余全 truthy(空数组空对象是真!)⭐⭐
|
||||
// 5. &&/|| 返回操作数原值(非布尔):|| 取第一个真值(设默认值),&& 取第一个假值(做守卫);
|
||||
// 要纯布尔用 !!。Python 同理,Java/C++ 才返回布尔 ⭐⭐
|
||||
|
||||
// ═════ 示范 ═════
|
||||
|
||||
// ── 1. if / else if / else ───────────────────────
|
||||
const age = 20
|
||||
if (age >= 18) {
|
||||
console.log('成年') // ← 命中这个,输出:成年
|
||||
} else if (age >= 13) {
|
||||
console.log('青少年')
|
||||
} else {
|
||||
console.log('儿童')
|
||||
}
|
||||
|
||||
// ── 2. 三元运算符 ? : ⭐ ──────────────────────────
|
||||
const status = age >= 18 ? '成年' : '未成年'
|
||||
console.log(status) // 成年
|
||||
|
||||
// ── 3. switch(每个 case 别忘 break)───────────────
|
||||
const day = 3
|
||||
switch (day) {
|
||||
case 1:
|
||||
console.log('周一')
|
||||
break
|
||||
case 3:
|
||||
console.log('周三') // ← 输出:周三
|
||||
break
|
||||
default:
|
||||
console.log('其他')
|
||||
}
|
||||
|
||||
// ── 4. truthy / falsy ⭐⭐ ────────────────────────
|
||||
// 假值只有 6 个:false 0 '' null undefined NaN
|
||||
if ('hello') console.log('非空字符串 → 真') // 会执行
|
||||
if (0) console.log('这行不会执行') // 0 是假
|
||||
if ([]) console.log('空数组 → 真!') // 会执行(容易错)
|
||||
if ('') {
|
||||
console.log('空串是真')
|
||||
} else {
|
||||
console.log('空串是假 → 走这') // 会执行
|
||||
}
|
||||
|
||||
// ── 5. 短路:|| 设默认值,&& 前真才看后 ⭐ ─────────
|
||||
console.log('' || '匿名') // 匿名 ← 前面假,取后面
|
||||
console.log('Tom' || '匿名') // Tom ← 前面真,直接取
|
||||
console.log(true && '继续') // 继续
|
||||
|
||||
// ═════ 轮到你 ═════
|
||||
|
||||
// 👇 轮到你(只给要求,自己写代码)
|
||||
|
||||
// 练习 A:给一个 const score = 75
|
||||
// 用 if/else if/else 判断等级并打印:
|
||||
// >=90 打印 'A',>=60 打印 'B',否则打印 'C'
|
||||
|
||||
const score = 75
|
||||
if (score >= 90) {
|
||||
console.log('A')
|
||||
} else if (score >= 60 ) {
|
||||
console.log('B')
|
||||
} else {
|
||||
console.log('C')
|
||||
}
|
||||
|
||||
// 练习 B:把练习 A 里"是否及格(>=60)"改用【三元运算符】写,
|
||||
// 得到 '及格' 或 '不及格',打印出来
|
||||
console.log(score >= 60 ? '及格' : '不及格')
|
||||
|
||||
|
||||
// 练习 C(truthy/falsy):有 const username = ''
|
||||
// 用 if 判断:如果 username 有值就打印它,否则打印 '游客'
|
||||
// (想想空字符串是真还是假)
|
||||
const username = ''
|
||||
if (username) {
|
||||
console.log(username)
|
||||
} else {
|
||||
console.log('游客')
|
||||
}
|
||||
|
||||
|
||||
// 练习 D(短路,动脑):用 || 把练习 C 改成【一行】,
|
||||
// 效果一样:有名字用名字,没名字用 '游客'
|
||||
console.log(username || '游客')
|
||||
|
||||
|
||||
// 练习 E(挑战):写一个三元,判断 num 是奇数还是偶数,
|
||||
// const num = 7,打印 '奇数' 或 '偶数'(回忆 % 取余)
|
||||
const num = 7
|
||||
console.log(num % 2 === 1 ? '奇数' : '偶数')
|
||||
|
||||
|
||||
if (-3) {
|
||||
console.log('true')
|
||||
} else {
|
||||
console.log('false')
|
||||
}
|
||||
@ -0,0 +1,213 @@
|
||||
// 06 · 数组(Array)
|
||||
// 学练复习一体:概念总结 → 示范 → 轮到你
|
||||
// 跑法:Quokka / 右上角 ▷ / node 本文件
|
||||
// 示范区带预期输出;轮到你区只给要求,自己写
|
||||
|
||||
// ═════ 概念总结 ═════
|
||||
//
|
||||
// 数组 = 有序的一组值,用 [] 包起来。JS 里最常用的数据结构。
|
||||
//
|
||||
// 0. 回顾:数组是"引用类型" ⭐
|
||||
// 复习 00-concepts/value-vs-reference.md:数组是对象/引用类型,
|
||||
// - typeof [1,2,3] → 'object'(不是 'array')
|
||||
// - 判断是不是数组用 Array.isArray(arr)
|
||||
// - 可以原地修改(和字符串不可变相反)
|
||||
// - let b = a 是共享同一个数组,不是复制
|
||||
//
|
||||
// 1. 创建 & 取值
|
||||
// const fruits = ['apple', 'banana', 'cherry']
|
||||
// fruits[0] // 'apple' ← 下标从 0 开始
|
||||
// fruits[2] // 'cherry'
|
||||
// fruits[fruits.length - 1] // 'cherry' ← 最后一个
|
||||
// fruits.length // 3 ← 长度(属性,不加括号)
|
||||
//
|
||||
// 2. 增删(会改原数组)⭐
|
||||
// ┌─────────────┬──────────────────────────────┬────────┐
|
||||
// │ 方法 │ 作用 │ 位置 │
|
||||
// ├─────────────┼──────────────────────────────┼────────┤
|
||||
// │ .push(x) │ 加到末尾 │ 尾 │
|
||||
// │ .pop() │ 删最后一个(并返回它) │ 尾 │
|
||||
// │ .unshift(x) │ 加到开头 │ 头 │
|
||||
// │ .shift() │ 删第一个(并返回它) │ 头 │
|
||||
// └─────────────┴──────────────────────────────┴────────┘
|
||||
// 记忆:push/pop 管尾,shift/unshift 管头。这四个都直接改原数组。
|
||||
//
|
||||
// 3. 查找
|
||||
// ┌────────────────┬──────────────┬────────────────────┐
|
||||
// │ 方法 │ 作用 │ 返回 │
|
||||
// ├────────────────┼──────────────┼────────────────────┤
|
||||
// │ .includes(x) │ 是否包含 │ true/false │
|
||||
// │ .indexOf(x) │ 首次出现下标 │ 数字;找不到 -1 │
|
||||
// └────────────────┴──────────────┴────────────────────┘
|
||||
//
|
||||
// 4. 截取 & 拼接
|
||||
// .slice(start, end) —— 不改原数组,返回新的(不含 end):
|
||||
// const a = [1, 2, 3, 4, 5]
|
||||
// a.slice(1, 3) // [2, 3] ← 和字符串 slice 一样,不含 end
|
||||
// a.slice(2) // [3, 4, 5]
|
||||
// .concat() / 展开 —— 合并数组:
|
||||
// [1, 2].concat([3, 4]) // [1,2,3,4]
|
||||
// [...[1, 2], ...[3, 4]] // [1,2,3,4] ← 展开语法(后面章节详讲)
|
||||
//
|
||||
// 5. ⚠️ .splice() —— 强力但会改原数组
|
||||
// splice(起点, 删几个, 要插入的...) 能删能插,直接改原数组:
|
||||
// const a = [1, 2, 3, 4]
|
||||
// a.splice(1, 2) // 从下标1删2个 → a: [1, 4]
|
||||
// const b = ['a', 'd']
|
||||
// b.splice(1, 0, 'b', 'c') // 下标1删0个,插入 b,c → b: ['a','b','c','d']
|
||||
// 第一个参数是"下标",插入发生在该下标之前 ⭐
|
||||
// 插入的新元素会占据 index 这个位置,原本在此及以后的元素全部后移。
|
||||
// 缝隙心智模型(同时解释 slice):把下标看成元素之间的"缝":
|
||||
// [ first , third ]
|
||||
// ↑ ↑ ↑
|
||||
// 缝0 缝1 缝2
|
||||
// splice(index, 0, x) = 把 x 塞进"缝 index":
|
||||
// ['first','third'].splice(0, 0, 'x') // ['x','first','third'] 塞缝0=最前
|
||||
// ['first','third'].splice(1, 0, 'x') // ['first','x','third'] 塞缝1=中间 ✅
|
||||
// ['first','third'].splice(2, 0, 'x') // ['first','third','x'] 塞缝2=最后(index=length)
|
||||
// 同一模型解释 slice(1,3):从缝1切到缝3,夹住的是下标1、2 → 所以"不含 end"。
|
||||
// ⚠️ splice 的返回值是"被删元素",不是修改后的数组:
|
||||
// const q = ['first', 'third']
|
||||
// const removed = q.splice(1, 0, 'second')
|
||||
// console.log(q) // ['first','second','third'] ← 改的是原数组,要打印它
|
||||
// console.log(removed) // [] ← 返回值是被删的元素,删0个就是空数组
|
||||
// 所以插入后要 console.log(q)(原数组),别打印 q.splice(...) 的返回值。
|
||||
// ⚠️ 别把 slice(切片,不改原,返回切出的新数组)和 splice(剪接,改原,
|
||||
// 返回被删元素)搞混!差一个字母,行为完全不同。
|
||||
//
|
||||
// 6. 数组 ↔ 字符串
|
||||
// ['a', 'b', 'c'].join('-') // 'a-b-c' ← 数组转字符串
|
||||
// 'a-b-c'.split('-') // ['a','b','c'] ← 字符串转数组(复习 04)
|
||||
//
|
||||
// 7. 一个坑:== 比不了数组内容
|
||||
// 数组是引用类型,=== 比的是"是不是同一个对象",不是内容:
|
||||
// [1, 2] === [1, 2] // false! ← 两个不同的数组对象
|
||||
// 比内容要逐个比,或转成字符串比(后面有更好的办法)。
|
||||
//
|
||||
// ✅ 本主题要点回顾
|
||||
// 1. 数组是引用类型(可原地改;let b=a 是共享)⭐
|
||||
// 2. [0] 取值,下标从 0;.length 长度
|
||||
// 3. push/pop 管尾,shift/unshift 管头,都改原数组 ⭐
|
||||
// 4. .slice 不改原(返回切出的新数组);.splice 改原(删/插,返回被删元素,
|
||||
// 插入后要打印原数组)—— 别搞混 ⭐
|
||||
// splice 第一个参数是下标,插入在其之前;用"缝隙模型"记(缝index)
|
||||
// 5. .join() 数组→字符串,.split() 字符串→数组
|
||||
// 6. [1,2] === [1,2] 是 false(比的是对象不是内容)⭐
|
||||
|
||||
// ═════ 示范 ═════
|
||||
|
||||
// ── 0. 数组是引用类型(复习)──────────────────────
|
||||
console.log(typeof [1, 2, 3]) // object ← 不是 'array'
|
||||
console.log(Array.isArray([1, 2, 3])) // true
|
||||
|
||||
// ── 1. 创建 & 取值 ───────────────────────────────
|
||||
const fruits = ['apple', 'banana', 'cherry']
|
||||
console.log(fruits[0]) // apple
|
||||
console.log(fruits[fruits.length - 1]) // cherry
|
||||
console.log(fruits.length) // 3
|
||||
|
||||
// ── 2. 增删:push/pop 管尾,shift/unshift 管头 ⭐ ──
|
||||
const a = [1, 2, 3]
|
||||
a.push(4)
|
||||
console.log(a) // [1, 2, 3, 4]
|
||||
a.pop()
|
||||
console.log(a) // [1, 2, 3]
|
||||
a.unshift(0)
|
||||
console.log(a) // [0, 1, 2, 3]
|
||||
a.shift()
|
||||
console.log(a) // [1, 2, 3]
|
||||
|
||||
// ── 3. 查找 ──────────────────────────────────────
|
||||
console.log([1, 2, 3].includes(2)) // true
|
||||
console.log([1, 2, 3].indexOf(3)) // 2
|
||||
console.log([1, 2, 3].indexOf(9)) // -1
|
||||
|
||||
// ── 4. slice 不改原(不含 end)────────────────────
|
||||
const b = [1, 2, 3, 4, 5]
|
||||
console.log(b.slice(1, 3)) // [2, 3]
|
||||
console.log(b) // [1, 2, 3, 4, 5] ← 原数组没变
|
||||
|
||||
// ── 5. splice 改原(删/插)⚠️ 别和 slice 混 ────────
|
||||
const c = [1, 2, 3, 4]
|
||||
c.splice(1, 2) // 从下标1删2个
|
||||
console.log(c) // [1, 4]
|
||||
|
||||
// ── 6. 数组 ↔ 字符串 ─────────────────────────────
|
||||
console.log(['a', 'b', 'c'].join('-')) // a-b-c
|
||||
console.log('a-b-c'.split('-')) // ['a', 'b', 'c']
|
||||
|
||||
// ── 7. === 比不了内容 ────────────────────────────
|
||||
console.log([1, 2] === [1, 2]) // false ← 是两个不同对象
|
||||
|
||||
// ═════ 轮到你 ═════
|
||||
|
||||
// 👇 轮到你(只给要求,自己写代码)
|
||||
|
||||
// 练习 A:有 const nums = [10, 20, 30]
|
||||
// ① 在末尾加上 40
|
||||
// ② 在开头加上 5
|
||||
// ③ 打印最终数组和它的长度
|
||||
// (最终应是 [5, 10, 20, 30, 40],长度 5)
|
||||
|
||||
const num = [10, 20, 30]
|
||||
num.push(40)
|
||||
num.unshift(5)
|
||||
console.log(num)
|
||||
console.log(num.length)
|
||||
|
||||
|
||||
|
||||
// 练习 B:有 const colors = ['red', 'green', 'blue']
|
||||
// ① 用 includes 判断有没有 'green'
|
||||
// ② 打印 'blue' 的下标
|
||||
// ③ 用 join 把它们拼成 'red, green, blue'(用 ', ' 连接)
|
||||
const colors = ['red', 'green', 'blue']
|
||||
console.log(colors.includes('green'))
|
||||
console.log(colors.indexOf('blue'))
|
||||
console.log(colors.join(', '))
|
||||
|
||||
|
||||
|
||||
|
||||
// 练习 C:有 const letters = ['a', 'b', 'c', 'd', 'e']
|
||||
// 用 slice 取出中间三个 ['b', 'c', 'd'],并打印。
|
||||
// 打印完再打印一次 letters 本身,确认它没被改动。
|
||||
const letters = ['a', 'b', 'c', 'd', 'e']
|
||||
console.log(letters.slice(1, 4))
|
||||
console.log(letters)
|
||||
|
||||
|
||||
// 练习 D(动脑):有 const q = ['first', 'third']
|
||||
// 用 splice 在 'first' 和 'third' 之间插入 'second',
|
||||
// 让它变成 ['first', 'second', 'third'],打印。
|
||||
// 提示:splice(位置, 删几个, 插入什么) —— 这里"删几个"是 0
|
||||
const q = ['first', 'third']
|
||||
|
||||
// ❌ splice 返回的是“被删除的元素",不是修改后的数组
|
||||
// console.log(q.splice(0, 0, 'second'))
|
||||
|
||||
// ✅
|
||||
q.splice(1, 0, 'second')
|
||||
console.log(q)
|
||||
|
||||
// 练习 E(挑战):有 const sentence = 'I love java script'
|
||||
// ① 用 split 把它拆成单词数组
|
||||
// ② 用 join 把单词用 '-' 重新连起来
|
||||
// (最终得到 'I-love-java-script')
|
||||
const sentence = 'I love java script'
|
||||
|
||||
|
||||
// ❌
|
||||
// sentence.split(' ')
|
||||
// console.log(sentence)
|
||||
// sentence.join('-')
|
||||
|
||||
|
||||
// ❌ 直接写 array = ... 而没声明。这在普通模式下能跑(JS 会偷偷创建一个全局变量),但这是坏习惯,而且埋雷:
|
||||
// - 严格模式('use strict')下会直接报错:array is not defined
|
||||
// - 它会"泄漏"成全局变量,以后可能和别处撞名,制造诡异 bug
|
||||
// array = sentence.split(' ')
|
||||
|
||||
// ✅
|
||||
const array = sentence.split(' ')
|
||||
console.log(array.join('-'))
|
||||
@ -0,0 +1,231 @@
|
||||
// 07 · 对象(Object)
|
||||
// 学练复习一体:概念总结 → 示范 → 轮到你
|
||||
// 跑法:Quokka / 右上角 ▷ / node 本文件
|
||||
// 示范区带预期输出;轮到你区只给要求,自己写
|
||||
|
||||
// ═════ 概念总结 ═════
|
||||
//
|
||||
// 对象 = 一组"键值对"(key-value),用 {} 包起来。
|
||||
// 数组是"有序列表"(按下标),对象是"带标签的数据"(按名字)。
|
||||
// 用来表示真实世界的东西:一个人、一辆车。
|
||||
//
|
||||
// 1. 创建 & 结构
|
||||
// const person = {
|
||||
// name: 'Asabeneh', // 键: 值
|
||||
// age: 100,
|
||||
// isMarried: true,
|
||||
// skills: ['HTML', 'CSS', 'JS'], // 值可以是数组
|
||||
// }
|
||||
// - 每个 键: 值 之间用逗号隔开
|
||||
// - 键(key) 是名字(字符串),值(value) 可以是任何类型(数字、字符串、数组、甚至另一个对象)
|
||||
//
|
||||
// 2. 取值:两种方式 ⭐
|
||||
// person.name // 'Asabeneh' ← 方式一:点号 .(最常用)
|
||||
// person['name'] // 'Asabeneh' ← 方式二:方括号 ['键']
|
||||
// const key = 'age'
|
||||
// person[key] // 100 ← 键名存在变量里,只能用方括号
|
||||
// 记:固定键名用 .name;键名是变量时用 [变量]。
|
||||
// person.key 和 person[key] 不一样!前者找叫 "key" 的键,后者找变量 key 的值对应的键。
|
||||
//
|
||||
// 3. 增 / 改 / 删
|
||||
// 对象是引用类型(和数组一样),可以原地改:
|
||||
// const p = { name: 'Tom' }
|
||||
// p.age = 20 // 增:直接给新键赋值
|
||||
// p.name = 'Jerry'// 改:给已有键赋新值
|
||||
// delete p.age // 删:用 delete
|
||||
// delete 是运算符,专删对象属性 —— 数组别用它 ⭐
|
||||
// delete 是一元运算符(和 typeof 同类),不是方法 → 写 delete obj.key,不是 obj.delete()。
|
||||
// delete p.age // ✅ 对象属性
|
||||
// p.delete('age') // ❌ 对象没有 delete 方法
|
||||
// ⚠️ 数组虽然是特殊对象、语法上能 delete,但会留下"空洞",不该用:
|
||||
// const arr = [1, 2, 3]
|
||||
// delete arr[1] // [1, empty, 3] ← 抹空但不移位、长度还是 3!畸形
|
||||
// 数组删元素用 splice(见 06 章):arr2.splice(1, 1) // [1, 3] 真删除、前移、长度变 2 ✅
|
||||
// ┌──────────────┬──────────────────┬────────────────┐
|
||||
// │ 目标 │ delete 能用吗 │ 该用什么 │
|
||||
// ├──────────────┼──────────────────┼────────────────┤
|
||||
// │ 对象属性 │ ✅ 正解 │ delete obj.key │
|
||||
// │ 数组元素 │ ⚠️ 留空洞,别用 │ splice │
|
||||
// │ 变量/原始值 │ ❌ 无效 │ —— │
|
||||
// └──────────────┴──────────────────┴────────────────┘
|
||||
//
|
||||
// 4. 键不存在 → undefined(不报错)⭐
|
||||
// const p = { name: 'Tom' }
|
||||
// p.height // undefined ← 没这个键,返回 undefined,不报错
|
||||
// 可以用它判断"有没有某个键":
|
||||
// if (p.name) … // name 有值 → 真
|
||||
// if (p.height) … // undefined 是假 → 不执行
|
||||
//
|
||||
// 5. 常用工具:Object.keys / values / entries ⭐
|
||||
// 这些是静态方法(复习 00-concepts/call-forms.md:数据传进去):
|
||||
// const p = { name: 'Tom', age: 20 }
|
||||
// Object.keys(p) // ['name', 'age'] ← 所有键
|
||||
// Object.values(p) // ['Tom', 20] ← 所有值
|
||||
// Object.entries(p) // [['name','Tom'], ['age',20]] ← 键值对数组
|
||||
// 注意是 Object.keys(p)(大写 Object,p 传进去),不是 p.keys()。
|
||||
//
|
||||
// 6. 检查键是否存在
|
||||
// 'name' in p // true ← in 运算符
|
||||
// 'age' in p // false
|
||||
// p.hasOwnProperty('name') // true
|
||||
//
|
||||
// 7. 对象里放函数 = "方法"
|
||||
// 值如果是函数,就叫这个对象的方法:
|
||||
// const person = {
|
||||
// name: 'Tom',
|
||||
// greet: function () {
|
||||
// return 'Hi, I am ' + this.name // this 指这个对象自己
|
||||
// },
|
||||
// }
|
||||
// person.greet() // 'Hi, I am Tom'
|
||||
// this 指"调用这个方法的对象"。这里 person.greet() 里的 this 就是 person。
|
||||
// (this 是个大话题,后面深入;现在知道"this = 当前对象"即可)
|
||||
//
|
||||
// 8. 数组 + 对象 = 最常见的数据形状 ⭐
|
||||
// 真实数据几乎都是"对象数组":
|
||||
// const users = [
|
||||
// { name: 'Tom', age: 20 },
|
||||
// { name: 'Jerry', age: 18 },
|
||||
// ]
|
||||
// users[0].name // 'Tom' ← 先取第0个对象,再取它的 name
|
||||
// users[1].age // 18
|
||||
// 这就是后端返回的 JSON、后面高阶函数处理的典型数据。记住"数组套对象"的形状。
|
||||
//
|
||||
// ✅ 本主题要点回顾
|
||||
// 1. 对象 = {键: 值} 键值对;表示真实事物
|
||||
// 2. 取值:.键(固定名)/ [变量](键名是变量)⭐
|
||||
// 3. 增改直接赋值,删用 delete;是引用类型可原地改
|
||||
// 4. 键不存在返回 undefined(不报错)⭐
|
||||
// 5. Object.keys/values/entries(obj) 拿键/值/键值对 ⭐
|
||||
// 6. 对象里的函数 = 方法,用 this 指自己
|
||||
// 7. "对象数组"是最常见的数据形状 ⭐
|
||||
|
||||
// ═════ 示范 ═════
|
||||
|
||||
// ── 1. 创建 & 结构 ───────────────────────────────
|
||||
const person = {
|
||||
name: 'Asabeneh',
|
||||
age: 100,
|
||||
isMarried: true,
|
||||
skills: ['HTML', 'CSS', 'JS'],
|
||||
}
|
||||
console.log(person) // 整个对象
|
||||
|
||||
// ── 2. 取值:点号 vs 方括号 ⭐ ────────────────────
|
||||
console.log(person.name) // Asabeneh
|
||||
console.log(person['age']) // 100
|
||||
const key = 'isMarried'
|
||||
console.log(person[key]) // true ← 键名在变量里,只能用方括号
|
||||
console.log(person.skills[0]) // HTML ← 值是数组,继续用下标
|
||||
|
||||
// ── 3. 增 / 改 / 删 ──────────────────────────────
|
||||
const p = { name: 'Tom' }
|
||||
p.age = 20 // 增
|
||||
p.name = 'Jerry' // 改
|
||||
delete p.age // 删
|
||||
console.log(p) // { name: 'Jerry' }
|
||||
|
||||
// ── 4. 键不存在 → undefined ⭐ ────────────────────
|
||||
console.log(person.height) // undefined ← 不报错
|
||||
|
||||
// ── 5. Object.keys / values / entries ⭐ ─────────
|
||||
const q = { name: 'Tom', age: 20 }
|
||||
console.log(Object.keys(q)) // ['name', 'age']
|
||||
console.log(Object.values(q)) // ['Tom', 20]
|
||||
console.log(Object.entries(q)) // [['name','Tom'], ['age',20]]
|
||||
|
||||
// ── 6. 检查键是否存在 ────────────────────────────
|
||||
console.log('name' in q) // true
|
||||
console.log('height' in q) // false
|
||||
|
||||
// ── 7. 对象里的方法 + this ───────────────────────
|
||||
const dog = {
|
||||
name: 'WangCai',
|
||||
bark: function () {
|
||||
return this.name + ' says woof'
|
||||
},
|
||||
}
|
||||
console.log(dog.bark()) // WangCai says woof
|
||||
|
||||
// ── 8. 对象数组(最常见形状)⭐ ──────────────────
|
||||
const users = [
|
||||
{ name: 'Tom', age: 20 },
|
||||
{ name: 'Jerry', age: 18 },
|
||||
]
|
||||
console.log(users[0].name) // Tom
|
||||
console.log(users[1].age) // 18
|
||||
|
||||
// ═════ 轮到你 ═════
|
||||
|
||||
// 👇 轮到你(只给要求,自己写代码)
|
||||
|
||||
// 练习 A:创建一个对象 book,包含:
|
||||
// title: 'JavaScript', pages: 300, published: true
|
||||
// 然后分别用【点号】打印 title,用【方括号】打印 pages
|
||||
|
||||
const book = {
|
||||
title: "JavaScript",
|
||||
pages: 64,
|
||||
published: true
|
||||
|
||||
}
|
||||
console.log(book.title)
|
||||
console.log(book['pages'])
|
||||
|
||||
|
||||
// 练习 B:接着上面的 book
|
||||
// ① 给它增加一个新键 author,值是你的名字
|
||||
// ② 把 pages 改成 350
|
||||
// ③ 删除 published 这个键
|
||||
// ④ 打印最终的 book
|
||||
|
||||
book['author'] = "author"
|
||||
delete book['published']
|
||||
console.log(book)
|
||||
|
||||
|
||||
// 练习 C:有 const car = { brand: 'Tesla', color: 'red', year: 2024 }
|
||||
// ① 用 Object.keys 打印所有键
|
||||
// ② 用 Object.values 打印所有值
|
||||
// ③ 判断 car 里有没有 'price' 这个键(用 in)
|
||||
const cat = { brand: 'Tesla', color: 'red', year: 2024 }
|
||||
console.log(Object.keys(cat))
|
||||
console.log(Object.values(cat))
|
||||
console.log("price" in cat)
|
||||
|
||||
|
||||
|
||||
// 练习 D:有 const student = { name: 'Amy', grade: 90 }
|
||||
// 给它加一个方法 describe,调用时返回 'Amy: 90'
|
||||
// (提示:方法里用 this.name 和 this.grade)
|
||||
// 写完调用它并打印
|
||||
|
||||
// ❌
|
||||
// const student = { name: 'Amy', grade: 90, describe: () => {
|
||||
// return this.name + ": " + this.grade
|
||||
// }}
|
||||
|
||||
|
||||
const student = {
|
||||
name: 'Amy',
|
||||
grade: 90,
|
||||
describe: function() {
|
||||
return this.name + ": " + this.grade
|
||||
}
|
||||
}
|
||||
console.log(student.describe())
|
||||
|
||||
|
||||
// 练习 E(挑战):有下面这个对象数组
|
||||
// const people = [
|
||||
// { name: 'Tom', age: 20 },
|
||||
// { name: 'Jerry', age: 18 },
|
||||
// { name: 'Spike', age: 25 },
|
||||
// ]
|
||||
// 打印出第 2 个人(Jerry)的年龄,以及第 3 个人的名字。
|
||||
const people = [
|
||||
{ name: 'Tom', age: 20 },
|
||||
{ name: 'Jerry', age: 18 },
|
||||
{ name: 'Spike', age: 25 },
|
||||
]
|
||||
console.log(people[1].age + "\n" + people[2].name)
|
||||
@ -0,0 +1,138 @@
|
||||
// 08 · Set 与 Map
|
||||
// 学练复习一体:概念总结 → 示范 → 轮到你
|
||||
// 跑法:Quokka / 右上角 ▷ / node 本文件
|
||||
// 示范区带预期输出;轮到你区只给要求,自己写
|
||||
|
||||
// ═════ 概念总结 ═════
|
||||
//
|
||||
// Set 和 Map 是对"数组/对象"的补充。数组允许重复、对象的键只能是字符串 ——
|
||||
// 这两个新结构补上了这两个短板。
|
||||
//
|
||||
// 1. Set —— 不重复的集合 ⭐
|
||||
// Set 里的值自动去重,不会有重复项。
|
||||
// const s = new Set([1, 2, 2, 3, 3, 3])
|
||||
// console.log(s) // Set(3) {1, 2, 3} ← 重复的自动没了
|
||||
// 常用操作:
|
||||
// const s = new Set()
|
||||
// s.add(1) // 加
|
||||
// s.add(2)
|
||||
// s.add(1) // 重复的,无效
|
||||
// s.has(1) // true ← 查是否存在
|
||||
// s.delete(2) // 删
|
||||
// s.size // 1 ← 数量(注意是 size,不是 length!)
|
||||
// ⚠️ Set/Map 用 .size 数数量,不是数组的 .length。
|
||||
// 最常见用途:数组去重 ⭐⭐
|
||||
// const arr = [1, 1, 2, 3, 3]
|
||||
// const unique = [...new Set(arr)] // [1, 2, 3]
|
||||
// // ↑ 转成 Set 去重,再用 ... 展开回数组
|
||||
// 这是一个必会的惯用法:[...new Set(数组)] = 数组去重。
|
||||
//
|
||||
// 2. Map —— 更强的键值对 ⭐
|
||||
// Map 像对象,但键可以是任何类型(对象的键只能是字符串/symbol)。
|
||||
// const m = new Map()
|
||||
// m.set('name', 'Tom') // set(键, 值)
|
||||
// m.set(1, 'number key') // 键可以是数字!
|
||||
// m.set(true, 'bool key') // 甚至布尔、对象
|
||||
// m.get('name') // 'Tom' ← 用 get 取值
|
||||
// m.has(1) // true
|
||||
// m.delete(true)
|
||||
// m.size // 2
|
||||
// Map vs 对象:什么时候用哪个
|
||||
// ┌──────────┬──────────────────────────┬──────────────────────┐
|
||||
// │ │ 对象 {} │ Map │
|
||||
// ├──────────┼──────────────────────────┼──────────────────────┤
|
||||
// │ 键的类型 │ 只能字符串/symbol │ 任意类型 │
|
||||
// │ 数量 │ Object.keys(o).length │ m.size(直接) │
|
||||
// │ 顺序 │ 不保证 │ 保证插入顺序 │
|
||||
// │ 遍历 │ 需 Object.keys 等 │ 直接可迭代 │
|
||||
// └──────────┴──────────────────────────┴──────────────────────┘
|
||||
// 日常大多数场景用对象就够;需要"非字符串键""频繁增删""保序"时用 Map。
|
||||
//
|
||||
// 3. 创建时初始化
|
||||
// const s = new Set(['a', 'b', 'c']) // Set:传数组
|
||||
// const m = new Map([ // Map:传"键值对数组"(和 Object.entries 形状一样!)
|
||||
// ['name', 'Tom'],
|
||||
// ['age', 20],
|
||||
// ])
|
||||
// m.get('age') // 20
|
||||
// 呼应 07 章:Object.entries(obj) 返回的正是这种 [[k,v],...] 形状,
|
||||
// 所以 new Map(Object.entries(obj)) 能把对象转成 Map。
|
||||
//
|
||||
// 4. 遍历(先知道,循环章会详讲)
|
||||
// const s = new Set([1, 2, 3])
|
||||
// for (const x of s) console.log(x) // 1 2 3
|
||||
// const m = new Map([['a', 1], ['b', 2]])
|
||||
// for (const [k, v] of m) console.log(k, v) // a 1 / b 2
|
||||
//
|
||||
// ✅ 本主题要点回顾
|
||||
// 1. Set = 不重复集合;add/has/delete/size ⭐
|
||||
// 2. [...new Set(arr)] = 数组去重(必会惯用法)⭐⭐
|
||||
// 3. Map = 键可任意类型的键值对;set/get/has/delete/size ⭐
|
||||
// 4. Set/Map 数数量用 .size,不是 .length
|
||||
// 5. Map 保证插入顺序;需非字符串键/保序/频繁增删时用 Map,否则对象够用
|
||||
|
||||
// ═════ 示范 ═════
|
||||
|
||||
// ── 1. Set 自动去重 ⭐ ────────────────────────────
|
||||
const s = new Set([1, 2, 2, 3, 3, 3])
|
||||
console.log(s) // Set(3) {1, 2, 3}
|
||||
|
||||
// ── 2. Set 常用操作 ──────────────────────────────
|
||||
const s2 = new Set()
|
||||
s2.add(1)
|
||||
s2.add(2)
|
||||
s2.add(1) // 重复,无效
|
||||
console.log(s2.has(1)) // true
|
||||
s2.delete(2)
|
||||
console.log(s2.size) // 1 ← 注意是 size 不是 length
|
||||
|
||||
// ── 3. 数组去重惯用法 ⭐⭐ ─────────────────────────
|
||||
const arr = [1, 1, 2, 3, 3]
|
||||
console.log([...new Set(arr)]) // [1, 2, 3]
|
||||
|
||||
// ── 4. Map:键可任意类型 ⭐ ────────────────────────
|
||||
const m = new Map()
|
||||
m.set('name', 'Tom')
|
||||
m.set(1, 'number key')
|
||||
console.log(m.get('name')) // Tom
|
||||
console.log(m.get(1)) // number key
|
||||
console.log(m.has(1)) // true
|
||||
console.log(m.size) // 2
|
||||
|
||||
// ── 5. Map 初始化(键值对数组)───────────────────
|
||||
const m2 = new Map([
|
||||
['name', 'Tom'],
|
||||
['age', 20],
|
||||
])
|
||||
console.log(m2.get('age')) // 20
|
||||
|
||||
// ── 6. 遍历 ──────────────────────────────────────
|
||||
for (const x of new Set([1, 2, 3])) console.log('set item:', x) // 1,2,3
|
||||
for (const [k, v] of m2) console.log('map:', k, v) // name Tom / age 20
|
||||
|
||||
// ═════ 轮到你 ═════
|
||||
|
||||
// 👇 轮到你(只给要求,自己写代码)
|
||||
|
||||
// 练习 A:有 const nums = [5, 3, 5, 2, 3, 1, 1]
|
||||
// ① 用 Set 给它去重,得到一个新数组并打印
|
||||
// ② 打印去重后有几个元素
|
||||
|
||||
// 练习 B:创建一个空 Set,叫 tags
|
||||
// ① 依次 add 'js', 'css', 'js'(注意第二个 js 重复)
|
||||
// ② 打印 tags 的 size(想想是几)
|
||||
// ③ 判断里面有没有 'css'
|
||||
|
||||
// 练习 C:创建一个 Map,叫 scores
|
||||
// ① 存入:'Amy' → 90,'Bob' → 85
|
||||
// ② 取出并打印 Amy 的分数
|
||||
// ③ 打印 scores 里有几对(用 size)
|
||||
|
||||
// 练习 D(动脑):有一句话 const text = 'a b a c b a'
|
||||
// 数一数里面有几个【不重复】的字母(空格不算)。
|
||||
// 提示:先 split 成数组,想办法过滤掉空格,再用 Set 去重数 size。
|
||||
// (答案应该是 3:a b c)
|
||||
|
||||
// 练习 E(挑战):有 const obj = { name: 'Tom', age: 20 }
|
||||
// 把这个【对象】转成【Map】,然后打印 Map 的 size。
|
||||
// 提示:回忆 07 章的 Object.entries,它的返回形状正好能喂给 new Map()
|
||||
@ -0,0 +1,117 @@
|
||||
// 09 · 循环(Loops)
|
||||
// 跑法:Quokka 或 右上角 ▷ / node js_notes/09-loops.js
|
||||
//
|
||||
// ═════════════════════ 概念总结 ═════════════════════
|
||||
//
|
||||
// 循环 = 重复做一件事。JS 有好几种,按场景选:
|
||||
//
|
||||
// 循环种类 用途 建议
|
||||
// ─────────────────────────────────────────────────
|
||||
// for 经典计数循环,能控制下标 需要下标时
|
||||
// while 条件为真就一直循环 不知道次数、靠条件停
|
||||
// do...while 先做一次再判断 至少执行一次
|
||||
// for...of 遍历【值】(数组/字符串/Set) ⭐ 最常用,遍历元素
|
||||
// for...in 遍历【键】(对象的 key) 遍历对象属性
|
||||
//
|
||||
// ── 1. for:三段式 ⭐ ────────────────────────────
|
||||
// for (初始; 条件; 每轮结束后) { ... }
|
||||
// for (let i = 0; i < 5; i++) → i 从 0 到 4
|
||||
// i++ 是 i = i + 1 的简写
|
||||
//
|
||||
// ── 2. for...of:遍历值(最常用)⭐⭐ ─────────────
|
||||
// for (const x of [10,20,30]) → x 依次是 10,20,30
|
||||
// 直接拿到【元素本身】,不用管下标。数组/字符串/Set/Map 都能用。
|
||||
//
|
||||
// ── 3. for...in:遍历对象的键 ────────────────────
|
||||
// for (const key in obj) → key 依次是对象的每个键名
|
||||
// 注意:for...in 拿到的是【键】,要值得用 obj[key]
|
||||
//
|
||||
// ⚠️ 口诀:of 拿值(value),in 拿键/下标(index/key)
|
||||
// ⚠️ 别用 for...in 遍历数组(会有意外的坑),数组用 for...of 或 for
|
||||
//
|
||||
// ── 4. while / do...while ───────────────────────
|
||||
// while (条件) { ... } 条件先判断,可能一次都不跑
|
||||
// do { ... } while (条件) 先跑一次再判断,至少跑一次
|
||||
// ⚠️ 循环体里必须有让条件最终变假的语句,否则【死循环】
|
||||
//
|
||||
// ── 5. break / continue ⭐ ──────────────────────
|
||||
// break 立刻【跳出】整个循环
|
||||
// continue 跳过【本轮】剩下的,直接进下一轮
|
||||
//
|
||||
// ═════ 要点回顾 ═════
|
||||
// 1. for 需要下标;for...of 遍历值(最常用);for...in 遍历对象键
|
||||
// 2. of 拿值,in 拿键 —— 别记反 ⭐
|
||||
// 3. 数组别用 for...in
|
||||
// 4. while 靠条件停,当心死循环
|
||||
// 5. break 跳出整个循环,continue 跳过本轮 ⭐
|
||||
//
|
||||
// ═════════════════════ 示范 ═════════════════════
|
||||
|
||||
// ── 1. for 经典计数 ──────────────────────────────
|
||||
for (let i = 0; i < 3; i++) {
|
||||
console.log('for i =', i) // 0, 1, 2
|
||||
}
|
||||
|
||||
// 用 for + 下标遍历数组
|
||||
const arr = ['a', 'b', 'c']
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
console.log(i, arr[i]) // 0 a / 1 b / 2 c
|
||||
}
|
||||
|
||||
// ── 2. for...of 遍历值(最常用)⭐ ─────────────────
|
||||
for (const fruit of ['apple', 'banana']) {
|
||||
console.log('of:', fruit) // apple / banana ← 直接拿到值
|
||||
}
|
||||
for (const ch of 'Hi') {
|
||||
console.log('char:', ch) // H / i ← 字符串也能遍历
|
||||
}
|
||||
|
||||
// ── 3. for...in 遍历对象的键 ─────────────────────
|
||||
const person = { name: 'Tom', age: 20 }
|
||||
for (const key of Object.keys(person)) {
|
||||
console.log('key(of):', key) // name / age
|
||||
}
|
||||
for (const key in person) {
|
||||
console.log('in:', key, '=', person[key]) // name = Tom / age = 20
|
||||
}
|
||||
|
||||
// ── 4. while ─────────────────────────────────────
|
||||
let n = 3
|
||||
while (n > 0) {
|
||||
console.log('while:', n) // 3, 2, 1
|
||||
n-- // ← 必须让 n 变化,否则死循环
|
||||
}
|
||||
|
||||
// ── 5. break / continue ──────────────────────────
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (i === 3) break // i 到 3 就整个跳出
|
||||
console.log('break demo:', i) // 0, 1, 2
|
||||
}
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (i % 2 === 0) continue // 偶数跳过本轮
|
||||
console.log('continue demo:', i) // 1, 3 ← 只打印奇数
|
||||
}
|
||||
|
||||
// ═════════════════════ 轮到你 ═════════════════════
|
||||
// (只给要求,自己写代码)
|
||||
|
||||
// 练习 A:用 for 循环打印 1 到 5(注意:是 1 到 5,不是 0 到 4)
|
||||
|
||||
// 练习 B:有 const nums = [4, 7, 2, 9]
|
||||
// 用 for...of 遍历,把每个数字打印出来
|
||||
|
||||
// 练习 C:有 const car = { brand: 'Tesla', year: 2024, color: 'red' }
|
||||
// 用 for...in 遍历,按 "键: 值" 的格式打印每一项
|
||||
// (例如 brand: Tesla)
|
||||
|
||||
// 练习 D:用 for 循环算出 1 + 2 + 3 + ... + 100 的总和,打印结果
|
||||
// 提示:在循环外先定义一个 let sum = 0,每轮把 i 加进去
|
||||
// (答案应该是 5050)
|
||||
|
||||
// 练习 E(动脑):有 const nums = [3, 8, 1, 10, 5]
|
||||
// 用循环找出里面【最大】的数并打印。
|
||||
// 提示:先假设第一个是最大(let max = nums[0]),
|
||||
// 遍历时遇到更大的就更新 max。
|
||||
|
||||
// 练习 F(挑战):打印 1 到 20,但遇到偶数就跳过(用 continue),
|
||||
// 只打印奇数。
|
||||
@ -0,0 +1,91 @@
|
||||
# 📚 我的 JavaScript 学习笔记
|
||||
|
||||
跟着 `30-Days-Of-JavaScript` 教程学,但**按知识体系整理**,方便日后复习。
|
||||
开始于 2026-07。
|
||||
|
||||
---
|
||||
|
||||
## 🧭 怎么用这个目录
|
||||
|
||||
- 每个主题就是**根目录下一个 `编号-主题.js` 单文件**(如 `08-sets-maps.js`),
|
||||
学练复习一体,内含三段:
|
||||
1. **概念总结区**(文件顶部注释,含 ASCII 表格)—— 复习先看这里
|
||||
2. **示范区** —— 带预期输出的可运行代码
|
||||
3. **轮到你区** —— 练习题,只给要求不给答案
|
||||
用 `// ═════ 概念总结 ═════` / `// ═════ 示范 ═════` / `// ═════ 轮到你 ═════` 分隔。
|
||||
- 编号(`01-`, `02-`…)只表示**推荐学习顺序**,不是"第几天"
|
||||
- 例外:`00-concepts/` 仍是文件夹(跨主题通用概念一组,多为 `.md`)
|
||||
|
||||
> 📌 结构演变:早期用 `NN-topic/`(内含 `notes.md`+`practice.js`)分开两文件,
|
||||
> 现已扁平化为**根目录单文件 `NN-topic.js`**(概念写进代码注释),减少重复、复习更快。
|
||||
|
||||
### 运行代码的三种方式
|
||||
|
||||
| 方式 | 怎么做 | 适合 |
|
||||
|---|---|---|
|
||||
| **Quokka(免费版)** | `Cmd+Shift+P` → `Quokka: Start on Current File` | 边写边看 `console.log` 的值(纯逻辑练习) |
|
||||
| **Code Runner ▷** | 右上角 ▷,或 `Ctrl+Alt+N` | 在终端跑整个文件 |
|
||||
| **浏览器 + F12** | 打开 `index.html`,看 Console | 涉及 `document`/DOM 的代码 |
|
||||
|
||||
> ⚠️ 判断能不能用 ▷/Quokka 跑:文件**自包含**(自己定义自己用、无 `document`)就能跑;
|
||||
> 依赖别的文件的变量、或含 `document` 的,要用**浏览器**。
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ 知识地图(带进度)
|
||||
|
||||
### 〇、通用概念(跨主题,随时查)
|
||||
- `00-concepts/my-mistakes.md` —— 🕳️ **我的踩坑记录**(复习先看这个!含报错秒查表)⭐
|
||||
- `00-concepts/call-forms.md` —— 三种调用形式:`值.方法()` vs `函数()` vs `类.方法()`
|
||||
- `00-concepts/value-vs-reference.md` —— 值 vs 引用:复制 vs 共享、字符串不可变 ⭐
|
||||
- `00-concepts/shortcircuit-bash-vs-js.md` —— `&&`/`||`:bash vs JS(`0` 真假相反)
|
||||
|
||||
### 一、基础与数据
|
||||
- [x] `01-basics` 基础:输出、注释、表达式、字符串引号、运行方式
|
||||
- [x] `02-variables` 变量与数据类型
|
||||
- [x] `03-operators` 运算符
|
||||
- [x] `04-strings` 字符串(常用方法)
|
||||
- [x] `05-conditionals` 条件与布尔
|
||||
|
||||
### 二、数据结构
|
||||
- [x] `06-arrays` 数组
|
||||
- [x] `07-objects` 对象
|
||||
- [x] `08-sets-maps` Set 与 Map
|
||||
|
||||
### 三、逻辑与函数
|
||||
- [x] `09-loops` 循环
|
||||
- [ ] `10-functions` 函数
|
||||
- [ ] `11-higher-order-functions` 高阶函数(map / filter / reduce)
|
||||
- [ ] `12-destructuring-spread` 解构与展开
|
||||
- [ ] `13-closures` 闭包
|
||||
|
||||
### 四、进阶
|
||||
- [ ] `14-classes` 类
|
||||
- [ ] `15-regex` 正则表达式
|
||||
- [ ] `16-error-handling` 错误处理
|
||||
- [ ] `17-json` JSON
|
||||
- [ ] `18-promises-async` 异步(Promise / async-await)
|
||||
|
||||
### 五、浏览器与项目
|
||||
- [ ] `19-dom` DOM 操作
|
||||
- [ ] `20-events` 事件监听
|
||||
- [ ] `21-web-storage` 本地存储
|
||||
- [ ] `22-projects` 综合项目
|
||||
|
||||
---
|
||||
|
||||
## 📏 练习规矩(给协作者/AI)
|
||||
|
||||
- **练习题让用户自己改**,不要替他写答案。
|
||||
- **出题不写答案**:"轮到你"区域只给要求,不放正确答案(哪怕注释掉也不行,会剧透)。
|
||||
- **提示只给方向,不给完整写法**:如 `raw.trim().toLowerCase()` 这种整句提示等于给答案,不行;
|
||||
应改成"两个方法可以链式连写"这种点思路的说法。
|
||||
- 示范区(讲解用的例子)可带预期输出;题目区不带。
|
||||
- **用户每犯一个新错,吸收进 `00-concepts/my-mistakes.md`**(错法→现象→正解→教训),
|
||||
并带 **📍来源索引**(原题文件+练习号、相关笔记章节),方便跳回原题。复习时才记得住坑在哪。
|
||||
|
||||
## ✍️ 命名约定
|
||||
|
||||
- 文件夹:`编号-英文主题`,如 `06-arrays`
|
||||
- 笔记:`notes.md`;练习:`practice.js`
|
||||
- 一个主题内容多时,练习可拆成 `practice_01_xxx.js` 等
|
||||
@ -0,0 +1,25 @@
|
||||
// 🐿️ Quokka 试验场(免费版写法)
|
||||
// 免费版不支持 //? ,但支持实时 console.log —— 值会显示在行尾
|
||||
// 用法:Cmd+Shift+P → "Quokka: Start on Current File"
|
||||
// 然后随便改数字/代码,右边的值会实时变化,不用保存、不用切终端
|
||||
|
||||
// 1) 基本变量
|
||||
const a = 2
|
||||
const b = 3
|
||||
console.log(a + b) // 实时显示 5
|
||||
|
||||
// 2) 字符串
|
||||
const name = 'JavaScript'
|
||||
console.log(name.length) // 10
|
||||
console.log(name.toUpperCase()) // 'JAVASCRIPT'
|
||||
|
||||
// 3) 数组方法(练前 20 天最常玩的东西)
|
||||
const nums = [1, 2, 3, 4, 5]
|
||||
console.log(nums.map((n) => n * 2)) // [2, 4, 6, 8, 10]
|
||||
console.log(nums.filter((n) => n % 2 === 0)) // [2, 4]
|
||||
console.log(nums.reduce((sum, n) => sum + n, 0)) // 15
|
||||
|
||||
// 4) 试试改这里的值,看右边跟着变 👇
|
||||
let radius = 5
|
||||
const area = Math.PI * radius ** 2
|
||||
console.log(area) // 改 radius 试试,这个值会实时跟着变
|
||||
Loading…
Reference in new issue