<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 徹底搞懂JavaScript作用域 > 摘自 https://github.com/prettyEcho/deep-js/issues/3 > 作者:張建成,有修改 我們常說,**萬物都有其存在的價值**,這話的確不錯,但是深思一下,是不是需要有個前提,**萬物都在某些領域或多或少的存在某些價值**。 舉個例子,**汽車**,絕對是個非常有價值的stuff,它給我們的日常出行,貨物運輸等帶來了極大的便利;**筷子**,同樣也是個非常有價值的stuff,它給我們吃飯帶來了極大的方便。但是,**汽車**能幫我們把菜送到嘴里嗎?**筷子**能載著我們出行嗎? 那么,我上面所說的**某些領域**,我們是不是可以稱其為**作用域**,我想是可以的。 說到這,那么我就想問了:在JS里,**作用域**是不是也是類似的概念呢? **首先,我可以肯定的說這是一個在 JavaScript 中灰常灰常重要的概念,關系著 JS 里很多核心的機制,理解它,很多問題都迎刃而解了。** 那么,問問自己,在JS里,作用域是什么? 心里大概知道是什么,但是細細一想又好像說不太清。 沒關系,下面我們就細細品味這個有意思的東東。 先throw概念吧: 作用域負責收集并維護由所有聲明的標識符(變量)組成的一系列查詢,并實施一套非常嚴格的規則,確定當前執行的代碼對這些標識符的訪問權限。 通俗來說,作用域相當于一個管理員(有自己的一套規則),他負責管理所有聲明的標識符的有序查詢。 我們來講個故事,說說作用域到底干了啥。 ### 三兄弟齊上陣 long long ago,有3個關系很好的基友,老大叫引擎,老二叫編譯器,老三叫作用域。三兄弟眼看年歲已長,可手上還是沒有幾個銀子。個個都很著急,于是三兄弟謀劃一同做個事。 求職過程:此粗略去數萬個字。。。 最終他們做的工作是:負責JS的編譯和運行。 他們的工作內容是這樣的: 老板甩給他們一項任務編譯并執行下面代碼: ``` var a = 1; console.log( a ); ``` 開始工作: - 編譯器:作用域,幫我看看你那有沒有儲存變量a。 - 作用域:二哥,還沒有。 - 編譯器:那好,幫我儲存一個。 - 引擎: 老三,你那有沒有一個叫做a的變量。 - 作用域:大哥,還真有,剛二哥讓我存儲了一個。 - 引擎: 真是太好了,幫我拿出來,它的值是幾,我需要給它復制。 - 作用域:大哥,它的值是2。 - 引擎: 謝謝你,三弟,這樣我就能打印它的值了。 上面講了一個不恰當的小故事,但是三者之間的關系大概就是這樣。 ### 詞法作用域 VS 動態作用域 - 詞法作用域 [徹底搞懂JavaScript作用域](https://github.com/prettyEcho/deep-js/issues/3) 里介紹過,大部分標準語言編譯器的第一個工作階段叫作詞法化(也叫單詞化)。回憶一下,詞法化的過程會對源代碼中的字符進行檢查,如果是有狀態的解析過程,還會賦予單詞語義。 在JS里,使用的作用域就是**詞法作用域**。 > 簡單地說,**詞法作用域就是定義在詞法階段的作用域**。換句話說,**詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪里來決定的**,因此當詞法分析器處理代碼時會保持作用域不變(大部分情況下是這樣的)。 - 動態作用域 在JS里,動態作用域和this機制息息相關。**它的作用域詩是在運行的過程中確定的** ``` var a = 1; function foo() { var a = 2; console.log( this.a ); } foo(); // 1 ``` 從上面的代碼,我們可以看出:foo中打印a的值不是由寫代碼的位置確定的,而是取決于foo執行的位置。 - 區別 - 詞法作用域是在寫代碼或者說定義時確定的,而動態作用域是在運行時確定的。(this 也是!) - 詞法作用域關注函數在何處聲明,而動態作用域關注函數從何處調用。 ### 函數作用域 JS里,生成作用域的方式: - 函數 - with、eval (不建議使用,影響性能) 由此,我們知道JS里,**絕大多數的作用域都是基于函數生成的**。 每個函數都會為自身生成一個作用域氣泡。這個氣泡內所有的標識符都可以在這個氣泡中使用。 ``` function bar() { var a = 1; function foo() { var b = 2; console.log(b); } foo(); console.log(a); } bar(); ``` 上面代碼,bar氣泡有標識符a、foo,因此在bar氣泡中可以訪問到a、foo; foo氣泡有標識符b,因此在foo氣泡中可以訪問到b; 當然還有一個全局氣泡,全局氣泡中有bar標識符,因此在全局氣泡中可以訪問到bar。 #### 最小授權原則 > 最小授權原則是指在軟件設計中,應該最小限度地暴露必要內容,而將其他內容都“隱藏”起來,比如某個模塊或對象的 API 設計。 這個原則可以延伸到如何選擇作用域來包含變量和函數。如果所有變量和函數都在全局作 用域中,當然可以在所有的內部嵌套作用域中訪問到它們。但這樣會破壞前面提到的最小 特權原則,因為可能會暴漏過多的變量或函數,而這些變量或函數本應該是私有的,正確 的代碼應該是可以阻止對這些變量或函數進行訪問的。 例如: ``` function doSomething(a) { b = a + doSomethingElse( a * 2 ); console.log( b * 3 ); } function doSomethingElse(a) { return a - 1; } var b; doSomething( 2 ); // 15 ``` 在這個代碼片段中,變量 b 和函數 doSomethingElse(..) 應該是 doSomething(..) 內部具體 實現的“私有”內容。給予外部作用域對 b 和 doSomethingElse(..) 的“訪問權限”不僅 沒有必要,而且可能是“危險”的,因為它們可能被有意或無意地以非預期的方式使用, 從而導致超出了 doSomething(..) 的適用條件。更“合理”的設計會將這些私有的具體內容隱藏在 doSomething(..) 內部, 例如: ``` function doSomething(a) { function doSomethingElse(a) { return a - 1; } var b; b = a + doSomethingElse( a * 2 ); console.log( b * 3 ); } doSomething( 2 ); // 15 ``` 現在,b 和 doSomethingElse(..) 都無法從外部被訪問,而只能被 doSomething(..) 所控制。 功能性和最終效果都沒有受影響,但是設計上將具體內容私有化了,設計良好的軟件都會 依此進行實現。 #### 規避沖突 當我們的程序代碼逐漸多起來,難免會出現變量沖突。那么如何規避沖突就顯得額外重要。 函數可以把標識符嚴謹的"隱藏"起來,外部無法訪問到,利用這個特性我們可以很好的規避沖突。 ``` function foo() { var a = 1; } function bar() { var a = 2; } ``` foo和bar中定義了相同的變量a,但是卻不會相互造成影響。因為函數可以很好的把標識符"隱藏"起來。 - 全局命名空間 變量沖突的一個典型例子存在于全局作用域中。當程序中加載了多個第三方庫時,如果它 們沒有妥善地將內部私有的函數或變量隱藏起來,就會很容易引發沖突。 這些庫通常會在全局作用域中聲明一個名字足夠獨特的變量,通常是一個對象。這個對象 被用作庫的命名空間,所有需要暴露給外界的功能都會成為這個對象(命名空間)的屬 性,而不是將自己的標識符暴漏在頂級的詞法作用域中。 例如: ``` var myLibrary = { name: 'echo', getName: function() { console.log( this.name ); } } ``` #### 函數聲明 VS 函數表達式 函數聲明和函數表達式判別的依據是:**函數的聲明是否以function關鍵詞開始**。 以關鍵詞function 開始的聲明是函數聲明,其余的函數聲明全部是函數表達式。 ``` //函數聲明 function foo() { } //函數表達式 var foo = function () { }; (function() { })(); ``` #### 具名函數 VS 匿名函數 - 具名函數 擁有名字的函數 ``` function foo() { } var foo = function bar() { } setTimeout( function foo() { } ) +function foo() { }(); ``` 需要注意:**函數聲明一定要是具名函數**。 - 匿名函數 沒有名字的函數 ``` var foo = function () { } setTimeout( function foo() { } ) -function foo() { }(); ``` #### 立即執行函數(IIFE) ``` var a=2; (function foo() { var a=3; console.log( a ); // 3 })(); console.log( a ); // 2 ``` 該函數是以()開始,不是以關鍵詞function開始,因此IIFE是函數表達式 函數名對 IIFE 當然不是必須的,IIFE 最常見的用法是使用一個匿名函數表達式。雖然使 用具名函數的 IIFE 并不常見,但它具有以下優勢: 1. 匿名函數在棧追蹤中不會顯示出有意義的函數名,使得調試很困難。 2. 如果沒有函數名,當函數需要引用自身時只能使用已經過期的arguments.callee引用, 比如在遞歸中。另一個函數需要引用自身的例子,是在事件觸發后事件監聽器需要解綁 自身。 3. 匿名函數省略了對于代碼可讀性/可理解性很重要的函數名。一個描述性的名稱可以讓 代碼不言自明。 因此具名函數的 IIFE 也是一個值得推廣的實踐。 - 另一種表達形式 ``` (function() { }()) ``` 這也是IIFE的一種表達方式,功能上和上面那種方式是一致的。**選擇哪種全憑個人愛好**。 - 參數傳遞 IIFE 也可以和其他形式的函數一樣實現參數的傳遞(多說一句:參數傳遞是按值傳遞)。 ``` (function foo(a) { console.log(a); })(3); ``` 這個模式的另外一個應用場景是解決 undefined 標識符的默認值被錯誤覆蓋導致的異常(雖 然不常見)。將一個參數命名為 undefined,但是在對應的位置不傳入任何值,這樣就可以 保證在代碼塊中 undefined 標識符的值真的是 undefined: ``` undefined = true; // 給其他代碼挖了一個大坑!絕對不要這樣做! (function IIFE( undefined ) { var a; if (a === undefined) { console.log( "Undefined is safe here!" ); } })(); ``` - UMD (Universal Module Definition) IIFE 還有一種變化的用途是倒置代碼的運行順序,將需要運行的函數放在第二位,在 IIFE 執行之后當作參數傳遞進去。盡管這種模式略顯冗長,但有些人認為它更易理解。 ``` var a=2; (function IIFE( def ) { //參數的處理 def( window ); })(function def( global ) { //邏輯運算 var a=3; console.log( a ); // 3 console.log( global.a ); // 2 }); ``` ### 塊作用域 盡管函數作用域是最常見的作用域單元,當然也是現行大多數 JavaScript 中最普遍的設計 方法,但其他類型的作用域單元也是存在的,并且通過使用其他類型的作用域單元甚至可 以實現維護起來更加優秀、簡潔的代碼。 - try...catch 非常少有人會注意到 JavaScript 的 ES3 規范中規定 try/catch 的 catch 分句會創建一個塊作用域, catch 的參數變量僅在 catch 內部有效。 ``` try{ throw undefined; }catch(a){ a = 2; console.log(a); // 2 } console.log(a); // ReferenceError ``` - let ES6的標準使我們能夠簡單的創建塊作用域,其中一個變量定義方式是let關鍵詞定義。 let定義的變量具有以下的特點: 1. let隱形的創建塊作用域({...}) 2. let聲明的變量不能進行變量提升,因此只能先定義,后使用 ``` { let a = 1; console.log(a); // 1 } console.log(a); // ReferenceError ``` let一個典型的應用就是在for循環里 我們看下面兩個例子: ``` // 每秒輸出一個5 for( var i = 0; i < 5 ; i++ ) { setTimeout(() => { console.log( i ); }, i *1000) } // 依次輸出0,1,2,3,4,時間間隔位1秒 for( let i = 0; i < 5 ; i++ ) { setTimeout(() => { console.log( i ); }, i *1000) } ``` 其原因就是let形成了5個塊作用域,使每次輸出的變量都從本次循環的塊作用域中獲取。 當然我們還可以有其他方式做到第二種效果,我們將在 [閉包,是真的美](https://github.com/prettyEcho/deep-js/issues/4)中說道。 - const 除了 let 以外,ES6 還引入了 const,同樣可以用來創建塊作用域變量,但其值是固定的 (常量)。之后任何試圖修改值的操作都會引起錯誤。 ``` var foo = true; if (foo) { var a=2; const b = 3; // 包含在 if 中的塊作用域常量 a=3;//正常! b=4;//錯誤! } console.log( a ); // 3 console.log( b ); // ReferenceError! ``` ### 作用域鏈 > 作用域鏈是由當前作用域與上層一系列父級作用域組成,作用域的頭部永遠是當前作用域,尾部永遠是全局作用域。作用域鏈保證了當前上下文對其有權訪問的變量的有序訪問。 ``` var a = 2; function bar() { function foo() { console.log(a); } foo(); } bar(); // 2 ``` 上面代碼是由3層作用域氣泡組成,foo氣泡中試圖打印變量a,引擎在foo氣泡中未找到a變量,于是去其父作用域氣泡bar中尋找...以此類推直到找到全局作用域氣泡,發現有變量a,將其值打印出來。如若沒找到,報ReferenceError錯誤。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看