关于变量提升那些你不知道的事

630 阅读3分钟

1. var定义的变量只会变量提升

console.log(a) // undefined
var a = 1
a = 2
console.log(a) // 2

2. function 关键字定义的变量会变量提升和定义

func()
function func () {
  console.log('ok')
}

会输出 ok

func()
var func = function () {
  console.log('ok')
}

上述代码执行会报错Uncaught TypeError: func is not a function

3. 匿名函数具名化,在函数外部无法访问

A()
var func = function A () {
  console.log('ok')
}
A()

匿名函数具名化不论是在函数定义之前还是之后都不能访问。

为什么匿名函数具名化在函数外部无法访问呢?

因为当前上下文中不会创建这个名字。这个函数执行时,在形成的私有上下文中,会把这个具名化的名字作为私有上下文的变量,对应的值就是这个函数。

函数具名化有什么作用呢?

递归调用,如果没有具名化想在函数内部递归调用,非严格模式下只能通过arguments.callee(严格模式下不支持)。

4. 条件判断无论是否成立,条件判断里面的 var和function 关键字都会进行变量提升

console.log(a) // undefined
if (false) {
  var a = 1
}
var foo = 1
function bar() {
  if (!foo) {
    console.log('a')
    var foo = 10
  }
  console.log(foo)
}
bar()

下面这道题很多人第一眼会做错,因为他们认为函数bar的if判断里面foo的值是1,这其实是错误的。函数bar的if体用 var 定义了 foo,会进行变量提升,且在if判断里面foo的值是undefined。

5. 形参赋值对变量提升的影响

var 关键字声明的变量和形参变量名一样,那么var 变量提升时,不会重复声明

var a = 1
function fn(a) {
  /*
   * EC(FN)
   *  作用域链:<EC(FN), EC(G)>
   *  形参赋值:a = 1
   *  变量提升:
   *    var a
   *    a -> 0x001 [[scope]]: EC(FN) // 不会重复声明
   *  ----------------------------
   *  函数体代码执行
   */
  console.log(a) // 1
  var a = 2
}
fn(a)

function 关键字声明的函数名和形参变量名一样,会重新定义(function 关键字的变量提升声明和定义是一起的)

var a = 1
function fn(a) {
  /*
   * EC(FN)
   *  作用域链:<EC(FN), EC(G)>
   *  形参赋值:a = 1
   *  变量提升:
   *    var a
   *    a -> [Function: a]: EC(FN) // 不会重复声明,但是需要重新赋值(定义)
   *  ----------------------------
   *  函数体代码执行
   */
  console.log(a) // [Function: a]
  function a () {}
}
fn(a)

6. 变量提升的一些变态机制

前置知识:除函数、对象等大括号外,其他大括号中出现了 let/const/function 则会单独形成块级上下文。

新版本浏览器要兼容ES3/ES5,也要兼容ES6,所以产生了一些变态机制:出现在其他大括号中的 function 不再是声明 + 定义,而是只声明。

console.log(foo) // undefined
{
  function foo() {} // 在块级作用域内,已经在变量提升时处理过了,此时在块级作用域内跳过,但是全局作用域内也有引用到,会把函数之前所做的操作给全局一份
  foo = 1
}
console.log(foo) // [Function: foo]

练习一下下面三道题,来检测一下自己对上述知识点的理解吧!

习题1

习题1是对上面1,2两知识点的综合考察

fn() // 输出5
function fn() { console.log(1) }
fn() // 输出5
function fn() { console.log(2) }
fn() // 输出5
var fn = function() { console.log(3) }
fn() // 输出3
function fn() { console.log(4) }
fn() // 输出3
function fn() { console.log(5) }
fn() // 输出3

我们需要牢记的是,var定义的变量只会变量提升,function 关键字定义的变量会变量提升和定义。所以前三个fn执行的时候输出5,第三个fn执行完之后,var 声明的 fn,会进行赋值,所以后面三个fn执行的时候输出3。

习题2

习题2是对知识点5的考察

var foo = 'hello';
(function (foo) {
  /**
  * EC(AN)
  *   作用域链:<EC(AN), EC(FN)>
  *   形参赋值:foo = 'hello'
  *   变量提升:var foo(foo 已经有了,不会重复声明)
  */
  console.log(foo) // hello
  var foo = 'world'
  console.log(foo) // world
})(foo)
console.log(foo) // hello

习题3

习题3是对知识点6的考察

console.log(foo) // undefined
{
  console.log(foo) // [Function: foo]
  function foo() {}
  foo = 1
  function foo() {}
  console.log(foo) // 1
  foo = 2
  console.log(foo) // 2
}
console.log(foo) // 1