Mark24
JavaScript变量提升
一、变量提升(Hoisting)
以下这段代码可以工作
speak()
var test
console.log(test)
function speak() {
console.log(test)
}
因为变量提升。
变量声明和赋值
var name = "Mark";
这句可以看成是下面两句:
var name; // 声明部分
name = "Mark"; // 赋值部分
函数声明和赋值
function foo() {
console.log('foo')
}
var bar = function() {
console.log('bar')
}
第一个函数 foo 是一个完整的函数声明,也就是说没有涉及到赋值操作。
第二个函数,先是声明变量bar,在把匿名函数赋值给了bar
var bar;
bar = function() {
console.log('bar')
}
所谓的变量提升,是指在JavaScript代码执行过程中,JavaScript引擎把变量的声明部分和 函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认 值就是我们熟悉的 undefined 。
模拟变量提升:
书写代码
test()
console.log(name)
var name = "mark"
function test() {
console.log("test run")
}
模拟变量提升,结果如下
// 变量提升部分 ----start
// 把变量 name 提升到开头,同时赋值为 undefined
var name = undefined
// 把函数 test 提到开头
function test() {
console.log("test run")
}
// 变量提升部分 ----end
// 代码执行部分
test()
console.log(name)
// 去掉声明部分的赋值语句
name = "mark"
JavaScript代码的执行流程
“变量提升”意味着变量和函数的声明会在物理层面移动到代码的前面,正如模拟的那样。但是,这样子理解并不准确。
实际上变量和函数声明在代码里的位置是不会改变的。而且是在编译阶段被JS引擎放入内存中。
一段JS代码在执行之前需要被JS引起编译,编译完成之后才会进入执行阶段:
大致流程
一段JS代码 ---> 编译阶段 ---> 执行阶段
1. 编译阶段
编译阶段和变量提升是什么关系呢?
分析上面的模拟代码:
第一部分:变量提升部分的代码
var name = undefined
function test() {
console.log("test run")
}
第二部分:执行部分的代码
test()
console.log(name)
name = "mark"
JS的执行流程详细化:
输入一段JS代码(原始代码)
|
|
// 编译阶段
执行上下文 { 变量环境 (代码提升部分) 、词法环境 、可执行代码(代码提升后剩余部分)}
|
|
//执行代码
输出结果
执行上下文是JS执行一段代码时候的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数执行时候用到的诸如 this、变量、对象以及函数等。
简单说执行上下文中存在一个“变量环境对象(Variable Environment)”用来保存变量提升的内容。
VariableEnvironment:
name -> undefined,
test -> function : { console.log(name)}
过程分析
1. 编译阶段
test()
console.log(name)
var name = "mark"
function test() {
console.log("test run")
}
第1行、第2行,这两行代码不是声明操作,JS引擎不做任何处理
第3行由于是声明语句 var 于是JS引擎将在“变量环境对象”中新建一个名为name的属性,并且使用undefined初始化
第4行JS引擎发现一个通过function定义的函数,所以将函数储存到了 堆(heap)中,并且在“变量环境对象”中创建了一个 test属性,并且指向堆中这个函数的地址。
这样就生成了“变量环境对象”。接下来JS引擎会把声明之外的语句编译为字节码,接下来就是执行阶段。
2. 执行阶段
JS引擎开始执行“可执行代码”,按照顺序一行一行执行。
当执行到 test 函数,JS引擎便在“变量环境对象”中查找该函数,由于保存了函数的地址,所以JS开始执行这个函数,并且输出执行结果。
接下来打印 name,JS引擎继续在 “变量环境对象”中查找,并且输出其值为 undefined。
接下来是一个赋值语句,环境变量中值被修改如下:
VariableEnvironment:
name -> mark,
test -> function : { console.log(name)}
这就是一段代码的编译和执行流程。实际的编译和执行阶段都非常复杂,包括词法分析、语法解析、代码优化、代码生成等。
代码中出现相同的变量或者函数怎么办?
当变量和函数名称相同时,函数声明始终优先于变量声明。这是因为函数声明被提升到作用域顶部时,它们会覆盖任何同名的变量声明。这种情况下,函数可以在代码中的任何地方调用,而变量仅在声明之后才能被访问。
- 函数位置先于变量声明
console.log(a); // undefined
function a() {
console.log("function a");
}
var a = "hello";
console.log(a); // function a
函数声明在 变量前面,输出的是:
[Function: a]
hello
- 变量位置先于函数声明
console.log(a); // [Function: a]
function a() {
console.log("function a");
}
var a = "hello";
console.log(a); // hello
思考
追加 2023年03月17日
为什么 变量提升,函数的优先级要比变量高?
在JavaScript中,变量提升是编译阶段发生的行为,而函数的优先级则是在代码执行阶段发生的。当JavaScript解释器第一次遍历代码时,它会扫描代码标识符,将变量和函数声明提升到顶部并将它们存储在内存中。这种行为导致在执行代码时,函数可以在它们实际被声明之前被调用,而变量却不能。这也是为什么JavaScript中的函数可以被认为是“第一等公民”(first-class citizen)的原因之一。
另一个原因是函数的声明方式不同于变量的声明方式。函数可以使用函数声明或函数表达式来声明,而变量只能使用var或let等关键字声明。在使用函数声明方式时,函数实际上是在编译阶段创建的一个对象,并在解析代码时将其名称提升到顶部,因此它们的优先级比变量高。而函数表达式则类似于变量声明,因此它们与变量的优先级类似。
综上所述,JavaScript中的变量提升和函数优先级是由JavaScript解释器的处理方式和不同的声明方式所导致的。熟悉这些特性可以帮助开发人员更好地理解和编写JavaScript代码。
来自 chatGPT 的回复。
这里面有一点我个人是比较倾向于的:
1) 函数是一等公民。
所有功能围绕函数。