Web前端软件工程师

从代码开始

在我们的认知中,代码都是一行一行顺序去执行的。带着这样的认知,我们读一下以下例子:

console.log(a)
var a = 10

-----------

console.log(a)
a = 10

----------

console.log(a)
let a = 10
复制代码

这三段代码不都是在没有a的时候去输出a吗?都报错呀

var flag = true

if(flag) {
  var a = 10
}

console.log(a) //输出10
复制代码

flag为true会输出10, flag为false不会运行if里面的代码,不存在a变量,所以报错

foo()

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

var a = 10
复制代码

未定义foo的情况下执行foo,报错!

foo()

var foo = function() {
  console.log(a)
}

var a = 10
复制代码

解释同上一题

很遗憾,答案都是错的。究其原因,变量提升!

浅谈词法作用域

简单介绍一下词法作用域,向更多了解,请看理解JavaScript的词法作用域

var a = 10

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

foo(a) // foo运行在全局作用域 输出10

function baz() {
  var a = 100
  foo() // foo运行在baz包裹的作用域,输出10,并不会输出离自己进的100
}

baz() 
复制代码

从上诉输出结果可知,在代码运行前(也就是编译阶段),作用域都已经确定好了。foo永远在全局作用域下,不管他在哪里运行,foo都会输出全局作用域下的a

编译阶段代码发生了什么?

编译阶段,除了会确定作用域,还发生了变量提升。看以下例子

// 很多逻辑代码

var a = 10
复制代码

编译完成之后

var a

// 很多逻辑代码

a = 10
复制代码

由上我们可以发现:

  1. var a = 10在编译阶段变成了两句。一句为声明变量:var a,一句为给变量赋值: a=10

  2. 其中声明变量提升到当前作用域(全局作用域)的最前面,赋值变量的语句留在原地

一下是编译前后的代码,很容易发现答案。

console.log(a)
var a = 10

-----------
// 编译后:
var a
console.log(a)
a = 10
复制代码

输出undefined

console.log(a)
a = 10

---------
// 编译后:
console.log(a)
a = 10
复制代码

没有变化,报错

var flag = true

if(flag) {
  var a = 10
}

console.log(a)

-------------
// 编译后
var flag
var a

flag = true

if(flag) {
    a = 10
}

console.log(a)
复制代码

flag为false也不会报错,只会输出undefined

函数提升

除了变量声明会提升之后,函数也会提升。函数声明与函数表达式情况会不一样。看以下例子

foo()

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

---------
// 编译后
function foo() {
  console.log(1)
}

foo()
复制代码

函数声明:整个函数会提升,输出1。

foo()

var foo = function() {
    console.log(1)
}

-------------
// 编译后
var foo

foo()

foo = function() {
    console.log(1)
}
复制代码

报错 foo is not function。跟变量提升的规则一样。

重复声明/函数优先

同一个作用域下,变量名与函数声明名(重复声明)相同的情况下,会优先函数,忽略变量

foo()

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

var foo
---------
// 编译后

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

// var foo  优先函数,直接忽略这一句

foo() // 输出1

复制代码

var foo 直接被忽略掉。foo优先为一个函数。

let/const

let与const不仅不会变量提升,而且还会把变量绑定到块作用域(说起块作用域,又要提到函数作用域,又要提到暂时性死区)。看例子

if(true) {
  var a = 10
}
console.log(a) // 输出10


if(true) {
  let a = 10
}

console.log(a) // 报错, 形成块作用域
复制代码

通过a声明的变量,不经没有变量提升,而且还形成了一个if{}块作用域。全局作用域访问不到a的值

小结

  1. 编译阶段确定作用域并发生变量提升

  2. 变量的声明提升,变量的赋值留在原地

  3. 重复声明下,函数优先

  4. let/const不会发生变量提升,而且还会形成块作用域