变量
var 关键字
如果要定义变量 可以使用 var
关键字 后面跟变量名 例如:
var message;
这行代码定义了一个名为 message
的变量,可以用它保存任何类型的值(不初始化的情况下,变量会保存一个特殊值 undefined
)
var 声明作用域
使用 var
定义的变量, 意味着无论在函数内部还是外部声明的变量,只要是使用 var
声明的。其作用域都将限定在最近的函数内(如果在函数内声明)或全局作用域(如果在函数外声明)
- 如果在函数内使用
var
声明变量,则该变量的作用域将限定在包含它的函数内部。
function test() {
var message = "hi"; //局部作用域
}
test();
console.log(message); //访问出错
- 如果在任何函数之外使用
var
声明变量,则该变量将具有全局作用域,在整个程序中都可以访问到。
var message;
function test() {
message = "hi"; //全局作用域
}
test();
console.log(message); //hi
虽然可以通过省略 var
操作符定义全部变量 但不推荐这么做 在局部变量中定义的全部变量很难维护,也会造成困惑。
这是因为不能断定 var
是不是有意而为之
var 声明提升
使用 var
时 下面的代码不会报错 这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部
function foo() {
console.log(age);
var age = 26;
}
foo(); // undefined
之所以不会报错 只因为 ECMAScript 运行的时候把它看成 等价如下代码:
function foo() {
var age;
console.log(age);
age = 26;
}
foo(); // undefined
这就是所谓的提升(hoist),也就是把所有的变量声明都拉到函数作用域的顶部。 此外反复多次使用 var
声明同一个变量也没有问题
function foo() {
var age = 16;
var age = 26;
var age = 36;
console.log(age);
}
foo(); // 36
let 声明
块级作用域
var
和 let
的作用差不多,但有着非常重要的区别。 最明显的区别 let
声明的范围是块级作用域 , var
声明的范围是函数作用域。
if (true) {
var name = "Matt";
console.log(name); // Matt
}
console.log(name); // Matt
if (true) {
let age = 26;
console.log(age); // Matt
}
console.log(age); // ReferenceError:age 未定义
在这块代码中,age
变量之所以不能在 if
块外部访问被引用 是因为它的作用域仅限于if
块内部
不允许重复声明
let
也不允许同一个块作用域出现冗余声明,这样会导致报错
var name;
var name;
let age;
let age; // SyntaxError 标识符age已经声明过了
let age = 30;
console.log(30); // 30
if (true) {
let age = 26;
console.log(age); // 26
}
暂时性死区
let
与 var
另一个重要的区别,就是 let
声明的变量不会在作用域中变量提升
// name 会被提升
console.log(name); // undefined;
var name = "Matt";
// age 不会被提升
console.log(age); // ReferenceError:age 未定义
let age = 30;
在解析代码时,Javascript
引擎也会注意出现在块后面的 let
声明,只不过在此之前不能以任何方式来引用未声明的变量。在 let
之前的执行瞬间会被称为
暂时性死区
。
for 循环中的 let 声明
在 let
出来之前 for 循环定义的迭代变量会渗透到循环体外部:
for (var i = 0; i < 5; i++) {
//...循环逻辑;
}
console.log(i); // 5
使用 let
之后这个问题就消失了 因为迭代变量的作用域仅限于 for 循环块内部:
for (let i = 0; i < 5; i++) {
//...循环逻辑;
}
console.log(i); // ReferenceError:i 未定义
在使用 var
的时候,最常见的就是对迭代变量的奇特声明和修改
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
// 你可能以为会输出0,1,2,3,4;
// 实际上输出5,5,5,5,5
之所以会这样,是因为在循环退出的时候 i
已经变成了 5 。在最后在执行 setTimeout
的时候 i
指向都是同一个变量 所以最后输出的都是同一个值
使用 let
声明的循环变量 i
具有块级作用域,每次循环都会创建一个新的 i
变量,因此,setTimeout
中的回调函数将会捕获到对应循环中的i
值。
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
// 输出0,1,2,3,4;
const 声明
const
和 let
基本相同。唯一一个重要的区别就是用它声明变量的时候必须初始化变量,并且尝试修改const
声明的变量会导致运行时的错误
const age = 26;
age = 36;
// TypeError:给常量赋值
const
也不允许重复声明
const age = 26;
const age = 36;
// SynataxError
const
声明的作用域也是块
const name = "Matt";
if (true) {
const name = "Nicholas";
}
console.log(name); // Matt
const
声明的限制只适用于它指向的变量的引用,如果const
变量引用的是一个对象,那么修改这个对象内部的属性并不违反 const
的限制。
如果想让整个对象都不能修改,可以使用Object.freeze()
,这样再给属性赋值的时候虽然不会报错,但是会静默失败
const person = {};
person.name = "Nicholas"; // ok
const obj = Object.freeze({});
obj.name = "Jake";
console.log(obj.name); // undefined
不能用 const
来声明迭代变量 因为迭代变量会自增
for (const index = 0; index < 10; index++) {} // TypeError:给常量赋值
可以用const
来声明一个不会被修改的 for
循环变量,这对for-in
,for-of
特别有意义
let i = 0;
for (const j = 7; i < 5; ++i) {
console.log(j);
}
//7,7,7,7,7
for (const key in { a: 1, b: 2 }) {
console.log(key);
}
//a,b
for (const value of [1, 2, 3, 4, 5]) {
console.log(value);
}
//1,2,3,4,5