变量

JavaScript变量相关

部分概念和例子来源于《ES6标准入门》——阮一峰
以及红宝书《javascript高级程序设计》

var带来的变量提升

ES5中var带来的变量提升现象可以说是很常见了,所谓变量提升,就是将var a = 123这样的语句,将声明变量提升到其作用域顶端去执行,而将赋值语句留在原地执行

1
2
3
4
5
6
7
8
9
10
11
12
function test () {
console.log(a); //undefined
var a = 123;
};
test();
//上述代码a的结果是undefined,它的实际执行顺序如下:
function test () {
var a;
console.log(a);
a = 123;
}
test();

同样的,下面这样写也不会出现错误

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

函数提升

既然提到了变量提升,那就顺带说一说函数提升。
我们知道,定义一个函数可以有函数声明和函数表达式。其中,函数表达式中的函数提升规则和变量提升一样,因为就是把匿名函数赋值给一个变量,就是将函数看作一个值。但是函数声明就不一样了,它是将一整个代码块提升到作用域顶端执行。

1
2
3
4
fun(2) //2
function fun(a){
console.log(a)
}

所以下面这个例子也很容易看懂为什么输出是1

1
2
3
4
5
6
7
8
foo(); //1
var foo;
function foo () {
console.log(1);
}
foo = function () {
console.log(2);
}

函数会比变量先提升

注意两种提升结合的优先级

我们先来看一段代码

1
2
3
4
5
6
7
var foo = function(x, y){
return x - y;
}
function foo(x, y){
return x + y;
}
var num = foo(1, 2);

这段代码中num值最终为-1而不是3。这里对于foo变量先后通过函数声明和函数表达式赋值了两个函数,那么就涉及到这两个东西的优先级,规则如下:

  1. 变量声明、函数声明都会被提升到作用域顶处;
  2. 当出现相同名称时,优先级为:变量声明(foo#1) < 函数声明(foo#2) < 变量赋值(foo#3)
  3. 函数表达式的赋值语句不会被提升

也就是说上述代码实际是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//variable hoisting变量提升
var foo;//foo#1
var num;

//function declaration hoisting函数声明提升
function foo(x, y){//foo#2
return x + y;
}

//function expression NOT hoisted函数表达式不会被提升
foo = function(x, y){//foo#3
return x - y;
}

num = foo(1, 2);//这里使用foo#3

let命令

ES6新增的let 命令,用于声明变量,但其只在命令所在的代码块内有效,

1
2
3
4
5
6
{
var a = 2
let b = 2
}
console.log(a)//a
console.log(b)//a is undefine

回顾函数中关于闭包的内容、

1
2
3
4
5
6
7
8
9
10
11
12
function a(){
var arr = []
for(var i = 1; i < 10; i++){
arr[i] = function(){
console.log(i)
}
}
return arr
}
var f = a()
f[1]()//10
f[2]()//10

如果使用let,声明的变量仅在for循环内有效,则可以正常输出

1
2
3
4
5
6
7
8
9
10
11
12
function a(){
var arr = []
for(let i = 1; i < 10; i++){
arr[i] = function(){
console.log(i)
}
}
return arr
}
var f = a()
f[1]()//1
f[2]()//2

不存在变量提升

对于var命令的变量提升,多多少少会有一些奇怪,为了纠正这种现象,let命令改变了这种语法行为,它所声明的变量一定要在声明之后使用,否则便会报错

1
2
3
console.log(a)
let a = 1
//报错

暂时性死区

只要块级作用域内存在let声明的变量,它所声明的变量就绑定在了这个区域,不会受到外部的影响

1
2
3
4
5
var a = 1
if(true){
a = 2 //报错
let a
}

上述代码存在全局变量a,但是块级作用域内又用let声明了一个局部变量a,导致后者绑定了这个区域,所以在a声明前给其赋值发生了报错
因此,所谓暂时性死区,是指使用let命令声明变量前,该变量不可使用,这在语法上就称为暂时性死区(TDZ)

1
2
3
4
5
6
7
8
9
if(true){
//TDZ开始
a = 1//报错
console.log(a)//报错

let a //TDZ结束
a = 2
console.log(a)//2
}

在暂时性死区中,typeof不再是一个安全的操作符

1
2
typeof x//报错
let x

上述代码中,变量x在暂时性死区当中使用,所以报了错,但是要注意,如果没有暂时性死区,一个变量直接不被声明,typeof操作符反而不会报错

1
typeof x//undefine

因此,在不用let命令时,typeof操作符是一个绝对安全的操作符。现在es6改变了这一点,所以需要在实际编程中养成良好的编程习惯

一些比较隐蔽的暂时性死区:

1
2
3
4
5
6
7
8
function a(x = y, y = 2){
return {x, y}
}
a()//报错
//参数x的默认值等于另一个参数y,而此时y并没有声明,属于死区

var x = x//不报错
let x = x//报错

不允许重复声明变量

let不允许在相同的作用域内声明同一个变量

1
2
3
4
function a(){
let a = 10
var a = 9
}//报错

块级作用域

ES5中只有全局作用域和函数作用域,并没有块级作用域的概念,导致一些场景很不合理

1
2
3
4
for(var i = 0; i < 10; i++){
...
}
console.log(i) //10

上述代码中,变量i只用来控制循环,但循环结束后,他并没有消失,而是泄露成了全局变量。
let实际上为JS提供了块级作用域

1
2
3
4
5
6
7
8
function a(){
let n = 5
if(true){
let n = 10
}
console.log(n)
}
a()//5

所以,实际上用let构建块级作用域这种方法就可以替代使用匿名函数这种方法

1
2
3
4
5
6
7
(function(){
var a = 9
})()
//等同于
{
let a = 9
}

块级作用域与函数声明

ES5规定,不能再块级作用域中使用函数声明。如下这种代码在ES5中是非法的,因为没有块级作用域,所以if内部声明的函数有可能被提升到if外面而产生错误

1
2
3
if(true){
function a(){...}
}

由于ES6引入了块级作用域,且明确允许在块级作用域中声明函数,声明出来的函数在块级作用域之外不能使用。需要理解的是,ES6中函数声明的行为是类似于let。来看个例子

1
2
3
4
5
6
7
8
9
10
11
function a(){
console.log('i am outside')
}
(function(){
if(false){
function a(){
console.log('i am inside')
}
}
a()
})()

上述代码在ES5环境中会运行i am inside,因为if内部环境对于es5并不是块级作用域,所以if内部的函数被提升到了if的外面。
而在ES6环境下执行得到的是i am out side,所以if内部的函数对于外部运行没有任何影响。
但是!!!
在如今大多数支持ES6的浏览器中运行上述代码依然会报错,是因为如果严格遵守上述规定,会对旧代码产生影响,因此ES6允许浏览器有自己的行为方式,具体如下

  • 允许在块级作用域中声明函数
  • 函数声明类似于var,会被提升到全局作用域或函数作用域的头部
  • 同时,函数声明也会提升到所在块级作用域的头部

综上所述:还是避免在块级作用域中声明函数(晕),所以上述例子在浏览器中,实际是这样的

1
2
3
4
5
6
7
8
9
10
11
12
function a(){
console.log('i am outside')
}
(function(){
var a = undefine
if(false){
function a(){
console.log('i am inside')
}
}
a()
})()//报错,a is not a functon

const命令

基本用法:声明一个常量,一旦声明,其值不再可以改变。

1
2
const PI = 3.14159
PI = 3.14//error

const和let一样,只在声明所在的块级作用域内有效,也不会产生变量提升,因此也存在暂时性死区,。同时,也不可重复声明

const的本质

const实际上并不是保证变量的值不可以改动,而是变量指向的那个内存地址不可以改动。对于JS的引用类型,变量指向的内存地址只是一个指针,换句话说,const只保证变量不指向另一个应用类型的对象,而不保证对象本身的属性不被更改。因此使用const要非常小心。

1
2
3
4
5
const f = {}
f.age = 20
f.age//20

f = {}//报错
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2015-2021 AURORA_ZXH
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信