在函数部分提到:ES6之前,JavaScript只有全局作用域,函数作用域,没有块作用域({}作用域),例如:
if(true){
var a = 3
}
console.log(a) // 3
for(var i=1;i<4;i++){
//...
}
console.log(i) //4
而在Java语言中,是有块作用域的哦,这也是JavaScript与Java语言的一个区别。下面我们进一步的来了解JavaScript作用域的相关知识。
一、作用域
域,通俗的理解就是一块“地盘”,从字面理解,它是一个静态的概念。
同样的,作用域呢,就是一个代码段所在的区域,编写时所在的区域,它也是一个静态的概念。
在ES6之前,除了全局作用域,只有函数才能创建作用域(创建的作用域叫做函数作用域)。
其实作用域是一个很抽象的概念,我们看下面的代码:
var a=10,b=20
function fn(x){
var a = 100,c = 300;
console.log('fn:',a,b,c,x)
function bar(x){
var a = 1000,d = 400;
console.log('bar:',a,b,c,d,x)
}
bar(5)
}
它产生了几个作用域呢?根据上面的描述,上述代码产生了三个作用域:
图1
综合上述的描述和实例代码,我们可以得到一个结论:作用域的个数遵循“n+1”原则,“n”表示定义的函数个数,“1”表示全局作用域
当我们调用fn函数的时候,输出如下:
图2
我们不难发现:
fn输出的数据:a来自fn作用域;b来自全局作用域;c来自fn作用域;x来自函数传参;
bar输出的数据:a来自bar作用域;b来自全局作用域;c来自fn作用域;d来自bar作用域;x来自函数传参;
那么这么变量的值是怎么查找的呢?是根据什么来找的呢,那就是我们的作用域链。
二、作用域链
如图1所示,产生了三个作用域:全局、fn作用域、bar作用域。全局包含fn,fn又包含了bar,分别形成了上下级关系。
2.1 作用域链的在变量查找中的体现
下面我们来描述一个变量的查找过程:bar中输出c时的查找过程
-
第一步:在当前作用域(bar作用域)查找变量c,没找到,那么向上一级作用域查找;
-
第二部:在bar的上一级作用域(fn作用域)中找,找到了,输出
大体上就是这样的一个查找过程。进一步总结:
第一步,现在当前作用域查找a,如果有则获取并结束。如果没有则继续;
第二步,如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;
第三步,(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;
第四步,跳转到第一步。
这样一级一级在作用域中查找变量的过程,形成了一个链路,这个链路就叫做作用域链。当前,作用域链在代码写下的时候就形成了,并非是在查找的时候才形成。
2.2 作用域的作用
作用域做大的作用就是隔离变量,不同作用域下的同名变量不会形成冲突。
2.3 歧义说法
经常会听到这样的说法:“当前作用域没有的时候,去它的父作用域找”。
这一句话,在理解上一不小心,就会被绕进去,就会产生歧义的。例如:
var a=10
function fn(){
console.log(a)
}
// fn2的入参f是个函数
function fn2(f){
var a = 200;
f();
}
fn2(fn);
//控制台输出多少呢?输出10,而不是200
所以,更贴切的说法是:要到创建这个函数的那个代码段所对应的作用域中去查找,而不是调用。这也更进一步印证了,作用域是个静态的概念。
三、总结
-
JavaScript在es6之前,没有块级作用域;
-
作用域是个静态的概念,作用域链也是个静态的链路;
-
作用域的个数遵循“n+1”原则:n表示定义的函数个数,1表示全局作用域;
-
作用域的作用,隔离变量,不同作用域下的同名变量不会形成冲突;
-
作用域是在函数创建的时候就产生了,一直存在,不会因为调用的场景不同而改变
-
注意“到父作用域中查找”的理解,避免产生歧义
以上是一些作用域的一些相关知识,希望能对你有所帮助。欢迎关注同步微信公众号:前端小菜的进阶之路