我们都知道声明一个变量,可以有很多种方式。
但是变量本身到底是什么意思呢?
事实上,在JavaScript里面的深浅拷贝能够给到我们一定的答案。
定义一个 a
变量,给予其赋值{age: 18}
,可以得知,JavaScript首先在内存中开辟了一个空间,用以存放该对象;并将其引用指向变量a
。并且可知,在深拷贝之后,a
变量和b
变量的值并不相等,可以说此时a
变量就代表了左侧内存当中的对象。
通俗的说,我们讨论一块豆腐的时候,可以称其为王粮、水欢、水判、水林、白物,当然肯定有豆腐(很抱歉,吃饭的时候和同事讨论过豆腐,就忍不住)。
我们在用这么多名字讨论的时候,豆腐,就是那块豆腐,客观存在的一块豆腐,你可以用很多名字 ---- 变量去称呼它。这就是变量,他的值等于这个对象,它本身不等于。
就像豆腐这个名字指的是一块豆腐,但是这个名字不等于一块豆腐。
变量,就像人名一样,你可以随意的更改,就好比变量一样,但是,他的值不仅指向这个对象,还包括路径(你家的地址,你是唯一的)。
是的,路径。深拷贝之所以称之为深拷贝,就在于b
对象从a
对象拷贝过来的时候,它在内存当中开辟了一个新的空间,它们所代表的路径已经发生改变。
内存/a ==> 内存/b
,这是一个很粗略的比喻,但是当a
和b
在比较的时候,会从两个不同的路径取出两个独一无二不同的对象,它们自然不可能相等。
声明变量
这个话题肯定不能避开这三兄弟,var、let、const
,
var | let | const |
---|---|---|
存在变量提升 | 不存在变量提升 | 不存在变量提升 |
代码块外部可调用 | 存在块级作用域 | 存在块级作用域 |
赋值后可更改 | 赋值后不可更改 | 赋值后不可更改 |
变量提升
console.log(age)
var age = 18;
==> undefined
如果这里使用let、const
结果显而易见,会直接报错。但是var不会,JavaScript
扫描到这里的时候,会将var
声明提升到顶部。也就是说在打印该变量的时候,是已经声明了的,不过还没有赋值而已。
同时因为const
不可修改值的原因,所以在一定程度上,let
可以称之为var
的完美进阶版。
作用域
在这三个声明当中,var
不被块级作用域限制。比如:
{
var a = 1;
}
console.log(a) // a => 1
在大量使用es6
语法后,你会觉得不可思议,为什么能在括号外面能访问到块级代码里面的作用域。
{
let b = 2;
}
console.log(b) // b => a is not defined
我们完全可以得知,var
不会被块级代码限制。反之,let
和const
是有严格的代码作用域。
再来看这道经典面试题:
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i)
}, 1000);
}
不知道多少人倒在这道题上,按照常理,应该或者说我们希望它打印1,2,3,4,5。
但很明显,答案是5个5。
事实上,因为var
不受块级作用域限制,该循环每次调取的i都是同一个,当循环结束时,i的值是5,当然会打印5个5了。
赋值
三个声明变量的方式里面,let
和var
的值都是可以直接修改的。但是const
不行,区别在于用它声明变量时必须同时初始化变量,且尝试修改const
声明的变量会导致运行时错误。
const a;
a = 1;
// 报错
// ----------
const b = 1;
const b = 2;
// 报错
const
和let
声明的变量一样,不允许重复声明,作用域也是块。
const
声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const
的限制。属性和值均可以修改。
声明风格
不使用var
明显,var
有各种各样的缺点,我们应当使用他的完美进阶版let
。
强制放弃使用var
,会让我们的代码更健康,因为变量有了明确的作用域、声明位置、以及不变的值。
优先级
使用const
声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。所以,const
应该是开发者声明变量的首要选择。
当然,如果变量的预期不确定,或者在预定的未来会发生改变,你应该使用let
。这样会让开发者更有信息的推断某些变量的值会不会发生改变,也能迅速发现因赋值导致的非预期行为。
关键字与保留字
同时我们要注意到,不是所有单词都是可以用来作变量的,为了和JavaScript
内置的一些单词有所区别,避免出错,ECMA-262 第6版描述了所有保留的关键字:
关键字 | |||
---|---|---|---|
break | do | in | typeof |
case | else | instanceof | var |
catch | export | new | void |
class | extends | return | while |
const | finally | super | with |
continue | for | switch | yield |
debugger | function | this | default |
if | throw | delete | import |
try |
这些保留的关键字不能作为标识符或者属性使用。而且,ECMA-262 第6版还描述一组未来的保留字:
始终保留
enum
严格模式下保留
严格模式 | |||
---|---|---|---|
implements | package | public | interface |
protected | static | let | private |
模块代码中保留
await
在未来的保留字中,你可以作为标识符和属性去使用,但是最好不要使用,以确保兼容过去和未来的 ECMAScript 版本。
Q.E.D.