JavaScript做用域

什麼是做用域

爲何要了解做用域

  做用域首先是變量或者函數的存儲規則,很重要,重要性無異於孩子他媽得知道孩子他爸在哪。javascript

什麼是做用域

  不是存儲區域的名稱,是根據變量名稱存儲和查找變量的一套規則,能夠說是一個畫圈的算法,把相同做用域得變量畫一個圈,父子級的畫個內圈。java

相關概念

  • 編譯器工做的三個階段:詞法分析(間隔成一個個詞法單元)、語法分析(根據嵌套規則生成語法樹)、代碼生成(將代碼轉換成機器指令併爲變量分配內存)。
  • 做用域的做用:一個是編譯期間與編譯器交互,將聲明的變量建立到對應做用域下;另外一個是代碼執行期間與引擎交互,在對應的做用域查找變量值和對相應做用域下的變量進行賦值。
  • 查找規則:LHS(left-hand-side),LHS能夠理解爲被賦值的變量應用的查詢規則,RHS能夠被理解爲取值的變量應用的查詢規則。二者在查詢不到結果時表現不同,非嚴格模式下,LHS會將查詢不到的變量聲明會全局變量,RHS則會拋出異常。

做用域在JavaScript 中和其它部分交互的

   舉例 var a = 2 涉及過程:es6

   1)詞法分析階段:編譯器碰到var a這類的聲明會向做用域詢問是否已存在該變量,而後生 成執行代碼。算法

   2)代碼執行過程:引擎運行代碼,進行賦值操做,賦值前會從當前做用域開始執行LHS查詢(見查找規則)是否又a這個變量,若是沒有拋出異常 Uncaught ReferenceError: a is not defined,若是有則進行賦值操做。瀏覽器

問題:JavaScript爲何被設計成解釋型語言?

   本身想到緣由是,解釋型語言方便別人查看和複用。安全

什麼是詞法做用域

上面說到了做用域的概念以及涉及的過程,那麼詞法做用域是什麼?

  表面上的意思是詞法分析階段定義得做用域,具體過程是詞法分析階段進行的是有狀態的解析過程,給單詞賦予語義,此時根據語義和做用域規則獲得了詞法做用域。閉包

那麼問題來了爲何沒有語法做用域?

  百度了下,好像真有語法做用域,不過和詞法做用域是一個東西,他大舅他二舅都是他就,咱們這裏暫且稱爲詞法做用域吧。ide

知道了什麼是做用域,也知道了做用域如何和其它部分交互,那麼它內部是如何工做的,也就是如何畫圈的

  暫且忽略塊級做用域,能夠認爲做用域是根據函數來畫圈的,切記這是詞法做用域,不是你調用時候畫圈的,而是根據函數代碼位置來畫圈的,千萬別掉坑裏。函數

上面的解釋準確嗎

  除了詞法做用域還有動態做用域,大部分狀況下是詞法做用域,那麼什麼是動態做用域,動態做用域是相對於詞法做用域這種靜態做用域的,就是在代碼執行階段改變做用域。性能

// eval, 特性:動態修改所處做用域

function foo(str, a){

​    eval(str);

​    console.log(a, b); //1 3 

}

var b = 2;

foo("var b = 3;", 1)

: 根據詞法做用域,詞法分析時並無b = 3的賦值,它只是一個字符串,那麼打印的3確定是執行代碼時賦值的,那麼作頗有趣,不用聽詞法分析的指揮了,咱們能夠隨時修改做用域,可是這樣作性能損失大,而且在嚴格模式下並不這樣。

// with, 特性:根據參數對象建立一個特殊做用域,該做用域包含參數對象的屬性同樣的變量標識符,可是該做用域中var聲明的變量不限制在當前做用域,而是添加到with所處做用域。

function foo(obj) {

​    console.log(age); // undefined !!!被認爲當前做用域的變量提高

​    with(obj){

​        name = "world";

​        sex= "男"

​        var age = '18';

​    }

}

var per = {

​    name: "hello"

};

foo(per);

console.log(per); // {name: "world"}

console.log(sex); // 男 !!!,和普通變量同樣。未聲明直接賦值被添加到全局變量中。

這麼有趣的東西爲何使用頻率那麼少呢,缺點:

​ 一、在嚴格模式下表現不一。

​ 二、致使詞法分析階段的優化失效或者編譯時碰了欺騙語法乾脆不進行優化了。

​ 三、大量的eval和with會致使代碼阻塞。
函數做用域和塊級做用域都是做用域,二者有相同點,也有區別,ES6中開始有塊級做用域。

塊級做用域和函數做用域

什麼是函數做用域

包含當前函數所有變量的範圍。

爲何要有函數做用域

首先要弄清爲何要有函數,函數的目的是隱藏,定義私有變量,最小限度的暴露必要內容,這樣作的好處是一方面私有變量更安全,不容易被修改,另外一方面是不容易出現命名衝突,這也就是函數做用域的目的。

1、函數是產生「氣泡」(即做用域)的一種方式,另外還有塊級做用域。

什麼是塊級做用域

一些關鍵詞{}之間的內容做爲了塊級做用域的範圍,目前來講JavaScript沒有真正的塊級做用域,不對?es6是有塊級做用域的概念了,但不是嚴格意義的做用域,看下面。

// 支持es6的瀏覽器下運行
console.log(foo); // undefined 
if(true){
    console.log(foo); // foo ... 我是老大
    function foo(){
        console.log('我是老大')
    }
} else {
    function foo(){
        console.log('我是老二?');
    }
}
console.log(foo); // foo ... 我是老大

第一處輸出undefined就很奇怪,這裏的意思是有這個聲明瞭可是沒賦值,塊級做用域的話不該該在這聲明,不合理啊。

第二處塊級做用域的代碼執行完了可是變量並無銷燬。

下面看一下ES5環境下的輸出

// es5環境(https://pan.baidu.com/s/1piMP-0E5c-FT24Dy8iNzcQ)
 function bar(){
     console.log(foo)
     if(true){
         function foo(){
            console.log(1)
         }
     } else {
         function foo(){
            console.log(2)
         }
     }
 }
undefined
> bar()
[Function: foo] // 總結一下:就是ES6有了做用域的概念但並非真的做用域,得配合let 或者 const聲明。
undefined
>

爲何要有塊級做用域

塊級做用域作爲函數做用域的擴展,使變量在更近的做用域內使用,增長了代碼的可讀性。

提高

什麼是提高

提高有變量提高和函數提高。變量提高(var 聲明),是在變量聲明位置的前面打印變量是undefined而不是not defined,就是該變量還沒有初始化可是已經聲明瞭;而函數提高(非函數表達式的具名函數)是在函數聲明的位置以前就能夠調用。

爲何會提高

由於JavaScript中的做用域是詞法分析是肯定的因此變量和函數執行代碼以前已經聲明好了,因此看上去就像是提高了。

提高的優先級

同一個做用域中變量和函數聲明同時出現了提高,那麼函數聲明的提高優先級更高,若是有多個變量提高那麼後面聲明的優先級更高,函數也是如此。

代碼塊(if、for)中的函數聲明的提高並不帶函數體,一樣是undefined。

這纔是閉包

什麼是閉包

    這個過重要了,我關注這個概念一年多,今天感受明白了,以前百度「JavaScript 閉包」,結果給的概念大部分是"經過閉包能夠在函數外部能訪問到函數內部的變量」,是這個嗎?很明顯不是,由於這個顯然說的是閉包的用途,今天我想從根上刨一下,爲何叫閉包?後來發現數學中的拓撲分支有閉包這個概念,難道。。。。拓撲中的閉包是這樣定義的:集合和這個集合的導集的並集,集合都知道是啥,導集的意思是凝聚點構成的集合,凝聚點是啥呢,我理解是集合的邊界點,也就是拓撲中閉包的概念是:集合和集合的邊界點集合的並集。看MDN上關於閉包的定義:函數和函數所在詞法環境的組合,若是把函數當成一個集合,把詞法環境當成邊界點,那麼這不正是拓撲的閉包嗎。因此說某個函數是閉包並不許確,閉包還有函數所處的詞法做用域,只要建立一個函數均可以說是建立了閉包,儘管它不能被外部訪問,能被外部訪問的確定是閉包,由於這個利用了閉包特性。

爲何要用閉包

從定義上分析閉包是函數和函數所處的詞法做用域的組合,那麼若是咱們在某個地調用了函數,那麼也就是在這個地訪問了對應的做用域,這個地能夠是返回值是函數的地方,能夠是你能調用這個函數的任意地方。

相關文章
相關標籤/搜索