# 1.for循环嵌套setTimeout

console.log('step1 =>', i)                          // s1 undefined
for (var i = 0; i < 3; i++) {
    console.log('step2 =>', i);                     // s2 0,1,2
    setTimeout(() => console.log('step6 =>', i), 1);    // s6 3,3,3
}

console.log('step3 =>', i)                             // s3 3

for (let i = 0; i < 5; i++) {
    console.log('step4 =>', i);                     // s4 0,1,2,3,4
    setTimeout(() => console.log('step7 =>', i), 1);    // s7 0,1,2,3,4
}
console.log('step5 =>', i);                         // s5 3
1
2
3
4
5
6
7
8
9
10
11
12
13

# 结果解析:

  • step1:变量提升问题
    • 在第一个 for 循环中 var 定义了 i ,提升至全局,此时{ i: undefined }
  • step2:for循环机制
    • 每循环一次打印一次,同时修改了全局中 i 的值,注意 i++,先运算再赋值,跳出循环时 i = 3
  • step3:
    • 此时跳过 setTimeout 执行,输出全局中 i 的值, { i: 3 }
  • step4:let块级作用域
    • 此处因为是 let 声明 i ,let 在 {} 中形成了块级作用域,i 被重新声明, for循环一次打印一次 i 的值
  • step5:
    • 打印全局中 i 的值,这里和执行step3是一致的,也跳过了 setTimeout 执行
  • step6:setTimeout 异步执行(全局作用域)
    • setTimeout是一个异步宏任务,他的回调函数会在遍历结束才执行,此时全局中 i=3,所以会输出3次3
  • step7:setTimeout 异步执行(块级作用域)
    • 在每次的遍历过程中,i 都有一个新值,并且每个值都在循环内的块级作用域中

step6 重点解析

  • var 声明的变量 i 是函数作用域,无法进入 setTimeOut
  • 异步回调执行时,循环已结束,跳出循环时 i = 3

step7 重点解析

  • let 的作用域是块作用域,在每次迭代创建新的绑定,let 定义的i的值也会跟随 setTimeOut 进去队列

# 如何解决step6的问题?

// IIFE(立即执行函数)方案
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 100);
  })(i);
}
// 输出:0, 1, 2
1
2
3
4
5
6
7

# 2.函数声明提升

var m = 1;
function add(n) {
  return (n = n + 1);
}
y = add(m);
function add(n) {
  return (n = n + 3);
}
z = add(m);
console.log(y, z);
// y和z的最终结果为: 4,4
1
2
3
4
5
6
7
8
9
10
11
  • 答案解析
    • 函数声明会提升,后面的定义会覆盖前面的
    • 所有 add 调用实际使用的是最后定义的版本(n + 3)
    • 因此两次调用结果都是 4

# 3.函数内变量不用var声明

(function() {
  var a = (b = 5);
  console.log(a);
})();
console.log(b);
console.log(a);

// 5
// 5
// Uncaught ReferenceError: a is not defined
1
2
3
4
5
6
7
8
9
10
  • 变量泄露​​:b = 5 没有使用 var/let/const 声明,导致 b 被隐式创建为全局变量(非严格模式下)
  • 作用域链​​:
    • a 是函数作用域内的局部变量
    • b 是全局变量(因为赋值时没有声明)
  • 严格模式​​:如果在严格模式下('use strict'),b = 5 会直接抛出 ReferenceError

# 4.​​作用域链考察

var z = 10;
function foo() {
  console.log(z);
}
(function(funArg) {
  var z = 20;
  funArg();
})(foo);
// 10
1
2
3
4
5
6
7
8
9
  • ​作用域链的静态性​​:

    • JavaScript 的函数作用域在 ​​函数定义时​​ 就已经确定(词法作用域)。
    • foo 函数在定义时,它的作用域链已经固定为: foo 作用域 → 全局作用域
    • 即使 foo 在另一个函数(IIFE)中被调用,它依然会访问 ​​定义时的作用域链​​​,而不是调用时的作用域。
  • 当执行 foo() 内部的 console.log(z) 时:

    • 先在 foo 自身作用域查找 z → ​​未找到​​
    • 向外层(全局作用域)查找 z → ​​找到全局变量 z = 10​​
    • 忽略 IIFE 中的 var z = 20(因为不在 foo 的作用域链上)

# 5.for循环嵌套未调用函数

var data = [];
for (var k = 0; k < 3; k++) {
  data[k] = function() {
    console.log(k);
  };
}
data[0]();
data[1]();
data[2]();
// 3
// 3
// 3
1
2
3
4
5
6
7
8
9
10
11
12
  • var k 会被提升到函数作用域顶部
  • 函数是在循环结束后才被调用的
  • 此时所有函数访问的都是最终的 k(3)
  • 可使用let定义变量k来解决

# 6.call、bind、apply的作用时机

var s = {
  s: "student",
  getS: function() {
    console.log(this.s);
  }
};
var t = {
  s: "teaher"
};

var getS = s.getS;
var getS1 = getS.bind(s);

// 写出以下输出结果
s.getS();
s.getS.apply(t); 
getS();
getS1.call(t); 
// student
// teacher
// {s: "student", getS: ƒ} this指向window
// student
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 通过对象 s 调用方法,this 自然指向调用者 s
  2. apply 方法临时将 this 绑定到第一个参数 t,即使通过 s 调用,apply 强制改变了 this 指向
  3. 独立函数调用时 this 默认指向window
  4. bind 创建的新函数永久绑定 this 到 s,即使使用 call 也无法改变已绑定的 this

# 7.普通函数和箭头函数 this指向

let a = "window";
const obj = {
  a: "a",
  fn1: () => {
    console.log(this.a);
  },
  fn2: function() {
    console.log(this.a);
  }
};
obj.fn1(); // undefined
obj.fn2(); // a
1
2
3
4
5
6
7
8
9
10
11
12
  • 箭头函数fn1

    • 箭头函数没有自己的 this 绑定
    • 它的 this 继承自定义时的词法作用域(即定义时的上下文)
    • 在这里,fn1 是在全局作用域中定义的(假设这段代码在全局执行),所以 this 指向全局对象(浏览器中是 window)
    • this.a 访问的是全局变量 a 的值 "window",但是 变量 a 是let声明的,不属于window,所以是undefined
  • 普通函数 fn2

    • 普通函数的 this 是在调用时动态绑定的
    • 当通过 obj.fn2() 调用时,this 指向调用它的对象 obj
    • 所以 this.a 访问的是 obj.a 的值 "a"

# 8. new Boolean 和 Boolean的区别

var x = new Boolean(false);
if (x) {
  console.log("hi");
}
var y = Boolean(0);
if (y) {
  console.log("hello");
}
// 只会输出hi,x是一个对象
1
2
3
4
5
6
7
8
9

new Boolean(false) 创建的是一个 ​​Boolean 对象​​,而不是原始布尔值

# 9. 事件执行机制(宏微任务)

setTimeout(function() {
  console.log(1);
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for (var i = 0; i < 10000; i++) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);

// 2,3,5,4,1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lastUpdate: 5/19/2025, 6:00:06 PM