1、编译执行阶段分析
var func = 1
function func() {}
console.log(func + func) // 2
- 编译阶段:函数声明提升 + 变量声明提升
函数声明 会被整体提升,并且优先级比 var 变量声明高。
变量声明(var)也会提升,但 不会提升赋值。
等价于:
function func() {} // 函数声明整体提升
var func; // var 声明提升(但这里不会覆盖函数的定义)
func = 1; // 运行时执行赋值
- 执行阶段
先加载了函数 func(所以最初 func 是一个函数)。
然后执行 var func = 1 的赋值,把 func 变成了数字 1,所以最终为两个 1 相加。
2、parseInt
;['1', '2', '3'].map(parseInt) // [1, NaN, NaN]
- Array.prototype.map
map 回调函数会传 三个参数:
callback(element, index, array)
element:当前元素
index:当前索引
array:原数组本身
所以 map(parseInt) 实际等价于:
['1', '2', '3'].map((val, idx) => parseInt(val, idx));
- parseInt 的两个参数
parseInt(string, radix)
string:要解析的字符串
radix:进制(2 ~ 36 之间的整数,如果为 0 或不写,会默认按 10 进制解析,或按字符串格式猜测)
- 每次迭代分析
第一次:val = '1',idx = 0
parseInt('1', 0)
// radix = 0 ⇒ 采用默认规则 ⇒ 当作 10 进制
// 结果 = 1
第二次:val = '2',idx = 1
parseInt('2', 1)
// radix = 1 (非法,进制范围只能 2 ~ 36)
// 返回 NaN
第三次:val = '3',idx = 2
parseInt('3', 2)
// radix = 2(二进制),只允许 0/1
// '3' 不是有效的二进制数字 ⇒ NaN
3、作用域(一)
let i
for (i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i)
}, 0)
}
// 4 4 4
i 是全局变量,用来控制循环。循环调用了 3 次 setTimeout 延迟执行。当 setTimeout 执行的时候,i 已经变成了 4。但是此时是异步行为,所以只能输出最终的结果 4 。
如果省略 delay 参数,则使用值 0,意味着“立即”执行(下一个事件循环执行),是异步行为。
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i)
}, 0)
}
// 4 4 4
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i)
}, 0)
}
// 1 2 3
除了使用 let (块级作用域)解决全局变量的问题,还可以使用 闭包(函数作用域)解决:
for (var i = 1; i <= 3; i++) {
(function (i) {
setTimeout(function () {
console.log(i)
}, 0)
})(i)
}
// 1 2 3
4、作用域(二)
let n = 10
function f1() {
n++ // (1)
function f2() {
function f3() {
n++ // (2)
}
let n = 20 // (3)
f3() // (4)
n++ // (5)
}
f2()
n++ // (6)
}
f1()
console.log('n', n)
let n = 10
全局变量 n
初始化为 10。
调用 f1()
→ 执行第一行:
n++ // (1)
这里的 n
指向 全局 n,所以:
n = 10 → 11
f2
内部:
function f3() { n++ } // 定义 f3(闭包)
let n = 20 // 局部变量 n(注意!屏蔽了外层的 n)
执行 f3
f3() // (4)
这里关键点:
f3
内部写的是n++
- 变量解析时,就近查找作用域链
- 最近的
n
是f2
内的let n = 20
因此执行:
f2.n = 20 → 21
回到 f2
继续执行:
n++ // (5)
依然是 f2
的 n
,所以:
f2.n = 21 → 22
f2
执行结束(局部变量 n=22
销毁)。
回到 f1
继续执行:
n++ // (6)
这里的 n
又是全局的 n
(因为 f1
内没定义自己的 n)。
所以:
global n = 11 → 12
最终结果
console.log('n', n) // 12
✅ 输出
n 12
5、作用域(三)
const n = 10
function print() {
console.log(n)
}
function f1(fn) {
const n = 20
fn()
}
f1(print) // 10
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。换句话说,说函数的作用域在函数定义的时候就决定了。
所以当调用 print 的时候,它会根据定义的位置向外查找变量,也就是 n = 10。
6、闭包(一)
function fn() {
let num = 10
return {
set: (n) => (num = n),
get: () => num,
}
}
let num = 20
const { get, set } = fn()
console.log('result1: ', get())
set(100)
console.log('result2: ', num)
// result1: 10
// result2: 20
执行 fn()
const { get, set } = fn()
- 进入
fn
,声明了一个局部变量num = 10
。 fn
返回一个对象,里面两个方法get
、set
都是闭包,捕获了 fn 作用域中的num
。-
注意:这里的
num
不是外面的let num = 20
,而是fn
内部的那个num
。
所以:get
和 set
操作的都是 fn
内部作用域的 num(初始值 10)。
执行 get()
console.log('result1: ', get())
get
返回fn
内部num
,此时还是10
。
输出:
result1: 10
执行 set(100)
set(100)
set
修改的是 闭包里的 num,所以fn
内部的num
变成了100
。- 这个修改跟全局的
let num = 20
没有关系。
打印全局 num
console.log('result2: ', num)
这里访问的 num
是全局作用域的 num
,它始终是 20,并没有被闭包修改。
✅ 最终输出
result1: 10
result2: 20