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,表示值未定义,区分两者意义不大。通常用nullundefined仅仅在判断函数参数是否传递的情况下有用。

  • 数组,语法和C++一样,用中括号表示,但是每个元素的类型不一定要相同。

  • 变量需要定义。

console.log(x);可以显示x的值。

期初JS的变量不用声明,自动变成全局变量。开启strict模式可以让所有变量强制使用var声明,不然报错。

  • 关于作用域

var声明的变量类似python,只有函数内部变量和全局变量之分,复合语句(比如循环)内部的变量的块级作用域不存在。

letconst声明的变量类似C++,有块级作用域。

4. 字符串

类似'"都可以表示,反引号 ` 可以表示。(markdown用更多反引号包裹反引号来显示)。

屏幕截图 2025-10-28 193138

在 JavaScript 里,undefinednull 都表示“没有值”,但类型不同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 会塞三个参数给它。

什么神人特性

你指出了一个非常经典的问题!这个例子完美展示了 letvar 在循环中创建闭包时的关键区别。让我们拆解一下:

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

  1. 块级作用域

    • 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
    }
    
  2. 闭包捕获的是"瞬间快照"

    • 每个函数捕获的是当时那个循环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?

  1. var 没有块级作用域

    • 整个循环共享同一个 i
    • 循环结束时 i 已经变成 4(因为 i<=3 后还会执行 i++
  2. 闭包捕获的是"活绑定"

    • 所有函数都指向同一个 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 的黄金组合:

  1. 时间胶囊机制:每个闭包都保存了循环当时的变量状态
  2. 独立宇宙:每个函数有自己的 i,互不干扰
  3. 避免污染:不需要全局变量就能保持状态

下次写循环内的闭包时,记住:

🔑 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() 的内置格式化逻辑(自动换行 + 层级缩进)配合,共同实现了换行效果。本质是方法内部为提升可读性添加了换行符,而非空格本身导致换行。

posted @ 2025-10-22 12:26  Zlc晨鑫  阅读(3)  评论(0)    收藏  举报