【JavaScript奇葩之美Ver.1】var,确定你真的会使它么?

前言

在茫茫前端路各种专业化、对象化、工程化的今天,你以为光会切个图写点DIV\CSS就能称前端了?

在Node.js火爆当道,在AngularJS\Express\Vue\React等各种框架层出不穷之时,万变不离其宗,唯一能让自己心安的,或许还是老老实实一点点打下扎实高端大气上档次的JavaScript基础,玩转玩遍它才是王道呀。自从学习JavaScript以来,它的无拘无束的「自由」实在令人印象深刻且赞,但同时也带来太多的奇葩与有些别扭的语法糖。

其实,换个角度出发,这些有时候让人不那么愉快的一面,不也正是JavaScript独特的美和气质么?OK,接下来我就一点点把平常遇到的一些“美”给记录下来,以供小伙伴们一同欣赏:)

发现美

前两天在V2EX看到一个童鞋的疑问:

JavaScript 里 var x = i = 0 这个写法是什么意思呢?

这还有啥意思!不就是最最最简单的赋值语句嘛!C\C++\Java等各种语言都有的嘛!

1
int x = i = 0;

打完收工!奏是这么简单,哟嘿哟嘿~

等等,既然你这么问,想必来者不善,不妨又是给我埋的一个坑吧~~那我们来动手看看是否有坑!

1
2
3
4
5
6
function wss() {
var x = i = 0;
}
wss();
alert(x); //'x' is not defined 这个结果正常嘛!毕竟函数内变量的作用范围是局限在函数内部的哟~
alert(i); //0 卧槽!肿么可以输出0来!!!恭喜你,果断入坑!

这样的结果,怎能不感慨“好一个人参惨淡”,在了解这个背后的原因之前,我们先来了解下JavaScript官方对于变量Var的一些定义及解释:

  • 变量的声明

    1.使用var多次声明同一个变量,是合法的; 重复的声明并初始化变量值,只是相当于普通的赋值语句;

    2.读取一个未声明的变量值,JavaScript会产生一个错误;

    3.尝试给一个未经var声明的变量赋值,JavaScript会隐式声明该变量,隐式声明的变量被直接创建为全局变量;

    4.无论是全局变量还是局部变量,最好都建议使用var进行声明(strict严格模式下不声明var变量直接报错);

看完这样的定义,一下子秒懂,有木有?!

刚才我们上面童鞋那样的写法,直接会导致 i 变成全局变量, 所以等同写法:

1
2
3
4
function wss() {
i = 0;
var x = i;
}

所以,看看,这是一个多么糟糕的写法,直接会隐式创建一个全局变量,继而引发变量污染。所以,我们切记,在日常开发过程中,不要这么写!

延伸一

那到这里,突然还有个疑问,你们说,这显式创建的全局变量和隐式创建的全局变量,自然都是window的属性,那他们俩者真的完全一样么?我们通过ECMAScrpit提供的属性特性查询方法,来发现他们之间的秘密。

1
2
3
4
5
6
7
8
var a = 2;
window.b = 3;
c = 4;
this.d = 4;
Object.getOwnPropertyDescriptor(window, 'a'); //configurable:false,enumerable:true,value:2,writable:true
Object.getOwnPropertyDescriptor(window, 'b'); //configurable:true,enumerable:true,value:3,writable:true
Object.getOwnPropertyDescriptor(window, 'c'); //configurable:true,enumerable:true,value:4,writable:true
Object.getOwnPropertyDescriptor(window, 'd'); //configurable:true,enumerable:true,value:4,writable:true

通过上面的查询发现,原来还是有差别的,我们再用delete删除属性来验证下,配置性configurable为false的属性无法删除,即通过变量var显式声明全局对象的属性无法删除。

1
2
3
4
delete a; // 无法删除
delete b; // 可删除
delete c; // 可删除
delete d; // 可删除

结论就是:在全局作用域下,使用var定义的变量不可以delete,没有var定义的变量可以delete。也就说明隐式全局变量严格来说不是真正的变量,而是全局对象的属性,因为属性可以通过delete删除,而变量不可以。

延伸二

我们试着来再看这一段小程序,看看结果如何?

1
2
3
4
5
6
7
var wss = "global";
function f(){
alert(wss); //提示undefined
var wss = "local";
alert(wss); //提示local
}
f();

为何会是这样的结果?这个跟使用var定义变量还会提升变量声明有关。

在函数内部显式声明了一个与全局变量同名的局部变量,第一次alert, 解析器就会先将变量声明提升,所以就导致局部变量wss是已经被声明了,但是没有初始化值,所以提示undefined; 而第二次的alert,wss = “local” 之后的,也就是局部变量wss完成了初始化,所以提示为local。

该段代码实际相当于:

1
2
3
4
5
6
7
8
var wss = "global";
function f(){
var wss; //声明局部变量wss,未初始化
alert(wss);
wss = "local"; //初始化wss
alert(wss);
}
f();

那如果不使用var定义变量呢?

1
2
3
4
5
6
7
8
9
var wss = "global";
function f(){
alert(wss); //提示global
alert(wss2); //'wss2' is not defined
wss = "local";
wss2 = "local2";
alert(wss); //提示local
}
f();
结论就是:使用var定义的变量的声明提前,未使用var定义的变量声明不变。

结尾

JavaScript一个如此简单的赋值语句背后,竟隐藏着太多奇葩的特性,而这些一个不留神,就将让你的程序陷入一个变量污染噩梦里。美,也是有杀伤力滴~