var
用于声明变量,变量声明的同时,可以赋值也可不赋值(不赋值的情况下,变量会保存一个特殊值 undefined
),后续可以更改变量的值。
- 注意1:
- 变量名可以包含字母,数字,下划线和美元符号,但要以字母开头【也可以以$和_开头(但一般不这么用)】。
- 变量名是大小写敏感的(y和Y是不同的变量)
- 保留字(如JavaScript关键字)不能作为变量名使用
- 注意2:var会发生“变量提升”现象。
之所以不会报错,是因为 ECMAScript 运行时把它看成等价于如下代码:function foo () { console.log(age); var age = 20; } foo(); // undefined
这就是所谓的“变量提升”,也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次使用 var 声明同一个变量也没有问题:function foo () { var age; console.log(age); age = 20; } foo(); // undefined
这里,function foo () { var age = 20; var age = 25; var age = 30; console.log(age); } foo(); // 30 ### var 声明作用域 使用 `var` 操作符定义的变量会成为 包含它的函数的局部变量。比如,使用 `var` 在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁: ```js function test () { var message = 'hi'; // 局部变量 } test(); console.log(message); // 出错!
message
变量是在函数内部使用var
定义的。函数为test()
,调用它就会创建这个变量并给该变量赋值;调用之后变量随即被销毁,因此实例中的最后一行会导致错误。
不过,在函数内定义变量时省略 var 操作符,可以创建一个全部变量。
去掉之前的 var 操作符之后,message 就变成了全局变量。只要调用一次函数 test() ,就会定义这个变量,并且可以在函数外部访问到。function test () { message = 'hi'; // 全局变量 } test(); console.log(message); // "hi"
注意:虽然可以省略 var 操作符定义全局变量,但不推荐这么做。因为在局部作用域中定义的全局变量很难维护,也会造成困扰。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出
ReferenceError
。
let
它的用法类似于var,但是所声明的变量,只在 let 命令所在的代码块内有效。 看下面例子:
if (true) {
var name = 'hg';
console.log(name); // hg
}
console.log(name); // hg
if (true) {
let age = 20;
console.log(age); // 20
}
console.log(age); // ReferenceError:age 没有定义
在这里,age 变量之所以不能再 if 块外部被引用,是因为它的作用域仅限于该块内部。
由于块作用域是函数作用域的子集,因此适用于 var 的作用域限制同样也适用于 let 。
- 注意1:let不允许在相同作用域内,重复声明同一个变量。
栗子2:// 报错 function() { let a = 10; var a = 1; } // 报错 function() { let a = 10; let a = 1; } // 报错 function func(arg) { let arg; } // 不报错 function func(arg) { { let arg; } } ``` - 注意2:let不像var那样会发生“变量提升”现象。 ## const **声明一个只读的常量。一旦声明,常量的值就不能改变;且声明变量时,就必须立即初始化,不能留到以后赋值。** - 注意1:`const` 的作用域与 `let` 命令相同:只在声明所在的块级作用域内有效。 - 注意2:`const` 命令声明的常量也是不提升,只能在声明的位置后面使用。 - 注意3:`const` 声明的常量,也与 `let` 一样不可重复声明。 - 注意4:对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。`const` 声明的限制只适用于它指向的变量的引用。**const 命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,也就是说,如果 const 变量引用的是一个对象,那么修改整个对象内部的属性并不违反 const 限制。** 栗子1: ```js const foo = {}; foo.prop = 123; console.log(foo.prop); // 123 foo = {}; // TypeError:"foo" is read-only // 上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把 foo 指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
const a = []; a.push('Hello'); // 可执行 a.length = 0; // 可执行 a = ['Dave']; // 报错 // 上面代码中,常量 a 是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给 a ,就会报错。
变量提升
关于变量提升,还有一些东西可以探讨一下。首先,变量提升是指把变量声明提升到当前执行环境的最顶端。
看一个例子:
console.log(foo); // 输出undefined
console.log(bar); // 报错ReferenceError
var foo = 2;
let bar = 2;
// 以上代码可看作:
var foo;
console.log(foo); // undefined
console.log(bar); // 报错ReferenceError
foo = 2;
let bar = 2;
上面代码中,由于 var
发生了“变量提升”现象,将 foo
的声明提升到了 console.log(foo)
前面,即脚本开始运行时,变量 foo
已经存在了,但是没有值,所以会输出 undefined
。
变量 bar
用 let命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。
优先级
var
和function
的变量提升是有优先级的,且function
的高于var
的。(另外,如果函数名字相同,后面函数会覆盖前面的函数。)var a; function a() {}; console.log(a) // ƒ a() {} // 可以隐式地理解为: function a() {}; var a; console.log(a); // ƒ a() {}
- 当遇到函数和变量同名且都会被提升的情况,由于函数声明优先级比较高,因此变量声明会被函数声明所覆盖,但是可以重新赋值。
alert(a); // 输出:function a(){ alert('我是函数') } function a() { alert('我是函数') } var a = '我是变量'; alert(a); //输出:'我是变量' // 上面代码可以隐式的理解为: function a(){alert('我是函数')} var a; // undefined alert(a); //输出:function a(){ alert('我是函数') } a = '我是变量';//赋值 alert(a); //输出:'我是变量'
- var 和 function 的变量提升优先级显而易见,但如果是函数声明
function foo(){}
和函数表达式var foo = function(){}
呢?
上面的代码并不是说函数声明的提升优先级高于函数表达式,而是因为当遇到函数表达式的时候,首先会将关键字+变量名提升到当前执行环境的最顶端,也就是console.log(f1) // function f1(){} console.log(f2) // undefined var f2 = function() {} // 函数表达式 function f1() {} // 函数声明
var f2
先被提升,然而此时f2
的值为undefined
,所以f2
打印值为undefined
。