js中的变量和作用域

158 阅读7分钟

变量

变量分类

  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';
        }
    }
}

上述案例的每一对“{}”都是一个块级作用域,而且互不干扰。