引言
对于变量提升这个问题,我想从事前端的同学都或多或少认为我懂这个。曾经,我也是这样认为的,我懂变量提升,并且可以从变量在 Chrome
中的内存分配讲起,以及中间发生了什么。
但是,在一次面试中,我遇到了几个一起面前端的同学(当然技术水平参差不齐,并不是很高),在和他们聊这次笔试中的变量提升的问题时,发现大家都支支吾吾的,很多讲的都是值的覆盖。
当时的面试题是这样的:
function fn(a) {
console.log(a)
var a = 2
function a() {}
console.log(a)
}
fn(1)
这个题目,最终会输出function a(){}
和 2
。那么,为什么是这个答案,这个过程发生了什么很重要。所以,今天我们就来彻底刨析一下变量提升的过程。
一、变量在内存中的分配
在分析整个过程前,我们先来回顾一下 JavaScript
中变量在内存中的分配。
大家都知道的是,对于原始类型会存储在栈空间中,对于引用类型会将引用存储在栈空间中,将数据存储在堆空间中。
其实这个过程还牵扯到函数上下文的创建,而每一个函数上下文中又会创建一个变量环境、词法环境。有兴趣的同学可以去看李斌老师的浏览器工作原理与实践
所以,我们来看一个简单的栗子,分析一下它在内存中的分配:
栗子:
var a = 1
var b = 2
var student = {name: 'wjc', age: 22}
它内存中的分配:
二、运行前的简单编译
众所周知,JavaScript
是一门动态类型的语言,即它是在运行时确定变量的类型,不同于静态类型语言的先编译再运行的过程。但是,事实是 V8
引擎在解析运行 JavaScript
之前是会进行一次简单的编译,也就是我们通常所说的初始化过程。
这个初始化过程,会做这几件事:
- 区分执行代码和变量声明代码
- 变量声明代码划分为赋值代码和初始化代码
- 初始化代码有两种情况,一是对变量(原生类型、对象类型)初始化为
undefined
;二是对函数的初始化,即直接指向函数在堆空间中的内存
那么,我们就来看一个简单的栗子:
console.log(a)
sayHi()
var a = 2
function sayHi() {
console.log('Hi')
}
那么按照我们上面所说,这段代码的赋值只有 var a = 2
,函数声明只有进行编译阶段的代码会是这样的:
// 编译代码
var a = undefined
var sayHi = function () {
console.log('Hi')
}
此时,它在内存中的分布:
然后,在执行阶段的代码会是这样:
// 执行代码
console.log(a)
sayHi()
a = 2
所以,也就是当我们真正执行的时候会走执行代码,所以很显然会输出:
undefined
Hi
而当走完所有执行代码后,此时内存是这样的:
我想通过这个栗子,大家应该大致搞懂变量提升的过程。但是,仍然存在一个较为特殊的情况,就是当函数形参存在时的变量提升,也就是我们文章开头提及的面试题。
三、函数形参的编译执行
首先,我们需要对函数调用做一个简单的理解,在我们平常调用函数的时候,真正会经历两个步骤:
- 如果此时存在形参,则进行函数形参的编译和执行过程
- 然后进入函数体,进行函数体内部的编译和执行
可以看到这里我们提到了当函数存在形参时,会先进行函数形参的编译和执行过程。
这里我们就来分析文章开头这个栗子:
function fn(a) {
console.log(a)
var a = 2
function a() {}
console.log(a)
}
fn(1)
首先,此时是存在函数形参的,那么函数形参的编译和执行会是这样:
var a = undefined
a = 1
然后,才会进行函数体的编译和执行:
// 编译
a = function a() {} // 重点!!!
// 执行
console.log(a)
a = 2
console.log(a)
可以看到的是,如果函数体内的变量名和形参的变量名重复时,则不会进行普通变量的编译赋值 undefined
的过程。但是,如果存在该变量是函数时,那么则会进行函数变量的编译赋值,即直接指向函数在堆空间中的地址。
所以,我们这个栗子在编译后,可以看作是这样的:
function fn() {
var a = undefined
a = 1
a = function a() {}
console.log(a)
a = 2
console.log(2)
}
很显然,它会输出会输出function a(){}
和 2
写在最后
不知大家在深度理解过变量提升过程后,是否有和我一样的感受就是学习编程的本质是追溯本源。现今,虽然我们可以用 ES6
的 let
或 const
来声明变量来避免 var
的种种缺陷。但是,如果因为这样而不去思考 var
为什么会存在这些缺陷。我想这是非常遗憾的。
写作不易,如果你觉得有收获的话,可以帅气三连击!!!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。