Skip to main content

变量

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
warning

虽然可以通过省略 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 声明

块级作用域

varlet 的作用差不多,但有着非常重要的区别。 最明显的区别 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
}

暂时性死区

letvar 另一个重要的区别,就是 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 声明

constlet 基本相同。唯一一个重要的区别就是用它声明变量的时候必须初始化变量,并且尝试修改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