js面试题 变量提升和函数提升


 

function demo(a){
    console.log(a);
    var a = 1;
    function a(){
        console.log('222');
    }
    console.log(a);
}
demo(2);

运行后可知,第一个 console 输出的是函数声明“function a”的函数体,第二个 console 输出的是 1。

为什么会这样?

很多人会用“变量提升和函数提升,函数提升优于变量提升”来解答,这么说好像解释了问题,但是好像又没有解释清楚“为什么”。而且“函数提升优于变量提升”这个说法,好像也有点模棱两可,到底是怎么个“优于”法呢?

执行上下文

要解释这个为什么,还得回到更基础的概念 —— 执行上下文。

一个函数在执行之前,先要创建执行上下文,执行上下文会做这么几件事(包括但不限于):

  • 确定作用域链
  • 创建变量对象(variable object)
  • 确定 this

其中所谓的“变量提升和函数提升”都发生在“创建变量对象”这里。

创建变量对象

js 在创建变量对象的时候,会进行三个步骤:

  1. 把函数的参数声明放到变量对象中
  2. 遍历函数体内的函数声明,把函数声明放到变量对象中,这一步也即是所谓的“函数提升”,注意,执行这一步的时候,会直接把变量对象中函数名的指针指向函数声明的函数体,如果存在同名参数,会被函数体覆盖。而如果函数体内存在多个同名的函数声明,会采用最后一个。
  3. 遍历函数体内的变量声明,如果变量对象中已存在该变量名,则跳过。注意,提升的只是变量声明,而不包含变量的赋值操作。变量的赋值操作将会在函数执行阶段才进行。

结论

在前面的代码里,当我们运行 demo(2) 的时候,js 会在创建变量对象的时候是这样进行的:

  1. 将参数 a 放入变量对象,将值设置为 2。
  2. 扫描 demo 的函数体,发现函数声明 function a,变量对象中已存在 a,将 a 指针指向 function a 的函数体。
  3. 扫描 demo 的函数体,发现变量声明 var a,变量对象中已存在 a,跳过。

此时 a 的值是 function a 的函数体。

下面进入到执行阶段。

  1. 执行第一个 console.log(a) ,输出 function a 的函数体。
  2. 执行 a = 1 的赋值操作,将 a 的值改写为 1
  3. 跳过函数声明 function a。
  4. 执行第二个 console.log(a),输出 1。

补充

从这个面试题我们可以领悟到什么?

1、不要用 var 声明变量,如果把代码中的 var 改成 let ,因为 let 不允许重复声明,js 将直接抛出语法错误。即使没有语法错误,let 声明的变量在声明之前被访问,也会因为触发“暂时性死区”而抛出引用错误。问题就可以化繁为简,代码也会少很多 bug。

2、参数名、变量名、函数名不要太随意,多用 eslint 之类的工具来检查是否存在重复声明、shadow name 等问题。

3、关于函数表达式和函数声明的取舍,个人建议,在不需要函数提升特性的情况下,应优先采用函数表达式,这样代码的书写顺序和执行顺序是一致的,更容易理解

转载于:王峰
链接:https://www.zhihu.com/question/489790136/answer/2148247810
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值