Fangjian

FE / 生活是一种态度

导航
 » 主页
 » 项目
 » Github
 » 关于我

变量的声明提升 与 TDZ [js]

04 Jul 2014 » js

此文章主要是想指出,通常我们认为js中 var定义的变量会发生声明提升,而let/const 定义的变量不会发生声明的提升

其实:let/const/var 定义的变量 都会发生声明提升

声明提升

先来看个例子


var  a = 1

;(function() {
  console.log(a)
  let a = 2
})()

如果没有发生变量声明提升,那么console.log应该输出 1

但实际上此处发生了 ReferenceError(引用错误) 表明一个不存在的变量被引用。

那麽:var/[let、const] 声明的变量在发生声明提升时,初始化行为不同

  • var 声明的变量初始化为 (undefined)

  • let/const 声明的变量会保持为未初始(uninitialised)化状态

(function() {
  console.log(a)  // undefined, a 在这里会被初始化为 undefined
  console.log(b)  // ReferenceError, b 在这里会保持为未初始化的状态不变
  var a = 1
  let b = 2
})()

TDZ

概念:TDZ 并不是某个地方, 或是内存中的某个区域,而是变量被声明和被初始化之间的这段时间

例子:

let a = "outer"

;(function() {
  // 内部的 a 变量在这里被声明,TDZ 开始的地方
  
  console.log(a)  // ReferenceError
  
  let a = "inner"  // a 变量被初始化,TDZ 结束
 })()

稍微复杂一点的例子:

var a = a // 没有问题

let b = b //  ReferenceError,在这里b并没有完全的被初始化,还处在 TDZ 当中

ES6 中函数默认参数的计算顺序是从左到右进行的

// 函数的参数 a 想要得到参数 b 的值,而参数 b 这时还未被初始化, 处在 TDZ 中, 所以会报 ReferenceError 的错误
function foo(a = b, b) {
  console.log(a)
}

foo(undefined, 1)
// 
function foo(a, b = a) {
    // 这里的 a 已经被初始化,所以 b 可以取到 a 的值
  console.log(b)
}

foo(1, undefined)

看了以上的例子估计还是不能理解,为什么会是这样子的? 函数参数的默认值有自己的作用域?

再看看下面的三个例子:


let y = 1;
function foo(x = function(){console.log(y)}, y = 2) {
  x(); // 2
  y = 3;
  x(); // 3
}
foo();
console.log(y); //1


let y = 1;
function foo(x = function(){console.log(y)}) {
  let y = 3;
  x(); // 1
}
foo();


function foo(x = function(){console.log(y)}) {
  let y = 3;
  x(); // ReferenceError: y is not defined
}
foo();

很让人困惑,感觉是存在3个作用域,全局/参数/函数体。参数默认值的函数,可以访问参数中定义的,和参数外定义(outer/global)的变量,不能访问函数体中定义的变量。 一个需要注意的地方是,如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的,即先是当前函数的作用域,然后才是全局作用域。

params scope

参数在有默认值的情况下,此时会创建一个新的作用域,这个作用域既不是函数作用域,也不是全局作用域。

// 默认值,并不会肯定从全局去取,而是从 params scope 中取

var x = 1;

function foo(x = x) { // throws!
  console.log(x);
}

foo()

总结

  • 如果参数存在默认值,则有三个环境 environment( environment in ES6 = scope in ES5). Outer environment / parameters environment / function body environment.

  • parameters environment 可以访问自己和外层,不能访问函数体内的变量。

  • 函数体内可以修改 parameters env 里定义的 formal parameters 的值,不能重新定义(除非用var……)。

提示

参数变量、函数体内变量、全局变量别用一样的名字

不要用 var 来定义变量,总是用 let 或 const


思考


// reference error: x is not defined. 因为这里 x 是处在 TDZ(Temporal Dead Zone)。

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

foo();

参考文献:

params default value & params environment & TDZ

ES6 Notes: Default values of parameters

相关文章