词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将 变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情 况下是这样的)。
_____________《你不知道的javascript》
目录
作用域
"词法作用域是作用域的一种工作模型",通俗的讲“没有作用域的概念就没有词法作用域的概念。”..
什么是作用域
域表示的就是范围,即作用范围,就是一个名字在什么地方可以别使用,什么时候不能使用。
作用域就是一套规则,用于确定在何处以及如何查找变量(标识符)的规则,通俗的讲,作用域就是查找变量的地方。在某函数中找到该变量,就可以说在该函数作用域中找到了该变量;在全局中找到该变量,就可以说在全局作用域中找到了该变量!
先看一段及其简单的代码:
function foo(){
var a=123;
console.log(a);//result 123
}
foo()
在foo函数执行的时候,输出一个a变量,那么这个a变量是哪里来的嘞,有看到函数第一行有定义a变量的代码var a = '123'
再看下面这段代码:
var num=123;
function foo(){
console.log(num);//result 123
}
foo()
同样的道理:在输出num的时候,在自己函数体内没有找到num,那么就会到函数外层的全局中查找,找到了就停止查找并输出了。
注意以上两段代码都有查找变量,第一段代码是在函数中找到a变量,第二段代码是在全局中找到b变量。函数作用域、全局作用域,把这两个词换入到原来那句话中,第一段代码是在函数作用域中找到a变量,第二段代码是在全局作用域中找到num变量。通俗的讲,作用域就是查找变量的地方。在某函数中找到该变量,就可以说在该函数作用域中找到了该变量;在全局中找到该变量,就可以说在全局作用域中找到了该变量!
块级作用域:即块级的作用范围
JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域。
而在C、Java、C#等编程语言中,下面的语法报错(伪代码)
{
var numb = 123;
{
console.log( numb ); // num => 123
}
}
console.log( numb ); //报错
在js 中采用词法作用域
所谓的词法(代码)作用域,就是代码在编写过程中体现出来的作用范围,代码一旦写好,不用执行,作用范围已经确定好了,这个就是所谓的词法作用域。
在js中词法作用域规则:
- 函数允许访问函数外的数据,
- 这个代码结构中只有函数可以限定作用域。
- 作用规则首先使用提升规则分析
- 如果当前作用规则中有名字了,就不考虑外面的名字。
例子1:
var num=123;
function foo(){
console.log(num);
}
foo()//result 123
例子2:
if(false){
var num=123;
}
console.log(num);//undefined
例子3:
var num=123;
function foo(){
var num=456;
function func(){
console.log(num);//456
}
func();
}
foo();
作用域链结构
可以发现只函数才能制作作用域结构,那么只要是代码,至少有一个作用域,即全局作用域。
凡是代码中有函数,那么这个函数就构成另一个作用域。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域,那么将这样的所有作用域列出来,可以有一个结构:函数内指向函数外的链式结构
例如:
function f1(){
function f2(){
}
}
var num=456;
function f3(){
function f4(){
}
}
作用域链结构与DOM树结构很相似。
绘制作用域链
步骤:
- 看整个全局是一条链,即顶级链,记为0级链
- 看全局作用域中有什么成员声明,就以方格的形式绘制到0级链上
- 再找函数,只有函数可以限制作用域,因此从函数中引出新链,标记为1级链
- 然后再每一个1级链中在此往复刚才的行为。
变量的访问(搜索)规则
- 首先看变量在第几条链上,在该链上看是否有变量的定义与赋值,如果有直接使用
- 如果没有就到上一级链上找(n-1级链),如果有直接使用,停止继续查找。
- 如果还没有在此往上找……直到全局链(0级),还没有就是is not defined
- 注意,切记:同级链不可混合查找
绘制如下程序的作用域链
function f1(){
var num=123;
function f2(){
console.log(num);
}
f2();
}
var num=456;
f1();//123
- 首先函数f1()和变量num=456,在0级链上
- 而f1下面又可以展开1级链,
- 1级链上有num=123 和 函数f2。
- 程序f1()调用进入左边1级链,而f1中又调用了f2函数,f2函数中console.log(num)可以看作在2级链,
- 此时,程序会向这一条链向上查找,首先2级链没有num声明,向上到达1级链,
- 刚好1级链上有num=123,所以就直接使用123,
- 程序最后的结果打印123,
分析代码
- 在分析代码的时候切记从代码运行角度上来分析,如果代码给变量赋值了,一定要标记到图中;
- 如果代码比较复杂,可以在图中表示代码的内容,有时候还要将原型图与作用域图结合起来分享。
分析代码如下:
var num=123;
function f1(){
console.log(num);
}
function f2(){
var num =456;
f1();
}
f2();//123
作用域链图绘制:
作用链图预解析:
- 首先把num=123,函数f1,函数f2画在0级链上。
- f1中只有一句console.log(num),画出一条1级链,
- f2也画出1级链,连上有num=456和函数调用语句f1();
- 调用f2(),进入f2函数的作用域链,而在f2中又调用了f1函数,
- 程序进入f1的作用链,所以console.log(num)会在此链上查找是否存在num,
- 没有,继续向上一级查找,刚好在0级链上找到了num=123,
- 所以f1函数中的console.log(num)打印就是123.
参考
- 《你不知到的javascript》卷