了解JS变量提升和函数提升的优先级

755 阅读3分钟
//函数表达式
var greeting = function(){
    console.log("hello world"); 
}
//声明式函数
function greeting(){
      console.log("hello world");  
}

上面的两种函数定义方法是我在写代码的时候比较常用的,一般是一直使用一种方法,但考虑到如果同时使用两种定义函数的方法,而且定义的函数名字是一样,会是怎样的结果?

首先,我们要了解到这里涉及到的知识有函数提升,变量提升,两者提升的优先级,还有JS引擎在编译阶段是怎么处理函数提升、变量提升的

JS的提升

在编译阶段阶段,代码真正执行前的几毫秒,会检测到所有的变量和函数声明,所有这些函数和变量声明都被添加到名为[Lexical Environment]词汇环境的JavaScript数据结构内的内存中。所以这些变量和函数能在它们真正被声明之前使用。

两者提升的优先级

 console.log(foo);//
 function foo(){
     console.log("函数声明");
 }
 var foo = "变量";
 /**
 答案:
 function foo(){
   console.log("函数声明");
 }
 */

那么问题来了,既然变量提升优先级是在后面,为什么没有将foo函数覆盖为undefined呢?(因为var声明变量提升会在编译阶段将变量添加到词法环境中,然后赋值为undefined)。

JS引擎工作:

一个代码块{}中包含的代码就是一段,里面的变量都是先编译后执行

  1. 找些代码来调用一个函数
  2. 在执行函数代码之前,创建执行上下文
  3. 进入创建阶段
  • 初始化作用域链

  • 创建变量对象

    • 创建arguments对象,检查参数的上下文,初始化名称和值并创建引用的副本。

    • 扫描上下文以获取函数声明:

      • 对于找到的每个函数,在变量对象(或活动对象)中创建一个属性,该属性是确切的函数名称,该函数具有指向内存中函数的引用指针。
      • 如果函数名已存在,则将覆盖引用指针值。
    • 扫面上下文以获取变量声明:

      • 对于找到的每个变量声明,在变量对象(或活动对象)中创建一个属性,该属性是变量名称,并将值初始化为undefined。
      • 如果变量名称已存在于变量对象(或活动对象)中,则不执行任何操作并继续扫描(即跳过)。
    • 确定上下文中的this

  1. 激活/代码执行阶段:
  • 在上下文中运行/解释功能代码,并在代码逐行执行时分配变量值。

上面的步骤可以看出,函数提升优先级比变量提升要高。

练习题目

console.log(greeting) 
//函数表达式
var greeting = function(){
    console.log("函数表达式"); 
}
//声明式函数
function greeting(){
      console.log("声明式函数");  
}
/**
答案:
function greeting(){
      console.log("声明式函数");  
}
*/
//函数表达式
var greeting = function(){
    console.log("函数表达式"); 
}
console.log(greeting) 
//声明式函数
function greeting(){
      console.log("声明式函数");  
}
/**
答案:
function greeting(){
      console.log("函数表达式");  
}

console.log(greeting)
//函数表达式
function greeting(){
      console.log("第一个声明式函数");  
} 
//声明式函数
function greeting(){
      console.log("第二个声明式函数");  
}
/**
答案:
function greeting(){
      console.log("第二个声明式函数");  
}

从练习题目可以得出,赋值之后,变量才会被覆盖,且在同名函数下,会被后面定义的同名函数覆盖

总结

  1. 函数提升优先级比变量提升要高,且在编译阶段不会被变量声明覆盖,JS引擎会选择跳过同名的变量,不做创建和初始化。

  2. 在执行阶段,与函数同名的变量会在赋值语句之后,将之前的同名函数覆盖

  3. 同名函数下,会被后面定义的同名函数覆盖