结合面试题学习 JS 词法作用域与预解析

在面试题之前先做一下名词解释

词法作用域

  1. 什么是作用域
    作用域就是指一个变量可以被使用的范围. 即从什么地方开始可以被访问到. 到什么地方结束, 不能在访问到, 这个范围称为变量的作用域.
  2. 什么是块级作用域
    块级作用域就是使用代码块来限定变量的使用范围.在 js 中( ES5 以前 )没有块级作用域.在 es6 中引入了 let 命令, 来代替 var 声明变量. 而 let 命令声明的变量就具有块级作用域.所谓的块级作用域就是从变量声明开始, 到变量所在的最近的(最小的)花括号结束为止.
  3. js 中的词法作用域
    在 js 中 js 的代码需要经过"预解析"( 提前解析 ), 再逐步的解释执行.所以在 js 中所谓的词法作用域从预解析开始全局起作用, 只有函数可以限制作用域的范围.

代码预解析

  1. js 中的声明
    声明就是 变量的声明和函数的声明, 其目的是让 js 解释引擎知道有什么东西.声明时是不参与运算和执行的, 是在预解析阶段就完成的.
  • 变量的声明
// 变量的声明就是 var 变量名.
var num = 123;
// 这是一个语法糖,可以理解成
var num;
num = 123;
  • 函数的声明
function 函数名 () { ... }
//  在一个独立于任何语句( 表达式, if 结构, while 结构 等 )的独立结构中, 或函数中出现的代码, 为函数声明.
  1. js 预解析代码如何执行
    js 的代码执行要经历两个步骤, 首先是预解析. 预解析会通读所有代码. 如果发现错误则停下, 如果遇到声明则记录.在声明的时候, 如果是变量名的声明, 解析器内部就会记录下这个变量. 如果使用则检查是否有记录.
    在声明的时候, 如果是函数声明, 则解析器会先记录函数的名字( 相当于变量声明 ), 然后将函数的名字与函数体联系在一起.
    在预解析中, 如果出现重复声明, 则第一次声明起作用, 其后所有的同名的声明无效.
// 例如:
  var num = 1;
  var num = 2;
// 等价
  var num = 1;
  num = 2;

声明结束后代码就会再从第一句话开始一句一句的执行.

理论知识介绍完了,下面来看面试题:

题目一

在这里插入图片描述
代码分析如下:

  1. 1-5行定义函数fun
  2. 6行定义变量n
  3. 7行执行函数并传入变量n

注意:fun函数内部有预解析。

预解析及执行步骤:

  1. Fun函数开始执行前,将var n提前执行,初始化为undefined。
  2. 由于函数传入参数n并没有使用,忽略。
  3. 开始执行第2行,输出为undefined。
  4. 执行第3行,此时即n = 456,即将n值重置为456。
  5. 执行第4行,输出改变后的n。

通过以上步骤分析,即可得知预解析的原理了(针对有var的变量提前赋初始值)

题目二

在这里插入图片描述
代码分析如下:

  1. 29行定义一个全局变量
  2. 30-32行定义一个函数f1
  3. 33-36行定义一个函数f2
  4. 37行执行函数f2
  5. 38行输出结果n

预解析及执行步骤:

  1. 代码执行前,预解析先初始化变量n, f1, f2,将它们都置为undefined.
  2. 接着执行第29行,为变量n赋值
  3. 接着执行第30-32行,为函数变量f1赋值,即f1为函数了
  4. 接着执行第33-36行,为函数变量f2赋值
  5. 执行第37行,即执行f2函数。
    6.f 2函数执行前,同样预解析,先将n初始化为undefined,接着把n赋值为456,接着调用f1函数执行。
  6. f1在f2中执行,那f1的作用域应该是f2,应该输出456?
  7. 35行执行f1函数时无调用者,即f1函数为全局作用域,输出全局n为123
  8. 第38行直接输出全局变量n,即123

题目三

在这里插入图片描述
代码分析如下:
预解析只针对var和function定义的变量

预解析及执行步骤:

  1. 预解析先初始化变量length, obj, f1并赋值为undefined
  2. 接着为变量length赋值为100
  3. 接着为函数变量f1赋值为函数
  4. 接着为变量obj赋值为对象
  5. 第52行,调用对象obj的f2函数执行,传入形参f1和1
  6. 第47行,f2函数接收实参为f1, 接着执行f1函数
  7. 同上,f1函数执行无调用者,作用域为全局,this指向window,输出全局变量length,即100
  8. 第47行,f2函数接收实参f1,若要取到所有实参则需要arguments对象,第一个参数arguments[0]==f1,第二个arguments[1]==1,依此类推。
  9. 第49行,arguments[0] ()看上去是f1(),那也应该输出100?
  10. 注意arguments[0]作用域与f1的作用域并不相同,第48行直接执行f1,无调用者,作用域为全局作用域,但arguments[0]作用域为arguments对象,即this为arguments,则应输出2,因为arguments对象的属性length为2。

参考文章:
JavaScript 预解析
JavaScript中的预解析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值