作用域是什么
JavaScript是一门非常神奇的编程语言,以至于你没有掌握它的精髓都可以写出完整的项目,本套读书笔记记录的就是《你不知道的JavaScript上卷》中的内容,其中会夹杂这我个人对某些知识点的感悟与见解。
1.1 编译器与引擎
在讨论作用域是什么的时候不得不讲讲另外两个概念,一个是编译器另一个是引擎。
- 编译器
负责语法分析及代码生成等工作
- 引擎
负责整个JavaScript程序的编译及执行的过程
看了这两个概念后相信大家跟我一样也是一头雾水,这两个东西到底是干什么的?简单的讲就是对于一行代码:
var a = 2;
这行代码在解析与执行的时候并不是按一次来处理,而是分两次来处理。
- 首先,编译器会判断当前作用域中有没有声明过该变量,如果声明过了则忽略该语句,如果没有生成过则声明该变量
- 其次,在运行的时候引擎会在作用域中查找该变量,如果找到了就赋值
所以上述代码可以看成是两行代码:
var a;//这部分由编译器来处理,先声明一个变量
a=2;//这部分由引擎来处理,给变量赋值
通常情况下我们程序猿不需要知道什么是编译器什么是引擎,用一个高大上的词来说就是他们对我们来说是透明的,但是我们一定要知道在处理声明和赋值的时候是分为两个过程,这在就是JavaScript中变量提升(后续笔记中会讲到)的原因。
1.2 作用域
上一小节谈到了是JavaScript声明和赋值是两个过程,本章节就是围绕这两个过程来展开,首先让我们谈谈什么是作用域。
作用域: 通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。 —— [ 百度百科 ]
简单的说作用域就是变量的有效范围。JavaScript最常见的作用域就是函数作用域。就比如:
function foo(){
var a = 3;
}
console.log(a);//这里找不到变量a因为它在自己的作用域中,即函数foo所形成的作用域
变量a所在函数foo的作用域中,而外面打印a的时候,在他所处的作用域中并没有a这么一个变量,所以会报ReferenceError这样的错误。
1.3 作用域链
在JavaScript中有两大链是我们必须要掌握的,其中一个就是作用域链,另一个就是原型链。我们这里首先讲的是作用域链,它是一个非常重要的概念。
作用域链:引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找,再找不到就继续向上一级中找,直到找到该变量或者到达最外层的作用域(即全局作用域)。
这句话,什么意思呢?上代码:
var a = 3;
function bar(){
var a = 2;//如果这一行注释掉那么将会打印3
function foo(){
console.log(a);//打印2
}
foo();
}
bar();
在讲这段代码之前大家首先要知道JavaScript中函数是可以嵌套的,像上面的bar函数中嵌套了一个foo的函数一样,这一点不同于Java等某些编程语言。
上述代码调用了bar函数,该函数内部又会调用函数foo,而foo内部打印了a变量,但是函数foo内部(即函数foo作用域中)并没有声明过a这个变量,那么它会去上一作用域中查找这个变量,即他外层的函数bar的函数作用域,这个作用域中有a的声明,并且值是2,那么foo函数打印的值就是2了。如果bar中的语句”var a = 2;”是不存在的那么在bar作用域中还是没有找到,那么它就会继续去上一作用域也就是最外层的全局作用域中查找,则会找到a=3,那么这种情况下将会打印3了。
1.4 异常
相信上小节讨论作用域链的时候很多人会问那如果到了最外层的全局作用域还没有找到那么会发生什么事。本节就讨论一下这种情况。
如果查询完所有嵌套的作用域中都没有找到所需要的变量的声明和赋值语句,引擎就会抛出出ReferenceError。
什么意思呢?如下:
function bar(){
function foo(){
console.log(a);//没有找到a,抛出ReferenceError
}
foo();
}
bar();
上述代码中没有找到a这个变量的声明和赋值语句就会抛出ReferenceError。
如果查询完所有嵌套的作用域中找到了所需要的变量赋值语句但没有声明语句,全局作用域就会创建一个该变量,并返回给引擎。
这句话又是什么意思呢?如下:
function bar(){
a = 1;//找到赋值了 但全局都没有声明,所以最外层的全局作用域会创建一个该变量,故打印1
function foo(){
console.log(a);//有a的赋值返回1
}
foo();
}
bar();
上述代码中没有找到a这个变量赋值语句了,但没有声明所以最外层的全局作用域会“友好“地创建一个该变量a,故打印1。
但是有赋值语句这种情况还有另外一种情况。
在ES5中引入了一种严格模式,顾名思义严格模式是因为它对语法的检查更为严格,这种模式下禁止自动或隐式地创建全局变量。对于这种情况, 如果查询完所有嵌套的作用域中只要没有找到所需要变量的声明语句无论是否找到赋值语句,引擎都会抛出ReferenceError。我们给上面代码加上严格模式后效果如下:
"use strict";//有了这行神奇的代码编译器就知道你使用的是严格模式了
function bar(){
a = 1;//找遍所有的作用域都没有找到a的声明语句,只有赋值语句
function foo(){
console.log(a);//同样抛出ReferenceError
}
foo();
}
bar();
说到这里这节的笔记就接近尾声了,最后简绍一下另外一个与ReferenceError同样高频的一个错误叫做TypeError。
TypeError: 当给一个变量做违反该类型的操作时就会抛出该异常。
这个异常太常见了就比如给一个非函数类型的值进行函数调用,或者调用undefined的某个属性呀(umdefined没有object类型中的属性)等等都会发生这个异常,如下,代码都会报TypeError错误:
var a = 1;
a();//a是number类型的不能进行函数引用
var b
console.log(b.name);//b没有赋值,是undefined类型,该类型没有name这么个属性