一、作用域

  • 作用域是代码在运行时,某些特定部分中的变量,函数和对象的可访问性。
  • 换句话说,作用域决定了变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期

二、JavaScript 中的作用域

  • 在 JavaScript 中有两种作用域

    • 全局作用域
    • 局部作用域
  • 如果一个变量在函数外面或者大括号{}外声明,那么就定义了一个全局作用域

  • 在 ES6 之前局部作用域只包含了函数作用域,

  • ES6 为我们提供的块级作用域,也属于局部作用域

三、全局作用域

  • 拥有全局作用域的对象可以在代码的任何地方访问到, 在 js 中一般有以下几种情形拥有全局作用域:

最外层的函数以及最外层变量

1
2
3
4
5
6
var globleVariable= 'global'; // 最外层变量 function globalFunc(){ // 最外层函数
var childVariable = 'global_child'; //函数内变量 function childFunc(){ //
内层函数 console.log(childVariable); } console.log(globleVariable) }
console.log(globleVariable); // global globalFunc(); // global
console.log(childVariable) // childVariable is not defined
console.log(childFunc) // childFunc is not defined
  • globleVariable 和 globalFunc 在任何地方都可以访问到, 反之不具有全局作用域特性的变量只能在其作用域内使用。

未定义直接赋值的变量

1
2
3
function func1(){ special = 'special_variable'; var normal = 'normal_variable';
} func1(); console.log(special); //special_variable console.log(normal) //
normal is not defined
  • 我们可以在全局作用域中声明函数以及变量, 使之成为全局变量, 但是不建议这么做,因为这可能会和其他的变量名冲突,一方面如果我们再使用 const 或者 let 声明变量, 当命名发生冲突时会报错
1
2
// 变量冲突 var globleVariable = "person"; let globleVariable = "animal"; //
Error, thing has already been declared
  • 另一方面如果你使用 var 申明变量,第二个申明的同样的变量将覆盖前面的,这样会使你的代码很难调试。
1
var name = 'koala' var name = 'xiaoxiao' console.log(name); // xiaoxiao

四、局部作用域

  • 和全局作用于相反,局部作用域一般只能在固定代码片段内可以访问到。最常见的就是函数作用域

函数作用域

  • 定义在函数中的变量就在函数作用域中。并且函数在每次调用时都有一个不同的作用域。这意味着同名变量可以用在不同的函数中。因为这些变量绑定在不同的函数中,拥有不同作用域,彼此之间不能访问。
1
2
3
4
5
6
7
8
9
10
//全局作用域
function test() {
var num = 9;
// 内部可以访问
console.log("test中:" + num);
}
test() //test中:9
//test外部不能访问
console.log("test外部:" + num);
// Uncaught ReferenceError: num is not defined
  • 如果在函数中定义变量时,如果不添加 var 关键字,造成变量提升,这个变量成为一个全局变量。
1
2
3
4
5
6
function doSomeThing(){
// 在工作中一定避免这样写
thing = 'w';
console.log('内部:'+thing);
}
console.log('外部:'+thing)
  • 任何一对花括号{…}中的语句集都属于一个块, 在 es6 之前,在块语句中定义的变量将保留在它已经存在的作用域中
1
2
var name = '程序员'; for (var i = 0; i < 5; i++) { console.log(i) } // 0 1 2 3 4
{}外部:5 console.log('{}外部:' + i);

我们可以看到变量 name 和变量 i 是同级作用域。

  • 注意 ES6 块级作用域之前的变量提升:
1
2
var tmp = new Date(); function f() { console.log(tmp); if(false) { var
tmp='hello'; } } f()
  • 有人会认为输出的是当前日期。但是正确的结果是 undefined。这就是由于变量提升造成的,在这里申明提升了,定义的内容并不会提升,提升后对应的代码如下:
1
2
var tmp = new Date(); function f() { var tmp console.log(tmp); if(false) {
tmp='hello'; } }
  • console 在输出的时候,tmp 变量仅仅申明了但未定义。所以输出 undefined。虽然能够输出,但是并不推荐这种写法

  • 推荐的做法是在申明变量的时候,将所用的变量都写在作用域(全局作用域或函数作用域)的最顶上,这样代码看起来就会更清晰,更容易看出来哪个变量是来自函数作用域的,哪个又是来自作用域链.

  • 注意 重复声明

1
2
// var var name = 'koloa'; console.log(name); // koala if(true){ var name =
'程序员'; console.log(name); // 程序员 } console.log(name); // 程序员
  • 虽然看起来里面 name 申明了两次,但上面说了,js 的 var 变量只有全局作用域和函数作用域两种,且申明会被提升,因此实际上name 只会在最顶上开始的地方申明一次,var name=’程序员’的申明会被忽略,仅用于赋值。也就是说上面的代码实际上跟下面是一致的。
1
2
// var var name = 'koloa'; console.log(name); // koala if(true){ name =
'程序员'; console.log(name); // 程序员 } console.log(name); // 程序员
  • 注意 变量和函数同时提升
1
console.log(foo); var foo ='i am koala'; function foo(){} console.log(foo);
  • 输出结果是 function foo(){} 和 i am koala

  • 如果是另外一种形式呢?

1
console.log(foo); var foo ='i am koala'; var foo=function (){} console.log(foo);
  • 输出结果是 undefined 和 函数 function()

  • 对两种结果进行分析说明:

    • 第一种:函数申明。就是上面第一种,function foo(){}这种形式
    • 另一种:函数表达式。就是上面第二种,var foo=function(){}这种形式
    • 第二种形式其实就是 var 变量的声明定义,因此上面的第二种输出结果为 undefined 应该就能理解了。
    • 而第一种函数申明的形式,在提升的时候,会被整个提升上去,包括函数定义的部分!因此第一种形式跟下面的这种方式是等价的!
1
var foo=function (){} console.log(foo); foo ='i am koala';
  • 函数声明被提升到最顶上;

  • 申明只进行一次,因此后面 var foo=’i am koala’的申明会被忽略。

  • 函数申明的优先级优于变量申明,且函数声明会连带定义一起被提升(这里与变量不同)

  • 总结一下

1
2
3
4
5
6
7
console.log(foo); var foo ='i am koala'; function foo(){} console.log(foo);
//首先函数提升优于变量提升 所以函数提升位于顶部 function foo(){}
console.log(foo); //函数foo //此时 foo已经被声明了 那么 var foo就不会再此声明
而是直接赋值 //所以相当于 foo = 'i am koala'; console.log(foo); //i am koala
//而对于函数表达式 console.log(foo); var foo ='i am koala'; var foo=function
(){} console.log(foo); //就相当于: var foo console.log(foo); //undefined foo =
'i am koala'; foo=function (){} console.log(foo); //函数function()

块级作用域

  • ES6 新增了 let 和 const 命令,可以用来创建块级作用域变量,使用 let const 命令声明的变量只在 let const 命令所在代码块内有效。
  • let 声明的语法与 var 的语法一致。基本上可以用 let 来代替 var 进行变量声明,但会将变量的作用域限制在当前代码块中。块级作用域有以下几个特点:
    • 变量不会提升到代码块顶部且不允许从外部访问块级作用域内部变量
1
2
3
console.log(bar);//抛出`ReferenceErro`异常: bar `is not defined` let bar=2; for
(let i =0; i<10;i++){ console.log(i) } console.log(i);//抛出`ReferenceErro`异常:
i `is not defined`
  • 这个特点带来了许多好处,开发者需要检查代码时候,可以避免在作用域外意外但使用某些变量,而且保证了变量不会被混乱但复用,提升代码的可维护性。就像代码中的例子,一个只在 for 循环内部使用的变量 i 不会再去污染整个作用域。

  • 不允许反复声明

1
2
3
4
function test() { var name = 'koloa'; var name = '程序员'; console.log(name); //
程序员 } test() // let || const function test2() { var name1 = 'koloa'; let
name1 = '程序员'; // Uncaught SyntaxError: Identifier 'name1' has already been
declared } test2()