首頁  >  文章  >  web前端  >  深入理解JavaScript作用域

深入理解JavaScript作用域

阿神
阿神原創
2017-02-21 15:31:021235瀏覽

深入理解JavaScript作用域

#JavaScript是閘全棧性的語言,尤其是在2016年,常聽到JavaScript要一統天下的梗,甚至有流言說16年會個Vue.js就能找到工作,和當年iOS會個TableView就能找工作一樣.(tableView就相當於Android的ListView,不過現在基本上都用RecyclerView了)

2016年前端的熱門技術基本上都和JavaScript有關,比如移動端跨平台的Facebook出品的React Native和阿里的Weex,熱修復技術JSPath,以及後端的Node.js(本寶寶非常喜歡的一門技術棧).昨晚去gibhub看了下,Vue的star數量已經超過了jQuery,雖然star數量並不能證明些什麼,但是至少我們已經看到,前端思想已經從之前的document操作到了數據驅動開發的轉變(有興趣的話我可以之後結合Android、iOS、Vue,用一個小demo演示一下這個思想轉變),有些公司甚至開始嘗試用餓了麼出品的Element來替代EasyUI(做過後端的同學應該都知道EasyUI真的是AV畫質....)

JS技術層出不窮,之前就有一篇很火的文章,2016年學JS是什麼樣的體驗,瞬間嚇尿了不少人.大家都把注意力放到了框架和新技術上,原生的JS反而遭到了冷落,所以想通過幾個基礎性的JS問題,和大家一起交流(更希望大家帶我飛,但是別把我丟到馬航上飛...)


JavaScript中的作用域

下面一個簡單的問題:

<script>
    var str1 = "hello";
    var str2 = "world";

    function t1() {
        console.log(str1);
        console.log(str2);

        var str2 = "toby";
        console.log(str2);
    }

    //这里会输出什么?
    t1();

</script>

這是一個很簡單的JS作用域問題,但是你越是強調簡單這兩個字,就越容易使人放鬆警惕,所以導致有些同學不假思索的回答輸出

● hello

● world

● toby

#但是結果是輸出

● hello

● undefined

● toby

那麼這就奇怪了,為什麼會有undefined呢,不是應該是world嗎?首先我們要明白,變數的尋找會遵循就近原則,所以js會先在函數中找,找不到才會向外找,而函數內有str2,但是運行到console.log(str2)時str2未定義,所以就出現了undefined


#詞法分析

#知其然還必須其所以然,那麼我們再來看幾個例子

例子1

#

<script>
    function t(userName) {
        console.log(userName);//这里输出什么?

        function userName() {
            console.log(&#39;tom&#39;);
        }
    }
    t(&#39;toby&#39;);
</script>

輸出的結果是什麼,這個例子好像和上面不一樣,有種回到高中數學,題型一變就懵逼的感覺,這時候可能有些同學會覺得是toby,但是實際輸出是

function userName() {
    console.log(&#39;tom&#39;);
}

為什麼是function呢?其實這種作用域的問題,都是可以透過"套公式"來得出,這個公式,就是JS中的詞法分析,JS中函數執行前,必須要做的一項工作就是詞法分析,那麼究竟要什麼呢?分析參數,分析變數宣告,分析函數宣告,那麼我們就拿這題來套一下公式

執行t('toby')的時候,會開始兩個階段,一個是分析階段,分析完就到執行階段

分析階段:######● 函數運行的瞬間,會產生一個Active Object物件(以下簡稱AO物件),一個函數作用域內能找到的所有變數,都在AO上,此時用程式碼表示為: t.AO = {}######● 分析參數: 接收參數,以參數名為屬性,參數值為屬性值,因為沒有參數,因此分析結果用程式碼表示為: t.AO = {userName : toby}######● 分析var宣告: t函數內沒有var宣告,略過######● 分析函數宣告: 這個函數宣告有個特點, AO上如果有與函數名稱同名的屬性,則會被此函數覆寫,因為函數在JS領域,也是變數的一種型別,因此用程式碼表示為: t.AO = { userName : function userName() {console .log('tom');}}######執行階段:######執行t('toby')的時候,當執行到console.log(userName)時,就呼叫t. AO.userName,所以,最後的輸出結果是function userName() {console.log('tom');}#########範例2######
<script>
    function t(userName) {
        console.log(userName);//这里输出什么?

        var userName = function () {
            console.log(&#39;tom&#39;);
        }
    }
    t(&#39;toby&#39;);
</script>
###那這裡的輸出又是什麼呢?這個好像又和上面的例子不一樣,又再次陷入懵逼狀態?別怕麻煩,堅定的按照公式再走一次流程(上面的的例子寫得比較詳細,下面的分析就簡單寫)######分析之前,首先要弄清楚兩個概念,一個叫函數宣告,一個叫函數表達式###
//这个叫函数声明
function userName() {
    console.log(&#39;tom&#39;);
}

//这个叫函数表达式
var userName = function () {
    console.log('tom');
}
###分析階段:######● 創建AO物件, t.AO = {}######● 分析參數: t.AO = {userName : toby}###

● 分析var声明: 在AO上,形成一个属性,以var的变量名为属性名,值为undefined,(因为是先分析,后执行,这只是词法分析阶段,并不是执行阶段,分析阶段值都是undefined,如果执行阶段有赋值操作,那值会按照正常赋值改变),也就是说代码应该表示为:t.AO = {userName : undefined},但是还有另外一个原则,那就是如果AO有已经有同名属性,则不影响(也就是什么事都不做),由于分析参数时,AO上已经有userName这个属性了,所以按照这个原则,此时什么事都不做,也就是说,此时按照分析参数时的结果t.AO = {userName : toby}

● 分析函数声明: 此时没有函数声明,略过

执行阶段:

调用t.AO.userName,所以,最后的输出结果是toby

例子3

<script>
    t();
    t2();

    function t() {
        console.log(&#39;toby&#39;);//这里会输出什么?
    }

    var t2 = function () {
        console.log(&#39;hello toby&#39;);//这里会输出什么?
    };
</script>

那么我们再来看一个例子,这下彻底回到高中时代,做了两个例子好像感觉掌握了,结果考试你给来看这个?

答案是,t()输出为toby,t2()则会报错.这又是为什么?

● t()可以调用是因为在词法分析的过程,就已经完成了t函数的分析,所以可以调用

● t2()不能调用是因为在词法分析的阶段,分析到有一个t2声明,在AO上只是形成了一个属性,但是值为undefined

例子4

<script>
    function t(userName) {
        console.log(userName);//这里输出什么?
        function userName() {
            console.log(userName);//这里输出什么?
        }
        userName();
    }
    t(&#39;toby&#39;);
</script>

函数里面套函数,这次竟然又和前面不一样了...这次我不说答案了,直接先套公式走一波

t('toby')的分析和执行阶段

分析阶段:

● 创建AO对象,t.AO = {}

● 分析参数: t.AO = {userName : toby}

● 分析var声明: 有同名属性,不做任何事,还是t.AO = {userName : toby}

● 分析函数声明: 有同名属性,覆盖: t.AO = {userName : function userName() {console.log(userName);}}

执行阶段: t.AO.userName 输出为function userName() {console.log(userName);}}

userName()的分析和执行阶段

这里也要搞清楚两个概念

//执行userName()分析的是
function () {
  console.log(userName);
};

//而不是
var userName = function () {
    console.log(userName);
};

分析阶段:

● 创建AO对象,userName.AO = {}

● 分析参数: 无,略过

● 分析var声明: 无,略过

● 分析函数声明: 无,略过

执行阶段: 因为此时userName.AO = {}是个空对象,无法执行userName.AO.userName,所以会向上一层找,所以输出t.AO.userName的结果,也就是function userName() {console.log(userName);}}

例子5

<script>
    function t(userName) {
        console.log(userName);//这里输出什么?
        var userName = function () {
            console.log(userName);//这里输出什么?
        }
        userName();
    }
    t(&#39;toby&#39;);
</script>

好吧,我保证这个是最后一道...这个输出结果是什么呢?我们只要坚定公式没问题,就一定能得出结果,那么再套公式走一波

t('toby')的分析和执行阶段

分析阶段:

● 创建AO对象,t.AO = {}

● 分析参数: t.AO = {userName : toby}

● 分析var声明: 有同名属性,不做任何事,还是t.AO = {userName : toby}

● 分析函数声明: 无,略过

执行阶段: 执行console.log(userName);时调用t.AO.userName 输出为toby,执行完后,代码继续往下执行,那么就到了进行var的赋值操作(var的分析和执行的区别看例子2中我有解释),此时t.AO = {userName : function userName() {console.log(userName);}}},代码继续往下执行,接着就执行到了userName()

userName()的分析和执行阶段

分析阶段:

● 创建AO对象,userName.AO = {}

● 分析参数: 无,略过

● 分析var声明: 无,略过

● 分析函数声明: 无,略过

执行阶段: 按照例子4我们知道userName.AO是个空对象,所以会往上调用t.AO.userName,所以输出为:function userName() {console.log(userName);}}}


总结

JavaScript作用域会先在自己的AO上找,找不到就到父函数的AO上找,再找不到再找上一层的AO,直到找到window.这样就形成一条链,这条AO链就是JavaScript中的作用域链.JavaScript中有两条很重要的链,一条是作用域链,一条是原型链,

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn