javascript 变量详解
mr00ff

变量

var 定义

定义一个变量
1
var msg;

定义但不初始化的情况,变量会保存一个特殊值 undefined

定义变量 并初始化
1
var msg = "hello word";

像这样的初始化不会将变量标识为 字符串类型, 只是一个简单的复制, 你可以随时改变保存的值,也可以改变值的类型

1
2
var msg = "hello";
msg = 250; // 合法 但不推荐!
定义多个变量

声明多个变量可用 , 号分割每个变量 (可选是否初始化)

1
2
3
4
5
6
7
8
// 定义
var demo,name,msg;

// 定义并初始化
var msg = "1231",
name = "zhangsan",
age = "18",
isStudy = true;

var 作用域

var 定义的变量会成为包含它的函数所属的局部变量**
1
2
3
4
5
6
function test(){
var msg = "test"; // 局部变量
console.log(msg);
}
test();
console.log(msg); // 报错 msg 为 undefined 未定义

这里的 msg变量 为 test 函数内部 使用 var 定义的. 调用 test 函数 会创建 msg 这个变量并给它赋值,调用后变量即被销毁,所以 在 函数外部调用 msg 这个变量会导致错误

函数内部定义变量时省略 var 操作符 可以创建一个全局变量
1
2
3
4
5
function test(){
msg = "hi";
}
test();
console.log(msg); // 输出 hi

去掉 var 操作符后, msg 就变成了全局变量, 只要调用一次 test() 就会定义这个变量, 并且可以在 函数外部访问

注意:

不要这样定义全局函数,很容易遗漏!!!!

不要这样定义全局函数,很容易遗漏!!!!

不要这样定义全局函数,很容易遗漏!!!!

在严格模式下.如果给这样未声明的变量赋值,会抛出 ReferenceError 错误

var 声明提升

使用 var 关键词 声明的变量会自动提升到函数作用域顶部,所以下面书写的代码不会报错

1
2
3
4
function test(){
console.log(age);
var age = 18;
}

之所以不会报错, 是因为 ECMAScript 运行时会把它看成等价于如下代码

1
2
3
4
function test(){
var age = 18;
console.log(age);
}

这就是所谓的 提升(hoist) , 也就是把所有变量声明都啦到函数作用域顶部

反复多次使用 var 声明同一个变量也没问题

运行时 变量结果会读取最后一次赋值

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

let 声明

letvar 的区别是 let 声明的范围是 块作用域,而 var 声明的范围是 函数作用域

1
2
3
4
5
6
7
function test(){
if (true){
var age = 18;
console.log(age); // 18
}
console.log(age); // 18
}
1
2
3
4
5
6
7
8
9
10
function test2(){
if (true){
// 块作用域开始
let age = 11;
console.log(age); // 11
// 块作用域结束
}
console.log(age); // ReferenceError age 没有定义
}

使用 let 定义的变量不能再 if 块 外部被引用, 是因为它的作用域限于该块的内部

块作用域是函数作用域的子集, 因此 var 的作用域限制同样也适用于 let

let 也不允许在同一个块作用域内重复声明遍变量
1
2
let age;
let age; // SyntaxError 标识符 age 已经声明过了

letvar 嵌套使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
var name = "zhangsan";
console.log(name); // zhangsan
if (true){
var name = "lisi";
console.log(name); // lisi
}

let age = 30;
console.log(age);
if (true){
let age = 18;
console.log(age); // 18
}

javascript 引擎会记录用于变量声明的标识符和其所在的块作用域, 因此嵌套的标识符不会报错,而是因为同一个块中没有重复声明

letvar 这两个关键词声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在
1
2
3
4
5
var name;
let name; // SyntaxError 语法错误

let age;
var age; // SyntaxError 语法错误

letvar 的区别

暂时性死区

letvar 的另一个重要的区别, 就是 let 声明的变量不会在作用域中被提升

1
2
3
4
5
6
// name 会背提升
console.log(name); // undefined
var name = "张三";

console.log(age); // ReferenceError age 未定义
let age = 19;

在解析代码时, JavaScript 引擎也会注意出现在块后面的 let 声明, 只不过在此之前不能以任何方式来引用未声明的变量

在 let 声明前的执行瞬间被称为 暂时性死区(temporal dead zone) , 所以 在此阶段引用任何后面才声明的变量则会抛出 ReferenceError

使用 let声明变量 必须要按照 先声明 后使用的原则!

使用 let声明变量 必须要按照 先声明 后使用的原则!

使用 let声明变量 必须要按照 先声明 后使用的原则!

全局声明

var 关键词不同, 使用 let 在全局作用域中声明的变量 不会成为 window 对象的属性 (var 声明的变量则会)

1
2
3
4
5
var name = "张三";
console.log(window.name); // 张三

let age = 18;
console.log(window.age); // undefined

不过, let 声明任然是在全局作用域中发生的, 相应变量会在页面的生命周期内存续, 因此, 为了避免 SyntaxError , 必须要保证页面不会重复声明同一个变量

声明条件

使用 var 声明变量时, 由于声明会被提升, JavaScript 引擎会自动将多余的声明在作用域顶部合并为一个声明.

因为 let 的作用域是块 ,所以不可能检查前面是否使用的 let 声明过同名变量也就不可能在没有声明的情况下声明它

1
2
3
4
5
6
7
8
9
10
11
12
13
<script> 
var name = 'Nicholas';
let age = 26;
</script>
<script>
// 假设脚本不确定页面中是否已经声明了同名变量
// 那它可以假设还没有声明过
var name = 'Matt';
// 这里没问题,因为可以被作为一个提升声明来处理
// 不需要检查之前是否声明过同名变量
let age = 36;
// 如果 age 之前声明过,这里会报错
</script>

使用 try/catch 语句 或 typeof 操作符也不能解决, 因为 条件块中 let 声明的作用域 仅限于 该块.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script> 
let name = 'Nicholas';
let age = 36;
</script>
<script>
// 假设脚本不确定页面中是否已经声明了同名变量
// 那它可以假设还没有声明过
if (typeof name === 'undefined') {
// name 被限制在 if {} 块的作用域内
let name;
}

// 因此这个赋值形同全局赋值
name = 'Matt';
try {
console.log(age); // 如果 age 没有声明过,则会报错
}
catch(error) {
let age;
}
// age 被限制在 catch {}块的作用域内
// 因此这个赋值形同全局赋值
age = 26;
</script>
for 循环中的 let 声明

在 let 出现前 , for 循环定义的迭代变量 会渗透到循环体外部

1
2
3
4
for(var i; i < 5; i++){
// TODO
}
console.log(i); // 5

改用 let 之后,这个问题就消失了, 因为 迭代变量的作用域仅限于 for 循环块内部

1
2
3
4
for(let i;i < 5; i++){
// TODO
}
console.log(i); // ReferenceError i 未定义

在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改

1
2
3
4
5
for (var i;i<5; i++){
setTimeout(()=> console.log(i),0);
}
// 你以为可能会输出 0,1,2,3,4
// 实际上会输出 5,5,5,5,5

之所以这样 是因为在退出循环时, 迭代变量保存的是导致循环退出的值 : 5 , 在之后的执行超市逻辑时, 所有的 i 都是同一个变量,因而输出的都是同一个最终值

使用 let 迭代变量时, JavaScript 引擎在后台会为每一个迭代循环声明一个新的迭代变量

每个 setTimeout 引用的都是不同的变量实例, 所以 console.log 输出的是我们期望的值,也就是循环过程中每个迭代变量的值

1
2
3
4
5
6
for(let i; i < 5; i++){
setTimeout(
() => console.log(i),
0);
}
// 会输出 0, 1, 2, 3, 4

这种迭代声明一个独立变量实例的行为适用于所有风格的 for 循环, 包括 for-in 和 for-of 循环


const 声明

const 的行为与 let 基本相同, 唯一一个重要的区别就是 用 const 来声明变量时必须同时初始化变量,且 尝试修改 const 声明的变量会导致运行时错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const age = 11;
age = 22; // TypeError 给常量赋值

// const 也不允许重复声明
const name = "xx";
const name = "pp"; // SyntaxError

// const 声明的作用域也是块
const name = "oo";
if (true){
const name = "xx";
console.log(name); // xx
}
console.log(name); // oo

const 声明的限制只适用于它指向的变量的引用,换句话说, 如果 const 变量引用的是一个对象, 那么修改这个对象内部的属性并不违反 const 的限制

1
2
3
const person = {};
person.name = "张三"; // 这是可以的

JavaScript 引擎会为 for 循环中的 let 声明分别创建独立的变量实例, 虽然 const 变量 和 let 变量 很相似, 但是不能用 const 来声明迭代变量 (因为迭代变量会自增)

1
for (const i = 0; i < 10; ++i) {} // TypeError:给常量赋值

不过,如果你只想用 const 声明一个不会被修改的 for 循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对 for-of 和 for-in 循环特别有意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

在 for 循环块作用域中,只是使用 const 声明变量,不重复赋值也是可以用的

所谓特别的意义

const 的作用域仅限于 块,那么 可以在 for 作用域中 用 const 来声明变量,达到不反复开辟内存的作用


声明风格及最佳实践

ECMAScript 6 增加 let 和 const 从客观上为这门语言更精准的声明作用域和语义提供了更友好的支持,行为怪异的 var 所造成的各种问题 已经让 JavaScript 社区为之苦恼很多年, 随着这两个新关键词的出现, 新的 有助于提升代码质量的最佳实践也逐渐显现,

1, 不使用 var

​ 有了 let 和 const , 大多数开发者会发现自己不再需要 var 了, 限制自己只使用 let 和 const 有助于提升代码质量, 因为变量有了明确的作用域, 声明位置, 以及不变的值

2, const 优先, let 次之

使用 const 声明可以让浏览器运行时强制保持变量不变, 也可以让静态代码分析工具提前发现不合法的赋值操作. 因此 很多开发者认为应该优先使用 const 来声明变量, 只在前提知道未来会有修改时,再使用 let ,这样可以让开发者更有信心的推断某些变量的值永远不会变, 同时也能迅速发现因为意外赋值导致的非预期行为

  • 本文标题:javascript 变量详解
  • 本文作者:mr00ff
  • 创建时间:2021-06-18 16:48:22
  • 本文链接:https://www.sysxo.com/javascript/javascript变量.html
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论