【JavaScript】代码编译执行/闭包笔试题

发布于:2025-09-05 ⋅ 阅读:(21) ⋅ 点赞:(0)

1、编译执行阶段分析

var func = 1
function func() {}
console.log(func + func)  // 2
  1. 编译阶段:函数声明提升 + 变量声明提升

函数声明 会被整体提升,并且优先级比 var 变量声明高。

变量声明(var)也会提升,但 不会提升赋值。

等价于:

function func() {}  // 函数声明整体提升
var func;           // var 声明提升(但这里不会覆盖函数的定义)
func = 1;           // 运行时执行赋值
  1. 执行阶段

先加载了函数 func(所以最初 func 是一个函数)。

然后执行 var func = 1 的赋值,把 func 变成了数字 1,所以最终为两个 1 相加。

2、parseInt

;['1', '2', '3'].map(parseInt) // [1, NaN, NaN]
  1. Array.prototype.map

map 回调函数会传 三个参数:

callback(element, index, array)
  • element:当前元素

  • index:当前索引

  • array:原数组本身

所以 map(parseInt) 实际等价于:

['1', '2', '3'].map((val, idx) => parseInt(val, idx));
  1. parseInt 的两个参数
parseInt(string, radix)
  • string:要解析的字符串

  • radix:进制(2 ~ 36 之间的整数,如果为 0 或不写,会默认按 10 进制解析,或按字符串格式猜测)

  1. 每次迭代分析
第一次: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++
  • 变量解析时,就近查找作用域链
  • 最近的 nf2 内的 let n = 20

因此执行:

f2.n = 20 → 21

回到 f2

继续执行:

n++  // (5)

依然是 f2n,所以:

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 返回一个对象,里面两个方法 getset 都是闭包,捕获了 fn 作用域中的 num
  • 注意:这里的 num 不是外面的 let num = 20,而是 fn 内部的那个 num

所以:getset 操作的都是 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

网站公告

今日签到

点亮在社区的每一天
去签到