什么是作用域?
在JS中,作用域就是变量和函数生效的区域,也就是当前执行期上下文中可访问变量、对象和函数的集合。
在JS中,能够定义全局作用域和函数局部作用域,随着ES6的到来,又带来了块级作用域,通过let 和 const 的控制,加强对作用域的控制。
JS全局作用域
变量在函数外定义,即为全局变量,全局变量有全局作用域: 网页中所有脚本和函数均可使用,也就是说在代码中任何地方都能访问到的变量拥有全局作用域。
以下三种情况拥有全局作用域:
最外层函数和在最外层函数外面定义的变量拥有全局作用域
var carName = "xiaoming";
// 此处可调用 carName 变量
function myFunction() {
// 函数内可调用 carName 变量
var age = '18'
}
//函数外不可调用 age 变量
2.所有未定义直接被赋值的变量为全局变量,也就果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量,拥有全局作用域
// 此处可调用 carName 变量
function myFunction() {
carName = "xiaoming";
// 此处可调用 carName 变量
}
3.所有window对象的属性拥有全局作用域
window对象的内置属性都拥有全局作用域,例如window.name、window.localtion、window.top等
全局作用域的弊端:如果我们写了很多行JS代码,变量定义都没有用函数包括,那么他们就全部在全局作用域中。这样就会污染全局命名空间,容易引起命名冲突。这就是为什么要模块化开发,使用模块化开发,当前模块内定义的所有变量都不会被外泄和暴露,不会污染到外面,不会对其他库和JS脚本造成影响。这是函数作用域的一个体现。
JS函数局部作用域
变量在函数内声明,变量只能在函数内部访问,为局部作用域。
// 此处不能调用 carName 变量
function myFunction() {
var carName = xiaoming";
// 函数内可调用 carName 变量
}
块级作用域
块级作用域通过let和const对变量进行声明,就可以把变量限制在当前代码块中。在块级作用域中定义的变量,它的作用域只会在当前的作用域下有效。let 和const的声明方式与 var 相同,但需要注意的是const 一般用于声明常量,其值一旦被设定便不可被更改。
形式:{ 在大括号内就是块级作用域 }
{
let a = 10;
var b = 10;
{
let c = 10;
console.log(a) //a=10
}
console.log(c) //Uncaught ReferenceError: c is not defined
}
console.log(b) //b=10
console.log(a) //a Uncaught ReferenceError: a is not defined
作用域链
首先我们来看看什么是执行期上下文?
执行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。
JavaScript里一切都是对象,包括函数。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是作用域,其中存储了运行期上下文的集合,包含了函数被创建的作用域中对象的集合,称为函数的作用域链。
总结来说,就是:作用域中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。作用域链决定了哪些数据能被函数访问。当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。
有了作用域链,每次执行代码,查找变量时,都是从作用域链的顶端依次向下查找。
接下来,我们看下面这段代码,分析它的作用域链:
function a() {
function b () {
var b = 234
}
var a = 123
b();
}
var global = 100;
a();
1.定义a函数,产生一个a的作用域,生产一个Global Object(全局对象,下文简称GO);
2.执行a函数,产生作用域链,生成a的Activation Object (执行期上下文,下文简称AO);
3.执行a函数的过程中,定义了b函数,b是在a的基础上生成的,再生出一个b的AO;
此时作用域链的结构是:
0: bAO;
1: aAO;
2: GO
在函数执行过程中,查找变量都是从上到下。
在函数执行结束之后,销毁上下文的顺序也是先生成的最后被销毁。即:先销毁bAO,再到aAO,回到函数最初的状态(GO一直在)。当函数下次执行时,再产生新的aAO,bAO。
有了作用域链,在访问变量时,如果自己的作用域中没有,会一层一层向上寻找,如果找到全局作用域还是没找到,就宣布放弃,通过作用域链可以更清晰明了每个变量作用的范围。
var a = 100
function f1() {
var b = 200
function f2() {
var c = 300
console.log(a) // 100 本作用域没有,顺作用域链向父作用域找
console.log(b) // 200 本作用域没有,顺作用域链向父作用域找
console.log(c) // 300 本作用域的变量
}
f2()
}
f1()
总结
作用域就是变量和函数生效的区域,也就是当前执行期上下文中可访问变量、对象和函数的集合;
作用域链,就是由当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。
作用域链有一个非常重要的特性,那就是作用域中的值是在函数创建的时候,就已经被存储了,是静态的。所谓静态,就是说作用域中的值一旦被确定了,永远不会变。函数可以永远不被调用,但是作用域中的值在函数创建的时候就已经被写入了,并且存储在函数作用域链对象里面。