变量
变量分类
js中的变量分为两种:全局变量和局部变量。全局变量很好理解,就是在js任何地方都能够调用的变量;而局部变量就只能在函数的内部才能够调用的变量。
var a=10;
function show(){
console.log('a:'+a); //a:10
var b=2;
console.log('inside b:'+b); //inside b:2
}
show();
console.log('outside b:'+b); //b is no defined
在上面的代码中,变量a就是一个全局变量,在函数的内部能够调用。但是这里的变量b就是局部变量,当函数结束调用后,变量b就被回收了,因此在函数外部调用失败。
另外需要特别注意的是:
如果在声明局部变量时不用var声明,那么这个变量自动“提升”为全局变量。
var a=10;
function show(){
console.log('a:'+a); //a:10
b=2;
console.log('inside b:'+b); //inside b:2
}
show();
console.log('outside b:'+b); //outside b:2
对比两段代码,如果你在声明b=2时没有写var,那么b就隐式地声明为全局变量,在函数外面还是能够被调用到的。虽然使用全局变量能够在任何地方调用,很方便,但是全局变量的优点也给他带来了缺点:一直占用内存:全局变量保存在静态存储区,如果全局变量过多会占多大量内存,严重影响页面的性能。 影响了函数的独立性:一般函数都是传入参数和传出返回值进行运算的,如果函数依赖于全局变量,破坏了函数的这种独立性,同时也降低了函数的可移植性。 因此我们在定义变量时一般要尽可能少的定义全局变量。
函数声明优先于变量声明
下面我们通过一段代码来说明.
var a;
function a(){
}
console.log(typeof a); //function
或许有人是认为函数声明在后面的原因,那么调换一下位置。
function a(){
}
var a;
console.log(typeof a); //function
调换位置后变量a的类型还是function,这时候声明变量a的语句还没有起作用,被函数声明覆盖了。因此函数声明优于变量的声明。
但是如果我们在声明的同时给a赋值
function a(){
}
var a='xyf';
console.log(typeof a); //string
我们将其调换一下位置再次进行验证。
var a='xyf';
function a(){
}
console.log(typeof a); //string
可以看到,给变量a进行赋值后,不管变量a在哪,其类型变为字符串类型,上面两段代码相当于如下代码:
function a(){
}
var a;
a='xyf';
console.log(typeof a); //string
a最后被赋值为字符串,因此a的类型自然是字符串
作用域
ES5 只有全局作用域和函数作用域,没有块级作用域
console.log(v); //undefined
var v = "world";
由于变量v在没有赋值前使用了,所以是undefined。其实这里存在着声明的提前。
当前作用域内的声明都会提升到作用域的最前面,包括变量和函数的声明
由于es5作用域中的声明都会被提升到作用域的最前面,所以,上面的代码相当于:
var v;
console.log(v); //undefined
v = "world";
这样就能很清晰地理解为什么变量v是undefined的了。 下面我们把变量v放到一个方法中去:
if(true){
var v = "hello";
}
console.log(v); //hello
在这里由于es5没有块级作用域,所以if方法没有“形成”一个封闭的作用域,并不能够“阻挡”外面的代码获取里面的变量。
函数作用域
我们再把变量v放到函数中去
function show(){
var v='world';
}
show();
console.log(v); //undefined
由于show函数是一个函数作用域,“阻挡”外面的代码获取里面变量(并不能阻挡里面的代码获取外面的变量),所以函数外部并不能获取到函数里面的变量v。因此证明了es5中只有函数作用域,没有块级作用域。 再来看下面的一段代码:
var v='hello';
function show(){
console.log(v); //undefined
var v='world';
}
show();
不是说这边show函数中能够获取到函数外面的变量的么?但是由于这边是一个函数作用域,而函数作用域存在着变量声明的提前,因此,上面的代码相当于下面的代码:
var v='hello';
function show(){
var v;
console.log(v); //undefined
v='world';
}
show();
这里把变量v的声明放到了整个函数作用域的最前面,因此显示为undefined。理解了上面的代码,相信下面的代码也不难理解了。
var v = "hello";
(function(){
console.log(v);
var v = "world";
})();
在这里自执行函数形成了函数作用域
需要注意的是 变量提升只提升函数的声明,并不提升函数的定义
show(); //show is not a function
var show = function(){
//...
}
show(); //成功运行
为什么这边定义的函数就不能执行呢?在这里我们需要明白函数在js中是如何进行定义的。函数有两种定义方式,一种是函数声明,另一种是函数表达式。那么什么是函数声明什么是函数表达式呢?
//函数声明
function show(){
//....
}
//函数表达式
var show=function(){
//...
}
- 乍一看,他们长得很像,写法都差不多,但是实际上还是有区别的。js的解析器对函数声明和函数表达式并不是一视同仁的对待的,有点“种族歧视”的意思在里面。
- 函数声明就像是“一等公民”,js会优先读取,确保在执行前就已经被解析了,所以函数声明放在当前作用域的任何地方都能够被调用,甚至放到调用函数声明之后面。
- 而函数表达式就显得比较“普通”,和一般的变量一样,只有到执行到该行时才进行解析,因此,调用函数表达式要在定义后进行使用。
块级作用域
-
JavaScript 语句通过代码块的形式进行组合。
-
块由左花括号开始,由右花括号结束。
-
块的作用是使语句序列一起执行。
-
JavaScript 函数是将语句组合在块中的典型例子。
ES6 在这个基础上引申出来一个叫做“块级作用域”的概念,即“ {} 中间的部分是一个块级作用域”。例如:for 循环、 if 逻辑判断、while 循环等语句后面的 {} 都是一个块级作用域。
没有块级作用域缺点:
- 内层变量可能会覆盖外层变量。
- 用来计数的循环变量泄露为全局变量
内层变量覆盖外层变量
var a = 'apple';
function testFn() {
console.log(a); // undefined
if (false) {
var a = 'AAA'; //如果没有这一行,if语句上面的a应该输出为apple
}
}
testFn();
//------------------
//由于ES5没有块级作用域,另外var声明的变量会产生变量提升现象,因此上面的案例等同于下面的代码
var a = 'apple';
function testFn() {
var a;
console.log(a); // undefined
if (false) {
a = 'AAA';
}
}
testFn();
循环变量泄露为全局变量
//不存在块级作用域的var语法
for (var i = 0; i < 10; i++) {
//handle
}
console.log(i); //10
//------------
//for循环被看作是块级作用域的let语法
for (let x = 0; x < 10; x++) {
//handle
}
console.log(x); //Error: x is not defined
es6块级作用域
没有内层变量覆盖外层变量的现象
//let命令声明的变量a
let a = 'apple';
function testFn() {
console.log(a); // apple
if (false) {
let a = 'AAA';
}
}
testFn();
//------------
//const命令声明的常量b
const b = 'banana';
function testFn2() {
console.log(b); // banana
if (false) {
const b = 'AAA';
}
}
testFn2();
不存在变量泄露为全局变量现象
//块级作用域对var命令无效,a变量泄露到外层作用域了
if (true) {
var a = 'apple';
}
console.log(a); //apple
//let由于块级作用域的作用,不会泄露为全局变量
if (true) {
let b = 'banana';
}
console.log(b); //Error: b is not defined
//const由于块级作用域的作用,也不会泄露为全局变量
if (true) {
const o = 'orange';
}
console.log(o); //o is not defined
支持“{}”划分作用域的语法
{
let a = 'apple';
{
let a = 'aaa';
{
let a = 'AAA';
}
}
}
上述案例的每一对“{}”都是一个块级作用域,而且互不干扰。