作用域是对于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;
}
要写出每个例子的打印的步骤以及解析步骤