引言
对于变量提高这个问题,我想从事前端的同学都或多或少以为我懂这个。从前,我也是这样以为的,我懂变量提高,并且能够从变量在 Chrome
中的内存分配讲起,以及中心发生了什么。
可是,在一次面试中,我遇到了几个一同面前端的同学(当然技术水平参差不齐,并不是很高),在和他们聊这次书面考试中的变量提高的问题时,发现咱们都支支吾吾的,很多讲的都是值的掩盖。
其时的面试题是这样的:
functionfn(a){
consoF 3 ! ^ a t 2 `le.log(a)
vara=2
functiona(){}
console.log(a)
}
fn(1)
这个题目,最终会输出function a(){}
和 2
。那么,为什么是这个答案,这个进程发生了什么很重E 6 ] g e ` X要。所以,今天咱们就来完全刨析一下变l s 2量提高的进程。
一、变量在内存中的分配
在剖析[ | h . c g ! &整个进程前,咱们~ 0 {先来回顾一下 JavaScript
中变量在内存中的分@ W配。
咱们都知道的是,对于原始类型会存储在栈空间中,对于引证类型会将引证存储在栈空间中,将数据存储在堆空间中。
其实这个进程还牵扯到函数上下文的创立,而每一个函数上下文中又会创立一个变量环境、词法y ^ s @ 6 /环境。有爱好的同学能够去看李斌老J f { @ + T 6 &师的浏J x * N g o ! )览器作业原理与实践
所以,咱们来看一个简略的栗子,剖析一下它在内存中的分配:
栗子:
vara=1
varb=2
varstudentv _ T F N H o 6={name:'wjc',age:22o @ - + 5}
它内存中的分配:
二、运转前的简略编译
众所周知,JavaScript
是一门动态类型的言语,即它是在运转时确定变量的类型,不同于静态类型言语的先编译再运转的进程。可是,事实是 V8B ? s
引擎在解析运转 JavaSch 1 J v & i }ript
之前是会进行一次简略的编译,也便是咱们一般所r b i S E – ~ & R说的初始化进程。
这个初始化进程,会做O u J 3这几件事:
-
区别履行代码和} j + o i ? h变量声明代码 -
变量n p ( = v D |声明代码划分为赋值代码和初始化代码 -
初始化代码z m F ` R d有两种情况Z ] z y _ 8 Z,一是对变量(原生类型、目标类型)初始化为 und q G D B Adefined
;二是对函数的初始化,即直接指向函数在堆空间中的内存
那么,咱们就来看一个简略的K L t + 8栗子:
console.log(a)
sayHi = $ ; o _ G = Si()
vara=2
functionsayHi(){
console.log('Hi')
}
那么按照咱们上面所说,这段代码的m ! . 3 S赋值只要 var a = 2
,函数声明只要进行编译阶段的代码会是这样的:
//- j { { g U W ( n编译代码
vara=undefined
varsayHi=function(){
console.log('Hi')
}
此刻,它在内存中的分布:
然后,在履行阶段的代码会是这样:
//履行代码
console.log(a)
sayHi()
a=2
所以,也便是当咱们真实履行的时候会走履行代码,所以很显然会输出:
undefined
Hi
而当走完一切履行代码后,此[ y / Y & %刻内存是这样的:
我想经过这个栗子,咱们应该大致搞懂变量提高的进程。可是,依然存在一个较为特别的情况,便是当函数形参存在时的变量提高,也便是咱们文章开头提及的面试题。M u h e _ u v R
三、函数形参的编译履行
首要,咱们需要对函数调用做e ` ( h S +一个简略的了解,在咱们平常调用函数的时候,真实会经历两个步骤:
-
假如此刻存在形参,则进行函数形参的编译和履行进程 -
然后进入函数体,进行g f i m O @ `函数体内部的编译和履行
能够看到这儿咱们说到了当函数存在形参时,会先进行函数形参的编译和履行进程。
这儿咱们就来剖析文章开头这个栗子:
funo Y h % + cctionfn(a){
console.log(a)
vara=2
functiona(){& l # o ~}
consolo g S A $ ie.log(a)
}
fn(1)
首要,此刻是存在函数形参的,那么函数形参的编译和履行会是这样:
vara=undefined
a/ r Z 3 S=1
然后,才o Y . W Y会进行函数体的编译和履行:
//编译
at . [ 3=functiona(){}//重点!!!
//履行
console.log(a)
a=2
coy [ X ~ T R p nsole.log(a)
能够看到的是,假如函数体内的变量名和形参的变量名重复时,则不会进行一般变量的编译赋值 uW a Zndefined
的进程。可是,假_ n i如存在该变量是函数时,那么则会进行函数变量$ l y A ! ] F 7的编译赋值,即直接指向函Q A ] z n c e ( 5数在堆空间中的地址。
所以,咱们这d R G Q n q _ p个栗子在编译后,能够看作是这样的:
functionfn(){
vara=undefined
a=1
a=functiona(){}
console.log(a)
a=2
console.log(2)
}
很显然,它会输出会输出function a(){}
和 2
写在最后
不知咱们在深度了解过变量提高进程后,是否有和我相同的感受便是学习编程的实质是追溯本源。现今,虽然咱们能够用 ES6
的 let
或 const
来声明变量来避免 var
的种种缺陷。可是,假如因为这样而不去思考 var
为什么会存在这些缺陷。我想这是非常遗憾的。
写作不易,假如你觉得有收获的话,能够英俊三连击!!!