• 
    

      <address id="upfr9"><pre id="upfr9"><strike id="upfr9"></strike></pre></address>
      1. <address id="upfr9"><tr id="upfr9"></tr></address><dl id="upfr9"></dl>

        詳解JavaScript作用域-創(chuàng)新互聯(lián)

        這篇文章主要介紹“詳解JavaScript作用域”,在日常操作中,相信很多人在詳解JavaScript作用域問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”詳解JavaScript作用域”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

        成都創(chuàng)新互聯(lián)公司長期為成百上千家客戶提供的網站建設服務,團隊從業(yè)經驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網生態(tài)環(huán)境。為鹽邊企業(yè)提供專業(yè)的網站制作、成都網站制作,鹽邊網站改版等技術服務。擁有10年豐富建站經驗和眾多成功案例,為您定制開發(fā)。

        JavaScript編譯過程

        在學習作用域之前先簡單了解一下JavaScript的編譯、執(zhí)行過程。

        JavaScript被稱之為解釋性語言,與Java等這類編譯語言區(qū)別在于:JavaScript代碼寫好了就可以直接立即執(zhí)行,Java則需要相對較長時間的編譯過程才可生成可執(zhí)行的機器碼。

        但其實JavaScript也是有編譯過程的,JavaScript使用的是一種即時編譯的方式(JIT)。 JIT會把JavaScript代碼中會多次運行的代碼給編譯器編譯,生成編譯后的代碼并保存起來,在下次使用時使用編譯好的代碼。這其實是JavaScript運行環(huán)境采用的一種優(yōu)化解決方案。 如果不這么做,大量重復的代碼都會在運行前重復編譯,這將極大的影響性能與運行效率。

        JavaScript引擎也會對JavaScript代碼在運行前進去預編譯,在預編譯的過程中會定義一套規(guī)則用來存儲變量,對象,函數(shù)等,方便在之后的運行調用。這套規(guī)則就是作用域。

        JavaScript引擎在編譯過種中要對代碼進行詞法分析、語法分析、代碼生成、性能優(yōu)化等等一系列工作。JIT就是這一過程中用來優(yōu)化的一部分。

        var a = 1;

        var a = 1; 這行代碼在運行前編譯器都會做哪些事情?

        編譯器會把這行代碼分成 var a 和 a = 1 ,兩個部分。

        1. 首先編譯器會在相同作用域內查詢是否已經存在一個叫 a 的變量,如是存在,編譯器會忽略聲明a,繼續(xù)下一步編譯;如果不存在,則在當前作用域聲明一個變量,命名為a。

        2. 然后編譯器會為引擎生成運行時的代碼,這些代碼中包含處理a = 1的部分,引擎在處理a = 1的時候,同樣也會查詢作用域中是否存在a變量(會逐級向上一個作用域查找), 存在則賦值為2,不存在則拋出異常(嚴格模式下,如非嚴格模式則會隱式創(chuàng)建一個全局變量a;LHS)。

        LHS查詢 & RHS查詢

        LHS 和 RHS 的含義是“賦值操作的左側與右側”,不過要注意并不單指“=”和左側與右側。 賦值操作還有其它的形式,因此可以理解為:LHS-賦值操作的目標是誰? RHS-誰是賦值操作的源頭。

        a = 1; 是對a LHS查詢,a是賦值操作的目標,為a賦值為1. 如LHS查詢失敗,非嚴格模式下會隱式創(chuàng)建一個全局變量,嚴格模式下會拋出ReferenceError: a is not defined;

        console.log(a) 是對a RHS查詢,a是賦值的源頭;如果在作用域鏈中沒有查詢到a,同樣也會拋出ReferenceError: a is not defined;

        作用域鏈

        作用域是存儲變量的一套規(guī)則,當代碼運行時可能并不只是在一個作用域查詢變量。 當一個作用域中包含另一個作用域的時候,就會存在作用域嵌套的情況。所以當內部的作用域無法找到某個變量的時候,引擎會在當前作用域的外層嵌套中繼續(xù)查詢;直到查到變量或者達到最外層的作用域為止。這就是作用域鏈接。

        var name = "rewa"; 
        function sayHi(){
            console.log("hello,"+name);
        }
        sayHi(); // hello,rewa

        如上述代碼,sayHi函數(shù)作用域中并沒有變量name;卻能正常引用。就是因為引擎在上一層作用域找到并使用了變量name;

        var name = "rewa"; 
        function sayHi(){
            var name = "fang"; // 添加的代碼
            console.log("hello,"+name);
        }
        sayHi(); // hello,fang

        sayHi作用域中已經找到變量name時,引擎會停止向上層作用域查找,這叫作“遮蔽效應”,內部變量遮蔽外部作用域變量。

        詞法作用域

        作用域有兩種主要的工作模型。一種是最為最為普遍的,被大多數(shù)編程語言采用的詞法作用域; 還有一種叫動態(tài)作用域。

        詞法作用域就是在寫代碼時將變量和塊作用域寫在哪里作用域就在哪里,定義在詞法階段的作用域。JavaScript就是采用的詞法作用域。

        詞法:就是組成代碼塊的字符串。比如:

        var a = 1;

        這行代碼中,var、a、=2、; 還有這中間的空格 都是詞法單元。

        編譯器的第一個工作就是詞法化,會把代碼分解成一個一個詞法單元;具體編譯器在詞法化階段都做了哪些工作遵守哪些規(guī)則,根據(jù)不同編程語言而不同。JavaScript是怎么樣的規(guī)則我特么也不清楚,等我研究清楚了;再來做一個筆記。

        簡單的說,詞法作用域就是你寫代碼的時候,把變量a寫在函數(shù)b中,那么編譯器編譯時b的作用域中就會包含有a變量,編譯器會保持詞法作用域不變。(也會有特殊情況)

        如下代碼:

        var a = 1;
        function foo(){
            var b = a + 2;
            function bar(){
                var c = b + 3;
                console.log(a,b,c)
            }
            bar();
        }
        foo(); // 1,3,6

        這段代碼編譯后的作用域與你編寫時的詞法作用域是一致的。

        全局作用域: 變量a, 函數(shù) foo

        函數(shù)foo()創(chuàng)建的作用域:變量b,函數(shù)bar

        函數(shù)bar()創(chuàng)建的作用域:變量c

        代碼寫在哪作用域就在哪。

        了解詞法作用域需要注意以下幾點:

        • 無論函數(shù)在哪里被調用,如何被調用,函數(shù)的詞法作用域都只由函數(shù)被聲明時所處的位置決定。

        • 詞法作用域查詢只會查找一級標識符,比如上述代碼中的變量a,b,c。如果訪問foo.bar.baz,詞法作用域只會查詢foo。找到這個變量后,再訪問屬性bar,再到baz。

        • 存在使詞法作用域編譯后不一致的方法,但會導致性能下降。

        修改詞法作用域的方法 eval & with (千萬不要這么做)
        eval

        代碼如下:

        var a = 1;
        function foo(str){
            eval(str);
            console.log(a);
        }
        foo('var a = 2;'); // 2

        var a = 1; 會在函數(shù)foo中運行,變量a將包含作用域。 eval(...)函數(shù)接受一個字符串,并將字符串當作代碼運行;就相當于把代碼寫在這個位置。

        eval在嚴格模式下會拋出異常:

        var a = 1;
        function foo(str){
            "use strict"
            eval(str);
            console.log(a); // ReferenceError: a is not defined
        }
        foo('var a = 2;');

        默認情況下,如果eval()中有包含聲明,就會對所處的詞法作用域進行修改;在嚴格模式下,eval()在運行時有其自己的詞法作用域,那么將無法修改所在的作用域,如上述代碼。

        with
        var obj = {
            a:1,
            b:2,
            c:3
        }
        obj.a = 11;
        obj.b = 22;
        obj.c = 33;
        // with 也可以達到同樣的效果
        with(obj){
            a=111;
            b=222;
            c=333;
        }
        //這樣 obj 被修改為:
        {   
            a:111,
            b:222,
            c:333
        }

        with()接受一個參數(shù),在這里是obj;此時with中作用域是obj, 可以訪問obj中的屬性。 這種方式賦值就變得簡潔很多。

        with可以為一個對象創(chuàng)建一個作用域,對象的屬性會定義為這個作用域中的變量;不過with中的通過var聲明的變量并不會成為這個作用域的成員,而是被聲明到with所在的作用域中。這不正常了,代碼使用with會變得很不容易控制。比如:

        with(obj){
            a=111;
            b=222;
            c=333;
            d=444;
        }
        console.log(obj.d); // undefined
        console.log(d); // 444

        原來以為會添加在obj中的屬性d,卻被添加到了全局作用域中;這就可能與開發(fā)編寫時的預期結果不符;也不符合詞法作用域的規(guī)則。

        所以evalwith都已經被禁止了,也不推薦使用。

        這種不可預估詞法作用域的特性,也帶了一個嚴重的性能問題。 JavaScript引擎在編譯階段會進行性能優(yōu)化。其中有一些優(yōu)化依賴代碼的詞法,對詞法進行靜態(tài)分析,并預先確定所有變量與函數(shù)的定義位置,才能在執(zhí)行過程中快速找到變量。

        如果引擎在代碼中發(fā)現(xiàn)了evalwith,它無法在詞法分析階段明確知道eval(...)接生什么代碼;也無法知道傳遞給with用來創(chuàng)建新詞法作用域的對象內容是什么。 那么優(yōu)化未知的代碼和詞法作用域是沒有意義的,引擎將放棄優(yōu)化這一部分。

        如果在代碼中頻繁使用evalwith,程序運行起來將會非常慢。

        函數(shù)作用域

        函數(shù)內部的變量和函數(shù)定義都可以封裝起來,外部無法訪問封裝在函數(shù)內部的變量標識符。

        如下代碼:

        function foo(){
            var a = 1;
            function sayHi(){
                console.log('Hello!')
            }
        }
        console.log(a); // ReferenceError:a is not defined
        sayHi(); //ReferenceError: sayHi is not defined

        在函數(shù)外部訪問其內部的變量與函數(shù)會拋出異常。

        這樣函數(shù)就可以行成一個相對獨立的作用域,可以用函數(shù)來封裝一個相對獨立的功能。 把業(yè)務代碼隱藏在函數(shù)內部實現(xiàn),對外暴露接口;只要傳入不同的參數(shù)就可以輸入對應的結果。 所以很多情況下函數(shù)可以用來模擬Java語言中類的實現(xiàn)。

        例如:

        function shoot(who,score){
            //這里面可以包含更多邏輯
            function one(){
                console.log(who + '罰籃命中!到得' +score+ '分!');
            }
            function dunk(){
                console.log(who + '扣籃,獲得' +score+ '分!');
            }
            function three(){
                console.log(who + '命中了一個' +score+ '分球!');
            }
            switch(score){
                case 1:
                    one();
                    break;
                case 2:
                    dunk();
                    break;
                case 3:
                    three();
                    break;
            }
        }
        shoot('Kobe',3); // Kobe投中了一個3分球!'
        shoot('Lebron',2); // Lebron扣籃,獲得2分!' 
        shoot('Shaq',1); // Shaq罰籃命中!到得1分!'

        函數(shù)內部隱藏變量與函數(shù)的定義可以避免污染全局命名空間;比如當全局作用域中也有one dunk three 這些函數(shù),并且內部實現(xiàn)不同;代碼邏輯就會混亂。 而在上面的代碼中,函數(shù)中定義的函數(shù)會遮蔽外部作用域的函數(shù)定義,只會調用到當前函數(shù)作用域中的同名函數(shù)。

        但是即使如此,大量的函數(shù)聲明同樣也會污染全局全名空間。 當下流行的模塊化就是解決這一問題的方案之一。不過在模塊化出來之前,大多數(shù)情況可以使用立即執(zhí)行函數(shù)(IIFE)來解決。 代碼如下:

        (function(){
            var name = 'kobe';
            console.log(name);
        })();

        當函數(shù)執(zhí)行結束后,name變量會被垃圾回收; 且不會與外部的任何作用域產生沖突,因為整個函數(shù)都執(zhí)行在一個立即執(zhí)行函數(shù)中。它是一個塊作用域,且本身也沒有在作用域下創(chuàng)建任何標識符。

        立即執(zhí)行函數(shù)也可以接受參數(shù),用來函數(shù)內部引用:

        (function(name){
            console.log(name);
        })('kobe');

        JavaScript中除了函數(shù)作用域,還有其它塊作用域。比如with也是塊作用域;上面有過介紹 with 。 還有一個容易被忽略的塊作用域 try/catch 。

        try{
            undefined(); //拋出異常
        }
        catch(err){
            console.log(err); // 正常執(zhí)行
        }
        console.log(err); //ReferenceError: err is not defined

        err只能在catch中訪問,在外部的引用會拋出異常。

        對于塊作用域,ES6中我們可以用let聲明實現(xiàn)這種需求。

        if(true){
            let a = 1;
            console.log(a); //1
        }
        console.log(a); //ReferenceError: a is not defined

        if(){} 并不是塊作用域,但上述代碼中let可以讓a變量成為僅if(){...}中的變量,外部不可訪問。

        這是不是像極了try/catch , 可letES6的標準;在ES6之前實現(xiàn)類似塊作用域效果的方法可沒這么輕松。 現(xiàn)在一般我們在編寫ES6代碼,想要運行在所有瀏覽器上需要通過轉譯。而轉譯器也會把類似let的聲明,轉為 try/catch的形式。

        {
            let a = 1;
            console.log(a); // 1
        }
        console.log(a); //ReferenceError: a is not defined

        轉為:

        try{
            throw 1;
        }catch(a){
            console.log(a); //1
        }
        console.log(a); //ReferenceError: a is not defined

        還有可能轉譯為:

        {
            let _a = 1;  // 把{}中的 a 轉為_a 
            console.log(_a); 
        }
        console.log(a);

        到此,關于“詳解JavaScript作用域”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注創(chuàng)新互聯(lián)-成都網站建設公司網站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

        文章標題:詳解JavaScript作用域-創(chuàng)新互聯(lián)
        文章來源:http://www.jbt999.com/article12/ceogdc.html

        成都網站建設公司_創(chuàng)新互聯(lián),為您提供移動網站建設、網站維護、企業(yè)建站、網頁設計公司、建站公司、定制開發(fā)

        廣告

        聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:[email protected]。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

        網站托管運營

      2. 
        

          <address id="upfr9"><pre id="upfr9"><strike id="upfr9"></strike></pre></address>
          1. <address id="upfr9"><tr id="upfr9"></tr></address><dl id="upfr9"></dl>
            日韩精品人妻系列 | 亚洲免费在线 | 国产精品视频99 | 日本黄色精品视频 | 我要看国产一级黄片 | 日韩精品成人一区二区三区蜜桃 | 亚洲无码视频观看 | 欧美激情网站 | 国产精品伦理久久久久 | 波多野结衣黄色视频 |