範例1:
这个範例下面执行Myname(genius),会秀出Eason名子
var genius='anyone';function Myname(){var genius='Eason'console.log(genius); }Myname();
範例2:
这个範例会秀出Eason 下一行 anyone。
var genius='anyone';function Myname(){var genius='Eason'console.log(genius);}Myname();console.log(genius);
我想试着用语法作用域(Lexical Scope)来解释这件事情。
首先带入的第一个观念是:静态作用域与动态作用域
JavaScript属于静态作用域,语法解析的时候就已经决定了作用域,且决定后不会再改变。
而它的作用域是一层一层向内的,如下图,最外层是全域,里面有Myname()跟Heyyou()两个函式,而这两个函式,各自是一个作用域,若函式内还有函式,里面包住的那个函式也是一个作用域,
而当函式中需要某变数,而该作用域裏头没有这个变数,它就会开始向外查找,如果一直找不到这个变数,就会出现「ReferenceError: OOO is not defined」的错误讯息 (OOO是变数名称)。
範例3: 向外查找
因为Myname()里面没有宣告genius所以向外查找,找到了全域变数中的genius,所以后来秀出了anyone
var genius='anyone'; //全域变数function Myname(){console.log(genius);}Myname();
所以我们回到範例1的部分,最下面那行Myname(); 呼叫了函式,里面宣告了genius='Eason',并且使用console.log(genius); ,秀出了Eason,展现的是Myname()这个函式的作用域里的执行状况。
var genius='anyone';function Myname(){var genius='Eason'console.log(genius); }Myname();
接着是範例2,如同範例1,我们知道Myname()函式秀出Eason,套用作用域的观念,最底下那行console.log(genius);并不会秀出含式的内容,因为作用域是个别运作的,所以没有因为先执行了函式而改写了全域变数的var genius='anyone';而最底下的console.log会抓到的就是全域变数。
所以依照执行的顺序,秀出的第一行Eason 第二行anyone。
var genius='anyone';function Myname(){var genius='Eason';console.log(genius);}Myname();console.log(genius);
最后一段範例,研究一下动态&静态作用域的差别。
经过上面的叙述我们可以判断JavaScript的静态作用域,在语法解析时便已经决定了作用域,fn02定义的var num=2只会存在于该函式中,fn02中呼叫了fn01,fn01中的console.log(num);函式中变数没有定义,所以向外查找,找到全域变数的var num=1;,所以秀出了1。
如果是动态作用域呢,程式码执行的顺序会是这样子:
宣告var num=1;,直行fn02,裏头从新宣告num=2,然后再fn02中呼叫fn01执行consolo.log,动态作用域再函式调用的时候才决定作用域,所以fn01就向上一层函式宣告的位置取得了num=2的结果。
var num=1;function fn01(){ console.log(num);}function fn02(){var num=2;fn01();}fn02();
参考资料:
六角学院-JavaScript核心篇
连载于Eason的前端笔记