嗑嗑瓜子,聊聊作用域、作用域链、变量提升、预编译和闭包(上)

738 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Tips:如果你看完了整篇文章,务必看一看本文的拓展内容,知识点很多,不要错过,码字不易,点个赞鼓励一下

Tips:本文有所更新,之前五个知识点太长了,可能很多人看不下去,手机上看也很不方便,所以分为上中下三篇,可以去本人主页观看后续,谢谢大家支持!

作用域、作用域链、变量提升、预编译和闭包

拿上你专属的保温杯,一包瓜子,今天带你拿捏作用域、作用域链、预编译和闭包,让你以后遇见它不会像嗑瓜子一样上火。

作用域

作用域简单来说就是指,一段代码中所用到的的变量、函数和对象等的生效范围。

1. 全局作用域

全局作用域,是可以被整个程序访问到的作用域

var c = 'ccc'
function F() {
    console.log(c)
}
F()

上面这段代码我们拿去执行,打印结果如下:

会发现,在函数F里面,并没有定义变量c,可是,在调用函数F的时候,他仍然打印出了c的值,这是因为内部作用域是可以访问到外部作用域的(原因会在后文作用域链中提到),而全局作用域就是整个程序最外层的作用域,那么函数F就是在全局作用域里面的内部作用域,也正因为全局作用域是最外层的作用域,所以,定义在全局作用域下的变量可以被整个程序访问到。

如果把第一行代码var关键字去掉呢

会发现结果还是一样,这是因为c='ccc'写在了全局作用域,就算没有var,let,const的声明,也会被挂在window对象上,等价于window.c='ccc'。

2. 函数作用域

函数作用域,就是定义在函数里的变量的作用范围在这个函数里。

function F() {
    var c = 'ccc'
}
console.log(c)

将c定义在函数里,然后在全局下打印这个c,结果如下:

结果是报错了,报错内容是c没有被定义,这是因为,c是定义在函数里的,全局作用域是函数F的外部作用域,外部作用域是无法访问到内部作用域的(原因会在后文作用域链中提到),所以这里报了错。

3. 块级作用域

块级作用域,简单来说,{}(花括号)包裹的就是一个块级,花括号里面可以访问外面,外面无法访问里面,常见的if,for循环,while循环,let作用域等

function foo(){
    var a = 1
    let b = 2
    {
        let b = 3
        var c = 4
        let d = 5
        console.log(a);//1
        console.log(b);//3
    }
    console.log(b)//2
    console.log(c)//4
    console.log(d)//error 因为代码块执行后就销毁,故d被销毁了,不存在,所以是error,如果存在却未定义叫做undefined
}
foo()

上述例子有五个打印,结果分别是1,3,2,4,报错,这里涉及到一个花括号包裹的代码块,是个块级作用域,我们来分析一下:

  • 第一个打印a,结果是1,是因为花括号是写在foo函数里面的,内部作用域可以访问外部作用域,因此,a能够顺利打印,结果为1,

  • 紧接着花括号里面打印了b,b在花括号的外面和里面都有定义,那么从里面找到外面(具体会在后文作用域链中详细解释),先找花括号里面是否有b,如果没有就往foo里面找,一层层向外,那么这里的结果是有b,因此打印的是3

  • 然后在花括号外面打印了b,因为,花括号包裹的是块级作用域,且花括号里面的b是关键词let声明的,所以根据外部作用域不能访问到内部作用域,是访问不到花括号里的b的,因此只能访问花括号外的b,故打印的是2

  • 然后是打印c,c虽然是花括号里的,但是他是关键词var声明的,var不会有块级作用域的效果,所以c可以被访问到,打印是4

  • 最后是d,d也是let声明的,且因为代码块执行后就销毁,故d被销毁了,不存在,所以访问不到,可是画括号的外部也没有定义d,故报未定义d的错。

变量提升和预编译

1. 变量提升

变量提升javaScript 代码在执行过程中,JavaScript引擎会把变量声明部分函数声明部分提升到代码的最前面的“行为”,根据提升的顺序,如果变量名相同,那么后者覆盖前者,且函数声明提升会在变量声明提升之后,当使用let,const等关键字时,是不会进行变量提升的。

首先我们了解一下代码是怎么运行的

  1. 在执行过程中,若使用未声明的变量,js执行会报错
  2. 在一个变量定义之前使用它,不会报错,但是该变量的值为undefined,而不是定义的值
  3. 在一个函数定义之前使用它,是不会报错,且函数能正确执行

其次是要分清函数声明和函数表达式(因为函数声明是会进行变量提升的,但是函数表达式不会):

  • 函数声明:function 函数名(){}

  • 函数表达式: var 函数名=function(){}

最后要分清变量的声明和赋值,一般我们写代码是这样写的,var a = 'aaa',但这其实是两部分,var a是变量声明,a = 'aaa'是赋值。

下面举一个变量提升的简单例子,看一下这段代码的打印结果:

console.log(a)
var a = 123
foo()
function foo (){
    console.log(a)
}
根据代码的运行规则,第一个打印结果是undefined,这里本应该报错才对,因为a的声明和赋值在console.log之后,可是他没有报错,但值也不是123,这是因为变量提升的关系,var a = 123,拆分成了var a(变量声明)和a = 123(变量赋值),变量声明会被提升到整段代码的最前面,但是赋值不会提升;第二个foo函数的执行也没有报错,打印出了123,这是因为函数声明部分被提升了,因为函数声明提升会在变量声明提升之后,所以这段代码经过变量提升之后是应该是等价于这样的:
var a;
function foo (){
    console.log(a)
}
console.log(a)
var a = 123
foo()

这样一来,执行结果就相符了

image.png 这里给大家检测一下,函数声明会被提升而函数表达式是不会提升的,下面的这两张图,第一张是函数声明,打印的结果是123,而第二张是函数表达式,他的打印结果是报错了,这就说明了函数声明会被提升而函数表达式是不会提升的,所以,分清楚函数声明和函数表达式很重要。