一、作用域
js中的作用域,简单来说就是变量和函数的起作用范围,js中有三种作用域:
全局作用域
全局作用域中的所有变量和函数都能在任何地方被访问到,所有直接写在script标签内或单独的js文件中的变量和函数,都是属于全局作用域。
全局作用域中有一个window对象,在全局作用域中定义的所有变量和函数,都会变成window的属性和方法。
let a = 10;
function foo(){
console.log(a);
}
console.log(window.a); // 10
window.foo(); // foo
上面的代码中,变量a和函数foo的作用域就是全局作用域,它们在声明的时候,被当做window对象的属性和方法保存在了window对象里面。
函数作用域
在js中,每声明一次函数,就会创建一个函数作用域,范围是函数的形参及其内部的变量和函数。
function foo(){
let a = 10;
function bar() {
console.log('bar')
}
}
console.log(a); // a is not defined
bar(); // bar is not defined
在这个例子里,变量a和函数bar都是在函数foo里面声明的,所以它们的作用域就是在函数foo内部,foo外部无法访问到(这里先不讲闭包)。
块级作用域
在ES6之前,是没有块级作用域的,只有全局作用域和函数作用域,在ES6引入了let和const之后,才有了块级作用域。
块级作用域是由{…}包裹的一片区域,里面的变量只能在{…}内部被访问到。
{
let a = 10;
let foo = function (){
console.log("foo");
}
function bar(){
console.log("bar");
}
}
console.log(a); // a is not defined
foo(); // foo is not defined
bar(); // bar
可以看出,用let声明的变量a和函数foo都不能被外部访问,而使用函数声明式的bar却可以被外部访问。这样说明要使得块级作用域起作用,就必须使用let或者const声明(这里不讨论let和const的区别)。
块级作用域还有一些特性,比如不存在变量提升,变量不能重复声明,这些都是let和const的特性导致的。
二、作用域链
先来看一个经典的图:
这个图里里面存在三层的作用域,最外层的是全局作用域,第二层是函数foo的作用域,第三层是函数bar的作用域,这个三层作用域就形成了层级嵌套的关系。
接着看下面这个例子:
let a = 10;
function foo(){
let b = 20;
function bar(){
let c = 30
function baz(){
console.log(a + b + c);
}
baz();
}
bar();
}
foo(); // 60
这里也有层级嵌套的作用域,函数baz在自己的作用域中没找到变量a,b,c,就到其父作用域bar函数作用域中查找,找到了变量c,然后继续往bar的父作用域foo函数作用域中查找,找到了变量b,继续往全局作用域中查找,找到了变量a,这时候才结束查找。
看了上面的例子,大概就能知道作用域链是个什么了,作用域链就是这一层层嵌套的作用域组成的,本层作用域查不到变量,就往外层查找,直到找到第一个符合的变量就不查找了,要是找到全局作用域也没找到,则报变量未定义的错误。
let a = 10;
function foo(){
let a = 20;
function bar(){
console.log(a);
}
bar();
}
foo(); // 20
上面这个例子就说明了第一个符合的变量是什么意思,函数bar在foo作用域中找到了变量a,就不会继续往更高一层的全局作用域中查找。