ECMAScript 的变量是松散类型,可以用于保存任何类型的数据
3 个关键字用于声明变量:var、const 和 let
var 可以在所有版本中使用,const 和 let 只能在 ES6 及更晚的版本中使用
命名
方法
匈牙利命名法
变量名 = 属性 + 类型 + 对象描述
Int整数型 iFloat浮点型 flBoolean布尔 bString字符串 sArray数组 aObject对象 oFunction函数 fnRegular Expression正则 re
驼峰命名法
1. 全部小写
-
单词与单词间用下划线分割
var get_node_child;
2. 大小写混合
-
大驼峰
每个单词首字母大写
var GetNodeChild; -
小驼峰
第一个单词首字母小写,后续首字母大写
var getNodeChild;
规则
ECMAScript 中所有都区分大小写
-
首字符
字母、下划线 ( _ ) 或者美元符号 ( $ )
-
组成
字母、数字、下划线 ( _ ) 或者美元符号 ( $ )
-
禁忌
JavaScript 中的关键字、保留字、true、false 和 null 都不能作为标识符
声明
关键字
格式:关键字 变量名 (不初始化的情况下,变量会保存特殊值 undefined)
let name;
console.log(name); // 返回undefined
var
-
声明提升( hoist )
使用 var 声明的变量会自动提升到函数作用域的顶部,但是赋值不会提升
function fun() {
console.log(age);
var age = 25;
}
fun(); // undefined
let
-
暂时性死区( temporal dead zone )
let 声明的变量不会在作用域中被提升
console.log(name); // undefined
var name = "John";
console.log(newName); // ReferenceError: newName 没有定义
let newName = "John";
在解析代码时,JavaScript 引擎也会注意出现在块后面的 let 声明,只不过在此之前不能以任何方式来引用未声明的变量。在 let 声明之前的执行瞬间被称为“暂时性死区”( temporal dead zone ),在此 阶段引用任何后面才声明的变量都会抛出 ReferenceError
const
const 行为与 let 基本相同,需要注意的是 const 声明变量的同时必须初始化变量
-
不允许重复声明
-
作用域范围是块
-
不能修改常量的值
-
不能用 const 来声明迭代变量(因为迭代变量会自增)
for(const i = 0; i < 10; i++) {} // 报错
不过可以用 const 声明一个不会修改的 for 循环变量
let i = 0;
for(const j = 7; i < 5; i++) {
console.log(j);
}
// 7, 7, 7, 7, 7
批量定义
在一条语句中用逗号分隔每个变量及可选的初始化
let message = "world",
found = false,
age = 25;
其中插入空格和换行不是必需的,其目的只是为了利于阅读
流程
- 先声明,后读写
- 先赋值,后运算
不同点
let和const在全局声明的变量不会保存在顶层对象( window <global> )里面,而var会
1.主要的区别在于lexicalEnvironment用于存储函数声明和变量( let 和 const )绑定,而ObjectEnvironment仅用于存储变量( var )绑定
2.由于let、const这类的词法环境都属于Declarative Environment Records和函数、类这些一样,在单独的存储空间,var这类,属于Object Environment Record,会挂载到某个对象上,也会沿着原型链去向上查找
var u = 23;
console.log(window.u === u); // true
let o = 9;
console.log(window.o); // undefined
变量类型
原始值 ( primitive value )
-
占用固定空间,保存在栈中
-
不能有属性,尽管不报错 (只有引用值可以动态添加使用的属性)
let name = "Nicholas";name.age = 27;console.log(name.age); // undefined -
使用 typeof 检测数据的类型
-
保存与复制的是值本身
-
复制是把值作为副本传递给其他变量,副本被其他变量改变后,也不会影响到自身的值
-
在内存中通过变量把一个原始值赋值到另一个变量是,原始值会被复制到新变量得位置,两个变量可以独立使用,互不干扰
let num1 = 5;let num2 = num1;
-
引用类型 ( reference value )
-
占用空间不固定,保存在堆内存中
-
使用 instanceof 检测数据的类型
通过 instanceof 操作符检测任何引用值和 Object 构造函数都会返回 true;但如果用 instanceof 检测原始值,则始终返回 false
-
使用 new() 方法构造出的对象是引用型
-
保存与复制是指向对象的一个指针
- 值会保存在全局作用域的堆内存上,堆内存里的值被任一对象改变了,其他对象再指向它时,会返回最后一次被修改成的值
- 把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置,相当于是把这个值在堆内存中的地址告诉了另外一个变量,让另外一个变量也能通过这个地址访问和使用到这个值。因为有两个变量都指向同一个对象,所以就符合了第一条的逻辑
let obj1 = new Object();
let onj2 = obj1;
obj1.name = "Nicholas";
console.log(obj2.name); // Nicholas
误区:当在局部作用域中修改对象而变化反映到全局时,就意味着参数就是按引用传递的
function setName(obj) {
obj.name = "John";
obj = new Object();
obj.name = "Greg";
console.log(obj.name); // Greg
}
let person = new Object();
setName(person);
console.log(person.name); // John
当 person 传入 setName() 时,其 name 属性被设置为"John"。然后变量 obj 被设置为一个新对象且 name 属性被设置为"Greg"。如果 person 是按引用传递的,那么 person 应该自动将指针改为指向 name 为"Greg"的对象;结果并非如此,这表示函数中参数的值改变之后,原始的引用仍然没变。当 obj 在函数内部被重写时,它变成了一个指向本地对象的指针,而本地对象在函数执行结束时就被销毁了
作用域
全局变量
可以在任何位置调用到全局变量
- 在函数体外定义的变量可作用至全局
- 在函数内部省略关键字去声明一个变量,该变量会变成全局变量
虽然可以通过省略关键字操作符定义全局变量,但不推荐这么做,会导致后期代码难以维护
省略var关键字
function demo() {
message = 'hello'; // 全局变量
}
demo();
console.log(message); // hello
let 在全局作用域中声明的变量不会成为 window 对象的属性,var 声明的变量则会;不过 let 声明仍然在全局作用域中发生
var a = 2;
alert(window.a); // 2
let b = 3;
alert(window.b); // undefined
局部变量
在当前块作用域内调用的变量以及函数的参数变量
var
- **var 声明范围是函数作用域**
使用 var 在函数内部定义一个变量,形成函数作用域中的局部变量;当函数在退出时被销毁。
```
function demo() {
var message = 'hello'; // 局部变量
}
demo();
console.log(message); // undefined
```
- **var 会忽略块级作用域**
```
for(var i = 0; i < 5; i++) {
}
console.log(i); // 5
```
let
- **let 声明范围是块作用域 { }** *(比 var 的声明范围更加严苛)*
let 声明的变量无法在 if 块外部被引用;因为块作用域是函数作用域的子集,所以 var 的作用域限制同样会限制 let
```
if(true) {
var name = "Matt";
console.log(name); // Matt
}
console.log(name); // Matt
if(true) {
let newName = 'John';
console.log(newName); // John
}
console.log(newName); // ReferenceError: newName 没有定义
```
- **let 在 for 循环定义的迭代变量不会渗透至循环体外部**
因为迭代变量作用域仅限于 for 循环块内部,而且 JS 后台会为每次迭代循环声明一个新的迭代变量
```
for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 输出 5、5、5、5、5(因为在退出循环时,迭代变量保存的是导致循环退出的值:5)
for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 会输出 0、1、2、3、4
```
- **同一作用域不能出现冗余声明,否则会报错;此外,声明冗余报错并不会因为混用 let 和 var 而受影响**
同一作用域下声明变量
```
let age;
let age; // SyntaxError; 表示标识符age已经声明过了
```
不同作用域声明变量
```
let age = 30;
console.log(age); // 30
if(true) {
let age = 26;
console.log(age); // 26
}
```
var、let 混用
```
let num;
var num; // SyntaxError;
var name;
let name; // SyntaxError;
```
优先级
- 局部变量高于同名全局变量
- 参数变量高于同名全局变量
- 局部变量高于同名参数变量
全局变量对象始终时作用域链的最后一个变量对象
特性
1. 作用域链
- 内层函数可访问外层函数的局部变量
- 外层函数不能访问内层函数的局部变量
2. 生命周期
-
全局变量
除非被显示删除,否则会一直存在
-
局部变量
自声明起至函数运行完毕或被显示删除
-
回收机制
- 标记清除 ( mark-and-sweep )
- 引用计数 ( reference counting )
如文中有描述不对的地方,还请各位同仁斧削