JavaScript:变量的作用域,window对象,关键字var/let与function

  • JavaScript:变量的作用域,window对象,关键字var/let与function已关闭评论
  • 104 次浏览
  • A+
所属分类:Web前端
摘要

为什么要将这些内容放在一起,因为他们都跟初始化有关系,我们慢慢说吧。

为什么要将这些内容放在一起,因为他们都跟初始化有关系,我们慢慢说吧。

我们在代码中,都会声明变量、函数和对象,然后由浏览器解释器(下面简称浏览器)执行;

我们还说过,变量和对象的内存结构;

那么,是什么时候,我们声明的变量和对象,被浏览器分配内存了呢?

我们使用不同的声明方式,浏览器分配内存的顺序是一样的吗?

window对象

我们先来看一个对象window,这个对象代表什么呢?

顾名思义,它代表一个窗口,一个浏览器窗口;

当我们用浏览器打开一个页面的时候,这个window对象就被初始化创建了;

这个window对象,出生就自带很多方法,包括JS内置函数和浏览器窗口函数;

所以下面的代码才能够在什么都没有的情况下,输出window对象:

JavaScript:变量的作用域,window对象,关键字var/let与function

可以看到,alert()也是window对象的一个方法,其实console.log()也是;

也就是说,上面的代码,其实是window.console.log(),只是省略了window而已;

window对象是一个非常值得细致研究的对象,但是这里就不赘述了,我们还有很多内容要说;

总之记住一点,window对象代表的是整个浏览器窗口,它的范围是最大的,它是最早被创建的对象;

变量的作用域

变量的作用域,其实就是指它的可见性,分为全局作用域和局部作用域:

  • 局部作用域

    先来说明一下代码块的概念;

    凡是用大括号{}括起来的都是代码块,包括函数体、循环体,以及大括号本身等等;

    在代码块中声明的变量,它就只在代码块中可见,代码块外面是无法访问到的;

    • 因为代码块只会在执行时用到,所以代码块中的变量的生命周期,也就是执行代码块的时候才会创建,而执行结束以后就会被垃圾回收;比如调用函数时,变量被创建,调用结束,变量就会被垃圾回收;
    • 因为大括号可以嵌套,所以外层代码块的变量,内层代码块可以访问到;但是内层代码块的变量,外层代码块访问不到;
    • 因为内外层代码块的可见性不一致,内层代码块可能创建和外层代码块同名的变量,此时内层代码块使用变量时,采用就近原则,也即使用离内层代码块最近的变量;

    具体看下面的代码吧:

    JavaScript:变量的作用域,window对象,关键字var/let与function

    如上图所示,每个代码块中的变量a的作用域,都用框表示出来了;

    值得一提的是,因为函数的特殊性,函数的局部性比其他代码块更强,这在关键字var那里体现的最明显。

  • 全局作用域

    那么全局作用域的概念就很清晰了,不在任何{}代码块中的,直接在script标签中声明的变量,都具有全局作用域;

    那么它的生命周期,也就是打开一个浏览器窗口的时候被创建,关闭一个浏览器窗口时才会被垃圾回收;

关键字var和let

还记得我们声明变量的三种方式吗(因为const和let的行为一致,这里就不讨论const了)?

我们先讨论全局作用域的变量声明的区别:

  • 不使用关键字

    JavaScript:变量的作用域,window对象,关键字var/let与function

    JavaScript:变量的作用域,window对象,关键字var/let与function

    可以看到,这种声明方式,变量会成为window对象的属性,也就是随着window对象一起初始化了;

    但是图二告诉我们,它似乎不能被访问,即此时内存并没有变量a,所以输出了a is not defined,这是为什么?我也不知道;

    所以非常不推荐这种声明方式;

  • 使用关键字var

    JavaScript:变量的作用域,window对象,关键字var/let与function

    JavaScript:变量的作用域,window对象,关键字var/let与function

    可以看到,这种声明方式,变量会成为window对象的属性,也就是随着window对象一起初始化,开辟了一块内存空间用来存储变量a;

    但是此时变量a还没有被赋值,所以输出了undefined,并不会报错;

    这也是为什么不推荐使用这种方式来声明变量,因为它的全局作用域太大,甚至在被赋值前,就可以访问而不报错;

  • 使用关键字let

    JavaScript:变量的作用域,window对象,关键字var/let与function

    JavaScript:变量的作用域,window对象,关键字var/let与function

    可以看到,这种声明方式,变量不会成为window对象的属性;

    但是此时变量a,实际上已经开辟了内存区域了,但是还没有初始化,所以才会报错Cannot access 'a' before initialization

    这是一种符号大多数编程语言的关于变量声明的处理方式,也是ES6新推出的,用来避免var的声明方式,也是被推荐的声明方式;

再来说一下局部作用域的一些特殊情况,主要是关键字var

先看下面代码的输出结果:

JavaScript:变量的作用域,window对象,关键字var/let与function

JavaScript:变量的作用域,window对象,关键字var/let与function

可以看到,哪怕是在代码块中,使用var和无关键字的声明方式,依然还是会成为window对象的属性,具有全局作用域;而let声明的变量,就具有正常的局部作用域。

再看下面代码的输出结果:

JavaScript:变量的作用域,window对象,关键字var/let与function

可以看到,在函数体的代码块中,三种声明方式具有相同的局部作用域,函数体外部都访问不到;

这说明,var虽然在其他代码块中不具有局部作用域,但是在函数体中却具有局部作用域,可以称之为函数作用域;

总结:

  • 除函数体外,在任何地方使用var或者无关键字的声明方式,变量都是作为window对象的属性,具有全局作用域;
  • 在函数体中,无论何种声明方式,都是局部变量,具有局部作用域;
  • let以及const的作用域表现,是最正常的,符合大多数编程语言对变量作用域的定义;
  • 推荐使用let以及const来声明变量,酌情考虑使用var来声明变量,最好不要使用无关键字的方式来声明变量;

关键字function

声明一个函数的时候,我们可以使用关键字function进行声明;

我们先看下面代码的输出结果:

JavaScript:变量的作用域,window对象,关键字var/let与function

按照代码从上至下的顺序执行,我们可以看到,尽管函数aaa的声明代码,是在打印和调用aaa的代码之后,依然打印和调用成功了;

说明,当浏览器执行代码的时候,函数声明是先执行的,并且它成为了window对象的一个方法;

注意:

  • 只有使用function关键字才会这样,使用函数表达式和箭头函数去声明函数的时候,因为有个赋值给变量的操作,所以它是一个执行语句,浏览器会按照顺序去执行;
  • 只有直接在script标签中声明才会这样,在其他任何代码块即在{}中进行声明,因为代码块也是执行语句,浏览器会按照顺序去执行;