likes
comments
collection
share

(深入JavaScript 六)预编译、全局作用域、函数作用域、块级作用域详解

作者站长头像
站长
· 阅读数 23

前言

作用域

在全局中

在全局中,当代码执行之前会先进行代码的预编译,这个过程会将var定义的变量进行作用域提升,也会把函数提升,如下面代码,所以我们可以在声明变量和函数之前就能进行使用。

console.log(num)
var num = 123
foo()

function foo(){
    console.log("foo")
}

但是使用在声明前打印num的时候,却发现打印的是undefined,因为这时候访问num变量是还未被赋值的num,而num没被赋值所以就为undefined。这里简短说明下全局代码执行前预编译的过程:

  1. 创建执行上下文栈
  2. 创建全局执行上下文,并将全局执行上下文压入执行上下文栈中
  3. 根据代码内的变量声明和函数声明创建活跃对象AO,若变量为var创建的,则将该变量当作一个属性放入AO中并赋值为undefined,若遇到函数声明则将该函数作为属性放入对象中。拿上面代码为例子,这时的AO对象就应该为{num:undefined,foo(){console.log("foo")}}
  4. 将AO放入全局执行上下文中

所以我们可以在var变量和函数声明前就能使用,但这里注意一下,上面说的AO对象其实是针对早期的ECMA规范进行定义的,现在最新的ECMA规定的不再是一个AO对象来记录这些变量和函数,而是规定一个叫做变量环境的AE来记录这些变量和函数。

在函数中

看下列代码:

function foo(params){
    console.log(params)//123
    console.log(p)//undefined
    bar()//bar
    var p = "aaa"
    function bar(){
        console.log("bar")
    }
}
foo("123")
console.log(p)//报错
bar()//报错

以下是函数代码在执行前预编译的过程:

  1. 创建函数执行上下文,并将该函数执行上下文放进上下文执行栈里
  2. 创建函数变量环境AE、作用域链、绑定this等操作
  3. 将形参和变量声明作为属性放入到AE中,这时会将形参和var定义的变量赋值为undefined,也就是var变量的作用域提升
  4. 实参与形参进行统一
  5. 找到函数声明,并将函数名作为属性放入到AE中

所以,结合上面代码和函数预编译过程,我们可以知道为什么在函数内,var定义的变量能在声明前进行访问而不会报错,函数能在声明前进行调用。但是在外部全局中我们去访问函数内定义的变量p和调用bar函数却会报错,那是因为在全局的AE里面并不会找到p和bar,p和bar记录在了foo函数的AE中,这就形成了函数的作用域。每个定义的函数都有自己的作用域,而由函数形成的作用域就是函数作用域,函数内定义的变量和方法不能被函数外部进行访问。函数内能访问外部定义的变量和函数是因为作用域链,对于变量和函数的查找会跟随作用域链进行查找,而查找方向只能从内向外进行的。

上面说了全局作用域和函数作用域中var定义的变量,那么let和const定义的变量在这些作用域又是如何表现呢?而块级作用域又是如何的?这里我们先讲解let和const,再说块级作用域。

let和const

let和const之间的区别不是很大,const相较于let,const定义的变量不允许后续再进行修改,如果const定义的变量是一个引用类型的数据,那么就要求这个变量的引用地址不能被修改,但是属性还是能修改的,如下代码所示:

console.log(name,obj)//报错
let name = "kobe"
name = "jie"//成功修改

const age = 123
age = 111//报错

const obj = {
    num:11
}
obj.num = 12//成功
obj = {}//报错

继续看上面代码,我们知道var变量是能进行作用域提升的,那么let和const呢?答案是不能,我们在let和const定义的变量之前进行访问,会直接报错。那么这是不是意味着,let和const定义的变量不会在预编译的时候被创建出来呢?我们看ECMA262对let和const的描述:

(深入JavaScript 六)预编译、全局作用域、函数作用域、块级作用域详解 这上面的描述说明,let和const定义的变量会被预解析,并放进我们说的AE里面去,只是js引擎做了处理,在这些变量没被赋值的时候是不允许访问的,所以才会报错。所以我们可以说,let和const定义的变量是没有作用域提升的,但是不能说它们只有在被执行的时候才被创建的。

var、let定义的变量和window对象的关系

在定义var变量的时候,我们还可以通过window这个对象进行访问,并且看似var定义的变量是直接定义在了window对象上,当然,早期的全局对象GO也指代的就是window对象:

var name = "kobe"
console.log(window.name)//kobe
var obj = {age:12}
console.log(window.obj,window.obj === obj)//{age: 12} true

let o = "ooo"
console.log(window.o)//undefined

但是通过上面的代码,我们使用let声明变量,在window对象上却访问不到这个变量了,这说明let定义的变量不再是定义在window上,现在let和const定义的变量会直接记录在当前作用域的AE上,而此刻在全局作用域中,AE就不再等于window对象了,准确的说现在的ECMA规范对AE的定义也不局限AE到底是不是一个对象,只要是满足变量环境描述功能的数据结构都能作为AE。并且,现在的var变量定义的变量也不是直接在window上定义的,只是为了兼容以前的老代码,所以现在我们在全局定义var变量和函数的时候会直接与window对象进行一个绑定,我们在全局创建一个var变量,window会同步创建一个,全局我们修改var变量,window上对应的这个变量属性的值同步进行修改。

块级作用域

上面解释清楚了let和const,那么我们看看块级作用域是怎么来的,看看下面代码:

{
  console.log(age);//undefined
  console.log(p);//报错
  foo()//foo
  var age = 123;
  let p = "sss";
  function foo() {
    console.log("foo");
  }
}
console.log(age,window.age);//123 123
console.log(p);//报错

在全局中,我们直接用{}包裹起来了一段代码,这个就为一个块,而在这个块中形成的作用域就是块级作用域。在块中,我们用var定义变量,实际与在全局中定义无异,我们在块外面照样能访问到var定义的变量,并且还能从window上拿到我们定义的这个变量。但是,如果我们是用let定义的变量在外面进行访问,那么就会报错。这说明了块级作用域只对let和const定义的变量生效。其实我们还能发现,块级中定义的函数,在全局中也是能使用的,但是按照现在的规范,函数其实也应该具备被块级作用域进行限制的功能,也就是说块里面定义的函数foo,在全局中不能被调用的,但现在我们照样还是能调用块级内声明的foo函数,这个操作其实也是为了兼容老代码,系统对函数做了特殊处理,让块内的函数也能在全局中进行访问。除了变量、函数,现在规范还对class进行了块级作用域的限制:

{
    class Person{}
    let p = new Person()
}
var p2 = new Person();//报错

在块里面我们定义了一个类,块里面用类创建p对象是正常的,但是在全局中我们使用块内的Person类创建对象就会报错。

实际上,我们除了用{}来生成一个块级作用域,for、if else、switch,这些都能生成块级作用域:

if(true){
    let age = 12//这里的age就受块级作用域的限制
}

for(let i = 0;i < 10;i++){//这里的i就受块级作用域的限制

}

let name = "aaa"
switch (name) {
  case "aaa":
    let s = "sss"//这里的s就受块级作用域的限制
    break;

  default:
    break;
}

var、let和const的选择

对于var

我们要知道,var所表现出来的特殊性:比如作用域提升、window全局对象存在var声明的变量、没有块级作用域等都是一些历史遗留问题,这是JavaScript这门语言设计之初的缺陷,所以我们现在应该避免去使用var。

对于let和const

对于let和const来说,是目前开发中肯定是推荐使用的。如果一个变量你声明的时候,确定以后也会进行修改,那么优先使用let声明,如果这个变量你知道以后不会被修改,或者不知道会不会被修改,那么你都可以用const进行声明,因为这样保证了数据的安全性,使它不会被随意篡改。

转载自:https://juejin.cn/post/7239645564257583159
评论
请登录