JS

declaration

为什么建议使用 let 而不是 var

  1. 主要原因是作用域不同,let 是块级作用域,var 是函数级作用域。
1
2
3
4
5
6
7
if (true) {
var x = 10;
}
console.log(x); // 10 — 块外仍能访问!

for (var i = 0; i < 3; i++) {}
console.log(i); // 3 — 循环变量泄漏到外部
1
2
3
4
5
6
7
if (true) {
let y = 10;
}
console.log(y); // ReferenceError: y is not defined

for (let j = 0; j < 3; j++) {}
console.log(j); // ReferenceError
  1. var 会进行变量提升
1
2
console.log(a); // undefined(不会报错,但值是 undefined)
var a = 5;

等价于

1
2
3
var a;        // 声明被提升
console.log(a); // undefined
a = 5;

let 也有提升,但存在暂时性死区(Temporal Dead Zone, TDZ),在声明前访问会直接报错:

1
2
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 5;
  1. 重复声明

var 允许在同一作用域内重复声明同名变量,后面的会覆盖前面的,且不会报错:

1
2
var name = "Alice";
var name = "Bob"; // 合法,但极易造成意外覆盖

let 在同一作用域内重复声明会直接报错:

1
2
let name = "Alice";
let name = "Bob"; // SyntaxError: Identifier 'name' has already been declared
  1. 全局对象污染

在浏览器中,全局作用域下用 var 声明的变量会成为 window 对象的属性:

1
2
3
4
5
var foo = 1;
console.log(window.foo); // 1 — 污染了全局对象!

let bar = 2;
console.log(window.bar); // undefined — 不会污染全局对象
  1. 循环中的闭包陷阱
1
2
3
4
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出: 3, 3, 3(不是 0, 1, 2)

不只是 js 有这种情况,golang 在 GO 1.22 之前存在闭包陷阱,具体原因是引用了同一块地址的值,后续修复的办法是每次循环都会重新创建一块地址,这样就不会三个地方都引用同一个地址,然后导致输出同一个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"time"
)

func main() {
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // Go 1.22+:每次迭代 i 都是新变量
}()
}
time.Sleep(time.Second)
}

// Go 1.22+ 输出(顺序不定):
// 0
// 1
// 2

// Go 1.21 及之前输出:
// 3
// 3
// 3

为什么要有 null 和 undefined

undefined 是表示存在有这个变量,但是还没有开始赋值。

null 代表是直接没有这个值

JS 的 WAT 故事

1
2
3
4
[] + []           // ""
[] + {} // "[object Object]"
{} + [] // 0 ({}被解析为代码块)
{} + {} // NaN (同上)
1
2
3
4
5
6
7
8
[1,2,3] + [4,5,6]      // "1,2,34,5,6"  (转字符串后拼接)
[1,2,3] * 2 // NaN
[10] - [5] // 5 ([10]→"10"→10, [5]→"5"→5)
[10,20] - [5] // NaN ("10,20" 无法转数字)

[] == ![] // true
// [] 转布尔是 true, ![] 是 false
// [] == false → "" == false → 0 == 0 → true

这部分主要是在警告 js 的隐式转化规则中存在的问题,因此其复杂的隐式转化规则,经常导致出现一些意料之外的错误,比如使用 == 的时候是一种相对宽松的比较,如果两边的类型不一致,那么就会出现隐式类型转化的情况,"" == 0 成立就是因为 "" 被转化为数字 0,从而使得条件成立。

1
2
3
if (1 < x < 3) { 
// x是 *任何* 值都为真!
}

除此之外,还有运算顺序引发的错误,下面的运算顺序是从左往右进行运算的,也就是说,上述代码等价于:

1
2
3
if ((1 < x) < 3) { 
// x是 *任何* 值都为真!
}

但是,typescript 对上述进行了处理,使用静态检查检测上述的错误。

TypeScript 是 JavaScript 的 超集。TypeScript 不会将任何 JavaScript 代码视为错误。这意味着你可以将任何有效的 JavaScript 代码放在 TypeScript 文件中,而不必担心它的确切编写方式。

for

for…in… 和 for…of…

for...infor...of 都可以用于遍历,但遍历的对象和结果不同。

  • for...in:遍历对象的可枚举属性名(key)
  • for...of:遍历可迭代对象(Iterable)产生的值(value)

这和 Python 中使用 for...in 遍历有所区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let array = [1, 2, 3, 4, 5];

array.name = "MyArray";

console.log("Using for...of loop:");

for (let x of array) {
console.log(x);
}
// 1
// 2
// 3
// 4
// 5

console.log("Using for...in loop:");

for (let x in array) {
console.log(x);
}
// 0
// 1
// 2
// 3
// 4
// name

typeof

null 类型为 object

1
2
3
4
5
6
7
8
9
function printAll(strs: string | string[] | null) {
if (typeof strs === "object") {
// 开发者预期:这里 strs 应该是 string[]
for (const s of strs) {
// 因为如果传入的是 null,for...of 会在运行时直接导致程序崩溃(抛出 TypeError)
console.log(s);
}
}
}

当你写 typeof strs === "object" 时,你原本以为它能过滤出数组(Array),但实际上它同时把 null 也放进来了。

1
2
3
"use strict";
let strs = null;
console.log(typeof strs); // Output: "object"