# 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
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
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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 通过对象 s 调用方法,this 自然指向调用者 s
- apply 方法临时将 this 绑定到第一个参数 t,即使通过 s 调用,apply 强制改变了 this 指向
- 独立函数调用时 this 默认指向window
- 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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15