JavaScript作用域解析以及例题

773 阅读7分钟

​作用域是对于JavaScript执行顺序理解中非常关键的一环,也是面试题中经常出现的,因为其灵活程度,很多新手在这里吃尽苦头。所以出一篇简单的教程教大家如何快速掌握JavaScript作用域的使用的。

​这里我们先抛弃ES6中的let const等声明方式,虽然他让JavaScript严谨性变强,但是我们还是需要先抛弃他们,理解这其中的细节之处,到时候无论面试还是开发,都能够避免很多潜在问题。

§ 什么是作用域

​作用域决定了这些变量的可访问性(可见性)。

​也就是说我在程序的任意位置什么一个变量,这个变量在程序的那些位置有效可以访问,哪些位置无效不可访问都是作用域所决定的。而变量在JavaScript中声明的方式有多种,如var声明,函数声明和参数声明。

var a = 0; // var声明变量

function a(){} //函数声明方式

function fn(a){} //参数a为参数声明

​当然还有一种特殊情况,如果是这样算什么声明了?

var a = function(){} //这也是var声明 只不过是函数赋值给变量a

​而变量声明之后实际上是在内存中开辟一块空间,用来存储这个变量所表示的数据。而JavaScript不像C/C++一样,对于内存的申请释放是需要手动的,JavaScript的内存控制都是全自动的,也就是说自动申请,自动销毁。所以这就是为什么这个变量可能在某一个块能访问,因为内存没有销毁,但是如果销毁了,那么程序就自然而然访问不到这个变量了。所以这就是作用域的作用,也就是JavaScript变量可访问性控制。

§ 作用域的分类

​JavaScript的作用域只有两种:

  • 全局作用域
  • 函数作用域

§ 全局作用域与全局变量

​在非函数内代码内都是全局作用域,一般在页面的script标签内居多。而在全局作用域并且非函数内声明的变量就叫做全局变量了。全局变量很好理解,也就是在页面中所有的脚本和函数都能访问到他,包括其他script脚本内。

<script type="text/javascript">
    var a = 0; // 全局作用域中声明全局变量a  
    function b(){} // 全局作用域内声明的函数b
    console.log(a); // 打印0 可访问全局变量a
</script>
<script  type="text/javascript">
    console.log(a); // 打印0 可访问全局变量a
</script>	

§ 函数作用域

​函数作用域顾名思义就是在函数内声明的变量了,并且这些变量只能在函数内访问,一旦是函数外部就无法访问了。

function fn(){
    var a = 0;
    console.log(a);// 执行函数的时候会打印在函数内部声明的变量a, 值为0
}
fn();
console.log(a);// 报错:变量a未定义 原因:无法在函数外部访问函数内部的变量

​从概念上来说还是很好懂得,但是凡事就怕万一,也就是我们所说的特殊情况。由于JavaScript是一门动态类型语言,所以对于类型的定义要求并不严格,可以随时修改。并且,也是可以允许变量重名的情况,虽然这种设定并不合理(这也是很多JAVA,C++等语言的开发者很讨厌JavaScript的原因),但是我们作为JavaScript学习者,一定要学会处理这些特殊情况,因为JavaScript不会因为不合理而报错,所以我们来看一些特殊情况。

function fn(a){
    var a
    function a(){}
}
//提问: 改函数作用域里面的变量a是指哪个声明方式 函数? 参数? var

​在以上例子中函数fn中作用域出现了3个不同方式的同一名称变量a,如果我们在函数内调用a的时候,a指向的是优先级关系的。总的来说就是函数 > 参数 > var。也就是说,如果三者都存在,那么a指向函数,如果只存在参数和var,那么a指向为参数。

§ 作用域的解析顺序

​重点来了,因为JavaScript没有类的存在(ES6中的class是作为语法糖而存在,本质不是真正的类),也没有代码块的说法,所以它的执行顺序有那么一丝不同的。大体一致,执行顺序为从上到下,但是在执行顺序执行还有一个过程叫做词法解析,即在程序执行之前解析代码中的变。

var a = 33;
function fu(a) {
    console.log(a);
    var a = 123;
    console.log(a);
    function a() {
        console.log(a);
    }
    console.log(b);
    var b = function () {}
    console.log(b);
    function b() { }
}
console.log(a)
fu(2);

词法解析的核心目的就是在执行某一个作用域代码之前的变量提取,以上面一个代码为例子,他拥有两个作用域,一个是全局,另外一个是fu函数作用域。代码在执行过程中第一步就是执行全局作用域,而全局作用域的第一步就是词法解析,解析步骤: 词法解析:提取全局作用域中的变量,也就是var关键字声明的变量a和function声明的函数b 代码执行: 代码执行的规律就是从上到下,而且要注意的是,赋值也算是代码的执行

  • 33数字赋值给变量a > 第一个行代码
  • console.log(a) > 此时控制台输出的这个a在全局中是属于全局作用域中var关键字声明的变量a,在上一步的时候被赋值了33,所以此处打印33
  • 执行函数fu,并且传入参数2。

这里需要注意的是,函数声明不是语句执行,是属于声明也就是在词法解析时候的步骤,所以在代码执行的步骤是直接跳过的

​至此,全局作用的代码全部执行完毕下一步就是函数作用域的代码了,因为在代码执行的第3步,执行了函数fu,此刻函数内的作用域即生效了(函数如果没有执行,函数内的任何语句都不要关心,可以直接忽略不计)

function fu(a) {
	console.log(a);
	var a = 123;
	console.log(a);
	function a() {
	  	console.log(a);
	}
	console.log(b);
	var b = function () {}
	console.log(b);
	function b() { }
}

​进入到函数作用域,步骤跟在全局是一致的。

词法解析:

函数内有提取参数a,var声明变量a,函数a,var声明变量b和函数声明b 因为第一步出现了同名变量,所以需要要确定变量唯一指向,依据函数 > 参数 > var的原则,变量a为函数,变量b为函数 代码执行:

  • 第一行控制台打印变量a,之前在词法解析步骤确认a为函数,所以这里打印  [Function: a]
  • 第二行给变量a赋值123,第三行再打印a就从函数变成了值123。因为JavaScript是属于动态类型语言,所以不同类型数值赋值不会有错误
  • 第四行声明函数,是属于词法解析步骤,可忽略。
  • 接着就是控制台打印b,词法解析过程中b为函数。所以这处打印为函数
  • 下一行为变量b被赋值为函数,这时是一个新的空函数,但本质还是函数,所以下一行打印b仍然为函数[Function: b]

以上就是对作用域的基本用法,当然一个案例不能概括所有的情况,还是需要大家大量练习才会熟练。下面有一些练习题,各位可以去练练

var a = 123;
function fun(){
  console.log(a);
  var a = 456;
  console.log(a);
}
fun();
console.log(a);

//---------------------------------------------------

var b = 123;
function fun(){
  console.log("b = "+b);
  b = 456;
}
fun();
console.log("b = "+b);

//---------------------------------------------------

var c = 123;
function fun(c){
  console.log("c = "+c);
  c = 456;
}
fun();
console.log("c = "+c);

//---------------------------------------------------

var d = 123;
function fun(d){
  console.log("d1 = "+d);
  d = 456;
}
fun(789);
console.log("d = "+d);

//---------------------------------------------------
var a = 33;
function fu(a) {
  console.log(a);
  var a = 123;
  console.log(a);
  function a() {
    console.log(a);
  }
  console.log(b);
  var b = function () { }
  console.log(b);
  function b() { }
}
console.log(a)
fu(2);

//---------------------------------------------------
function test(a, b) {
  console.log(a);
  c = 0;
  var c;
  a = 3;
  b = 2;
  console.log(b);
  function b() { }
  function d() { }
  console.log(d);
}
test(2);


//---------------------------------------------------

fn();
console.log(a);
var a = 10;
console.log(a);
function fn(){
  var a = 1;

}

​ 要写出每个例子的打印的步骤以及解析步骤