JavaScript之变量

60 阅读4分钟

1 作用域

在讲变量之前,我们应该先了解JavaScript有哪几种作用域,在ES6之前,JavaScript将作用域分为全局作用域局部作用域

1.1 全局作用域

全局作用域对应的是全局变量,那什么是全局变量呢?

简单概括一下:不在任何函数内声明的变量(显式定义)或在函数内省略var声明的变量隐式定义)都称为全局变量,它在同一个页面文件中的所有脚本内都可以使用。

例如:

var a = 1;
function foo() {
    console.log("我在函数内部访问:", a);
}
foo(); // 我在函数内部访问: 1
if (true) {
    console.log("我在块级内部访问:", a); // 我在块级内部访问: 1
}

a在全局作用域使用var声明,在函数和if里面都可以访问,这就是a声明的全局变量的作用域为全局作用域。

1.2 局部作用域

在引入ES6之前,JavaScript的作用域还只有函数作用域这个概念,在ES6引入了let和const之后,才出现了块级作用域

1.2.1 函数作用域

函数作用域,又称局部变量:在函数体内利用var关键字定义的变量称为局部变量,它仅在该函数体内有效。

例如:

function foo() {
    var a = 1;
    console.log("我在函数内部访问:", a);
    function test() {
        console.log("我在函数test内部访问:", a);
    }
    test(); // 我在函数test内部访问:1
}
foo(); // 我在函数内部访问:1
console.log("我在块级内部访问:", a); // ReferenceError: a is not defined

可以看到,当我们在函数内部用var定义局部变量之后,我们只能在函数foo内部去访问声明的变量,在foo外部访问就会出现错误。

1.2.2 块级作用域

块级作用域是在ES6引入了let和const之后出现的概念,let或const声明的变量为块级变量, 常见的有if、for和while。

例如:

if (true) {
    let a = 1;
    console.log("我在if内部访问:", a); // 我在if内部访问:1
}
console.log("我在if外部访问:", a); // ReferenceError: a is not defined

2 闭包

在有了作用域的概念之后,我们就可以对闭包进行了解了。

2.1 什么是闭包?

闭包,即将函数内部变量私有化,以另一种形式返回,让在函数外部也能对该变量进行访问。

例如:

function foo() {
  var a = 2;
  function bar() { 
  console.log( a );
  }
  return bar;
 }
 var baz = foo();
 baz(); // 2 —— 朋友,这就是闭包的效果

以上函数bar就形成了一个闭包,闭包所达到的一个效果就是能在外部去访问一个局部变量。

在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。由于看上去 foo() 的内容不会再被使用,所以很 自然地会考虑对其进行回收。

而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域?原来是 bar()本身在使用。

拜 bar() 所声明的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用。

bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。

2.2 闭包的特点

  1. 作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
  2. 一个闭包就是当一个函数返回时,一个没有释放资源的栈区。

因此,在我们使用闭包时要谨慎,以免造成内存泄露。

3 附录

3.1 const误区

在我们的印象里,const声明的变量好像都是只读的,即改变时会报错,这是真的吗? 例如:

const PI = 3.141592653589793;
PI = 3.14; // 报错
PI = PI + 10; // 报错

但是如果当我们这样去执行代码呢?

const obj = {
    a: 1,
    b: 2,
};
obj.a = 3;
obj.c = 4;
console.log(obj); // { a: 3, b: 2, c: 4 }

好像没有出现我们预想的报错,为什么呢?

因为const 定义的变量并非常量,并非不可变,它定义了一个常量引用一个值。使用 const 定义的对象或者数组,其实是可变的。也可以理解成const是声明一个地址,当地址里为基本变量时,const为只读,为引用变量时,const可变。

小结:即const声明的变量不可以被重新用 "=" 赋值,但可以通过其他方式改变其内部属性或值

3.2 块级作用域的替代方案

在ES6没有引入let之前,块级作用域是怎样实现的呢?

答案是使用catch。

例如:

// ES6 块级作用域
{
	let a = 2;
	console.log(a); // 2
}
console.log(a); // ReferenceError
// ES6之前
try {
    throw 2;
} catch (a) {
    console.log(a); // 2
}
console.log(a); // ReferenceError