diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..95f66ab --- /dev/null +++ b/CLAUDE.md @@ -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 (`(scope): description`). diff --git a/js_notes/00-concepts/call-forms.md b/js_notes/00-concepts/call-forms.md new file mode 100644 index 0000000..136a7d2 --- /dev/null +++ b/js_notes/00-concepts/call-forms.md @@ -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 帮你查最快。 diff --git a/js_notes/00-concepts/my-mistakes.md b/js_notes/00-concepts/my-mistakes.md new file mode 100644 index 0000000..99b7091 --- /dev/null +++ b/js_notes/00-concepts/my-mistakes.md @@ -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 | diff --git a/js_notes/00-concepts/shortcircuit-bash-vs-js.md b/js_notes/00-concepts/shortcircuit-bash-vs-js.md new file mode 100644 index 0000000..8594971 --- /dev/null +++ b/js_notes/00-concepts/shortcircuit-bash-vs-js.md @@ -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 是假**。 diff --git a/js_notes/00-concepts/value-vs-reference.md b/js_notes/00-concepts/value-vs-reference.md new file mode 100644 index 0000000..7533dc0 --- /dev/null +++ b/js_notes/00-concepts/value-vs-reference.md @@ -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]` 通向本体。** diff --git a/js_notes/01-basics.js b/js_notes/01-basics.js new file mode 100644 index 0000000..eda6703 --- /dev/null +++ b/js_notes/01-basics.js @@ -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) diff --git a/js_notes/02-variables.js b/js_notes/02-variables.js new file mode 100644 index 0000000..4cd079d --- /dev/null +++ b/js_notes/02-variables.js @@ -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) diff --git a/js_notes/03-operators.js b/js_notes/03-operators.js new file mode 100644 index 0000000..5cde38d --- /dev/null +++ b/js_notes/03-operators.js @@ -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 + diff --git a/js_notes/04-strings.js b/js_notes/04-strings.js new file mode 100644 index 0000000..64243ac --- /dev/null +++ b/js_notes/04-strings.js @@ -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)) diff --git a/js_notes/05-conditionals.js b/js_notes/05-conditionals.js new file mode 100644 index 0000000..277cc30 --- /dev/null +++ b/js_notes/05-conditionals.js @@ -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') +} diff --git a/js_notes/06-arrays.js b/js_notes/06-arrays.js new file mode 100644 index 0000000..eeea157 --- /dev/null +++ b/js_notes/06-arrays.js @@ -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('-')) \ No newline at end of file diff --git a/js_notes/07-objects.js b/js_notes/07-objects.js new file mode 100644 index 0000000..96a5916 --- /dev/null +++ b/js_notes/07-objects.js @@ -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) \ No newline at end of file diff --git a/js_notes/08-sets-maps.js b/js_notes/08-sets-maps.js new file mode 100644 index 0000000..e471057 --- /dev/null +++ b/js_notes/08-sets-maps.js @@ -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() diff --git a/js_notes/09-loops.js b/js_notes/09-loops.js new file mode 100644 index 0000000..7842bd1 --- /dev/null +++ b/js_notes/09-loops.js @@ -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), +// 只打印奇数。 diff --git a/js_notes/README.md b/js_notes/README.md new file mode 100644 index 0000000..b97ae04 --- /dev/null +++ b/js_notes/README.md @@ -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` 等 diff --git a/js_notes/quokka_playground.js b/js_notes/quokka_playground.js new file mode 100644 index 0000000..d44af11 --- /dev/null +++ b/js_notes/quokka_playground.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 试试,这个值会实时跟着变