JS学习记录
2025.10.28 打卡
2025.10.28 打卡
1. 运行环境
直接在PC上创建好HTML和JavaScript文件,然后用浏览器打开,不一定看得到效果。这种方式运行部分JavaScript代码没有问题,但由于浏览器的安全限制,以file://开头的地址无法执行如联网等JavaScript代码,最终,你还是需要架设一个Web服务器,然后以http://开头的地址来正常执行所有JavaScript代码。
2. 基本语法
JS不要求语句结束一定要加分号,理论上JS引擎会自动添加,但是有时候可能会出问题,还是加上比较好。
跟C++一样,可以只写一个表达式作为一个语句(尽管这没有意义)。
字符串可以用''或者""包起来,复合语句也是用{}包起来,注释也是//或者/**/。
JS区分大小写。
3. 数据类型
Number:JS中不区分浮点数和整数,二者统一用Number类型表示。所以浮点数和整数可以直接比较。甚至浮点数都可以取余。
一些特殊的数值:
1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
0x...表示16进制数,0o...表示8进制数,注意8进制数不用0...表示,这样有时候会变成8进制数,有时候会变成10进制数,可以开启strict模式禁止单独0开头的数字。
"use strict";
-
字符串、布尔值:和C++一样,字符串还可以用单引号表示。
-
比较运算:实际上JS允许不同数据类型作比较。对于等于判定,
===会先看数据类型是否一致,一致才判断,否则一律返回false。
false == 0; // true
false === 0; // false
NaN这个特殊的Number与所有其他值都不相等,包括它自己:
NaN === NaN; // false
唯一能判断NaN的方法是通过isNaN()函数:
isNaN(NaN); // true
-
高精度整数:
BigInt,可以在常数后面加一个n,比如12345n,也可以用构造函数把Number和字符串转成BigInt,比如BigInt(123)。 -
空值:
null表示空值,它不表示任何东西。还有一个undefined,表示值未定义,区分两者意义不大。通常用null,undefined仅仅在判断函数参数是否传递的情况下有用。 -
数组,语法和C++一样,用中括号表示,但是每个元素的类型不一定要相同。
-
变量需要定义。
console.log(x);可以显示x的值。
期初JS的变量不用声明,自动变成全局变量。开启strict模式可以让所有变量强制使用var声明,不然报错。
- 关于作用域
var声明的变量类似python,只有函数内部变量和全局变量之分,复合语句(比如循环)内部的变量的块级作用域不存在。
let和const声明的变量类似C++,有块级作用域。
4. 字符串
类似'和"都可以表示,反引号 ` 可以表示。(markdown用更多反引号包裹反引号来显示)。

在 JavaScript 里,undefined 和 null 都表示“没有值”,但类型不同(undefined 是 Undefined 类型,null 是 Null 类型)。
根据“要精确”还是“要宽松”,有 4 种常见写法:
✅ 1. 只关心“是不是空值”(最常用)
if (value == null) {
// 进入这里的只有两种可能:null 或 undefined
// 其他 falsy 值(0、''、false、NaN)都不会进来
}
== null是语法级语法糖,严格等价于value === undefined || value === null,但短得多。
✅ 2. 要区分 undefined 和 null
if (value === undefined) {
// 真的是 undefined
}
if (value === null) {
// 真的是 null
}
✅ 3. typeof 判断 undefined(兼容未声明变量)
if (typeof value === 'undefined') {
// 不会抛 ReferenceError,即使 value 都没声明
}
注意:
typeof null返回'object',这是历史遗留 bug,不能用来判 null。
✅ 4. 可选链 / 空值合并(ES2020+)
const safe = value ?? '默认值'; // 仅当 undefined 或 null 时取默认值
const deep = obj?.a?.b; // 遇到 undefined/null 立即返回 undefined,不报错
✅ 一句话速记
- 只想过滤“空值”:
if (value == null) … - 必须区分两者:
=== undefined/=== null - 防止变量未声明:
typeof value === 'undefined'
错因一句话:
arr.map(parseInt) 等价于
['1','2','3'].map(function(item, index, array){
return parseInt(item, index); // 第二个参数被当成“进制”
});
parseInt 的第二个参数是 radix(进制),而 map 回调的第二个参数是 当前索引。
于是实际调用变成:
| 轮次 | 调用 | 结果 |
|---|---|---|
| 0 | parseInt('1', 0) |
1 (0 被当作 10) |
| 1 | parseInt('2', 1) |
NaN (没有 1 进制) |
| 2 | parseInt('3', 2) |
NaN (3 超出 2 进制范围) |
✅ 正确写法
只把字符串传进去,把进制固定成 10:
r = arr.map(Number); // [1, 2, 3]
// 或
r = arr.map(s => parseInt(s, 10)); // [1, 2, 3]
// 或
r = arr.map(s => +s); // [1, 2, 3]
小结
map(parseInt) 是经典面试坑:
parseInt 要吃两个参数,而 map 会塞三个参数给它。
什么神人特性
你指出了一个非常经典的问题!这个例子完美展示了 let 和 var 在循环中创建闭包时的关键区别。让我们拆解一下:
function count() {
let arr = [];
for (let i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
const funcs = count();
console.log(funcs); // 1
console.log(funcs); // 4
console.log(funcs); // 9
为什么它能正常工作?关键在 let!
-
块级作用域:
let为每次循环创建一个新的作用域- 相当于每次循环都重新声明一个
i:
{ // 循环1 let i = 1; arr.push(function() { return i*i }); // 捕获 i=1 } { // 循环2 let i = 2; arr.push(function() { return i*i }); // 捕获 i=2 } { // 循环3 let i = 3; arr.push(function() { return i*i }); // 捕获 i=3 } -
闭包捕获的是"瞬间快照":
- 每个函数捕获的是当时那个循环的
i值 - 就像给每个循环拍了张照片保存起来
- 每个函数捕获的是当时那个循环的
🆚 对比使用 var 的灾难版本:
function brokenCount() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
const brokenFuncs = brokenCount();
console.log(brokenFuncs); // 16 😱
console.log(brokenFuncs); // 16 😱
console.log(brokenFuncs); // 16 😱
为什么全是16?
-
var没有块级作用域:- 整个循环共享同一个
i - 循环结束时
i已经变成 4(因为i<=3后还会执行i++)
- 整个循环共享同一个
-
闭包捕获的是"活绑定":
- 所有函数都指向同一个
i - 调用时读取的是最终的
i=4 4*4=16
- 所有函数都指向同一个
🔧 闭包在循环中的正确姿势
| 方法 | 示例 | 原理 |
|---|---|---|
使用 let |
for(let i=...) |
每次循环创建新作用域 |
| IIFE 包装 | (function(i){...})(i) |
立即执行创建闭包 |
| 函数工厂 | arr.push(createFn(i)) |
每次传递当前值 |
forEach 方法 |
[1,2,3].forEach(i => {...}) |
每次迭代创建新作用域 |
🌟 终极结论
这个例子展示了闭包 + let 的黄金组合:
- 时间胶囊机制:每个闭包都保存了循环当时的变量状态
- 独立宇宙:每个函数有自己的
i,互不干扰 - 避免污染:不需要全局变量就能保持状态
下次写循环内的闭包时,记住:
🔑 用
let声明循环变量 = 给每个闭包发专属身份证
🚫 用var= 所有闭包共享同一张公交卡
JS的生成器,可以看成一个好多return的函数,return完一次就会停留在这个状态
数字字面量的点号困境
123.toString(); // ❌ SyntaxError: Invalid or unexpected token
123..toString(); // ✅ "123"
(123).toString(); // ✅ "123"
原因分析:
JavaScript 解析器将 123.解释为浮点数(期待小数点后的数字)
第一个点被当作小数点而非属性访问符
解决方案的核心是明确区分数字和小数点
你提到的这个现象,本质上是正则表达式中锚点(^ 和 $)的作用导致的匹配行为变化。我们一步一步拆解:
✅ 原始匹配:js 可以匹配 'jsp'
这是因为正则表达式默认是子串匹配的,只要目标字符串中包含正则表达式所描述的模式,就算匹配成功。
- 正则:
js - 目标字符串:
jsp - 匹配结果:✅ 匹配成功
- 原因:
jsp中包含子串js(前两个字符)
✅ 加上锚点后:^js$ 只能匹配 'js'
这里的 ^ 和 $ 是正则表达式中的锚点(anchors):
^表示行首$表示行尾
所以 ^js$ 表示:整行必须恰好是 js,不能多也不能少。
- 正则:
^js$ - 目标字符串:
jsp - 匹配结果:❌ 不匹配
- 原因:
jsp不是恰好等于js,它多了一个p
🔍 总结:锚点的意义
| 正则表达式 | 匹配含义 | 是否整行匹配 |
|---|---|---|
js |
包含子串 "js" | ❌ |
| `^js | 正则表达式 | 匹配含义 |
| ------------ | ----------- | ---------------- |
js |
包含子串 "js" | ❌ |
| 整行恰好是 "js" | ✅ |
💡 类比理解
你可以把正则表达式想象成一个“搜索”工具:
- 不加锚点:像 Ctrl+F 搜索,只要出现就算命中
- 加锚点:像严格验证,必须从头到尾完全吻合
✅ 举个例子
| 目标字符串 | js 是否匹配 |
^js$ 是否匹配 |
|---|---|---|
"js" |
✅ | ✅ |
"jsp" |
✅ | ❌ |
"xjs" |
✅ | ❌ |
"jsx" |
✅ | ❌ |
如你所见,这不是 JavaScript 或 JSP 的问题,而是正则表达式本身的语法规则。加上 ^...$ 后,就从“模糊包含”变成了“精确整行”。
在 JavaScript 中,JSON.stringify() 的第三个参数用于控制缩进格式。当你使用 ' '(两个空格)作为该参数时,它会实现以下效果:
1. 换行的原因
- 结构化缩进:
JSON.stringify()会自动在 每个键值对、数组元素和嵌套结构之间插入换行符(\n),使 JSON 以树状结构展示。 - 缩进字符:
' '指定了每一级缩进使用两个空格。每深入一层嵌套,就会多添加一次缩进字符串。
2. 示例对比
假设对象 xiaoming 如下:
const xiaoming = { name: "小明", age: 14, skills: ["JavaScript", "CSS"] };
-
无缩进参数(紧凑无换行):
JSON.stringify(xiaoming); // 输出: {"name":"小明","age":14,"skills":["JavaScript","CSS"]} -
带空格缩进(自动换行 + 缩进):
JSON.stringify(xiaoming, null, ' ');输出:
{ "name": "小明", "age": 14, "skills": [ "JavaScript", "CSS" ] }
3. 空格的作用
' '中的空格本身 不会导致换行,而是作为缩进单位。- 换行行为由
JSON.stringify()内部逻辑触发:当检测到对象/数组的层级变化时,自动插入\n并追加对应层级的缩进字符串(例如两个空格)。
4. 设计目的
这种设计是为了生成 人类可读的格式化 JSON。通过换行和缩进,可以清晰展示数据结构层次,便于调试或日志输出。
总结
空格字符串 ' ' 通过定义缩进单位,与 JSON.stringify() 的内置格式化逻辑(自动换行 + 层级缩进)配合,共同实现了换行效果。本质是方法内部为提升可读性添加了换行符,而非空格本身导致换行。

浙公网安备 33010602011771号