我们都知道声明一个变量,可以有很多种方式。

但是变量本身到底是什么意思呢?

事实上,在JavaScript里面的深浅拷贝能够给到我们一定的答案。

深浅拷贝

定义一个 a 变量,给予其赋值{age: 18},可以得知,JavaScript首先在内存中开辟了一个空间,用以存放该对象;并将其引用指向变量a。并且可知,在深拷贝之后,a变量和b变量的值并不相等,可以说此时a变量就代表了左侧内存当中的对象。

通俗的说,我们讨论一块豆腐的时候,可以称其为王粮、水欢、水判、水林、白物,当然肯定有豆腐(很抱歉,吃饭的时候和同事讨论过豆腐,就忍不住)。

我们在用这么多名字讨论的时候,豆腐,就是那块豆腐,客观存在的一块豆腐,你可以用很多名字 ---- 变量去称呼它。这就是变量,他的值等于这个对象,它本身不等于。

就像豆腐这个名字指的是一块豆腐,但是这个名字不等于一块豆腐。

变量,就像人名一样,你可以随意的更改,就好比变量一样,但是,他的值不仅指向这个对象,还包括路径(你家的地址,你是唯一的)。

是的,路径。深拷贝之所以称之为深拷贝,就在于b对象从a对象拷贝过来的时候,它在内存当中开辟了一个新的空间,它们所代表的路径已经发生改变。

内存/a ==> 内存/b,这是一个很粗略的比喻,但是当ab在比较的时候,会从两个不同的路径取出两个独一无二不同的对象,它们自然不可能相等。

声明变量

这个话题肯定不能避开这三兄弟,var、let、const

varletconst
存在变量提升不存在变量提升不存在变量提升
代码块外部可调用存在块级作用域存在块级作用域
赋值后可更改赋值后不可更改赋值后不可更改

变量提升

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不会被块级代码限制。反之,letconst是有严格的代码作用域。

再来看这道经典面试题:

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了。

赋值

三个声明变量的方式里面,letvar的值都是可以直接修改的。但是const不行,区别在于用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。

const a;
a = 1; 
// 报错
// ----------
const b = 1;
const b = 2;
// 报错

constlet声明的变量一样,不允许重复声明,作用域也是块。

Assignment

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

属性和值均可以修改。

声明风格

不使用var

明显,var有各种各样的缺点,我们应当使用他的完美进阶版let

强制放弃使用var,会让我们的代码更健康,因为变量有了明确的作用域、声明位置、以及不变的值。

优先级

使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。所以,const应该是开发者声明变量的首要选择。

当然,如果变量的预期不确定,或者在预定的未来会发生改变,你应该使用let。这样会让开发者更有信息的推断某些变量的值会不会发生改变,也能迅速发现因赋值导致的非预期行为。

关键字与保留字

同时我们要注意到,不是所有单词都是可以用来作变量的,为了和JavaScript内置的一些单词有所区别,避免出错,ECMA-262 第6版描述了所有保留的关键字:

关键字
breakdointypeof
caseelseinstanceofvar
catchexportnewvoid
classextendsreturnwhile
constfinallysuperwith
continueforswitchyield
debuggerfunctionthisdefault
ifthrowdeleteimport
try

这些保留的关键字不能作为标识符或者属性使用。而且,ECMA-262 第6版还描述一组未来的保留字:

始终保留

enum

严格模式下保留

严格模式
implementspackagepublicinterface
protectedstaticletprivate

模块代码中保留

await

在未来的保留字中,你可以作为标识符和属性去使用,但是最好不要使用,以确保兼容过去和未来的 ECMAScript 版本。

Q.E.D.


I thrive to be a creator of things.