js重點——作用域——作用域分類及變量提升


  一、作用域分類

   定義:在js中,作用域是變量,對象,函數可訪問的一個范圍。

   分類:全局作用域,局部作用域,塊級作用域

   全局作用域:全局代表了整個文檔document,變量或者函數在函數外面聲明,那它的就是全局變量和全局函數。之所以全局變量在這個文檔的任何位置都可以訪問是因為它是window下的屬性,window是一個全局對象,它本身在頁面中任何位置可以用,同樣它身上的屬性在頁面的任何位置也是可以用的。

   聲明全局作用域的方法:把變量或者是函數放在函數外聲明或者變量不用var聲明直接賦值(不管是在函數內還是函數外它都是一個全局變量).要避免使用全局變量,聲明變量的時候一定要加上var.

<script> var x = 3; //在函數外,全局變量  function fn1() { var c = 10; //函數內,局部變量,它的范圍僅限於該函數,函數運行完成后,函數內定義的變量將會自動銷毀 c += 3; a=18; console.log(c); //13   x += 10; // 在函數內修改了全局變量后,全局變量就完成了修改,外面調用時,也是修改后的  } fn1(); console.log(x, "______"); //13 "______"  b=20; console.log(a);// 18 沒有用var聲明,雖在函數內聲明但也是全局變量 console.log(window); //可以看到a,b,x  </script> <script> console.log(x,a); //13 18 在這也可以訪問 </script> 

  全局作用域下帶var,聲明的一個變量,相當於給window全局對象設置了一個屬性,變量的值就是屬性值。

console.log(a);  //undefined
console.log(window.a);//undefined
console.log('a' in window);//true //在變量提升階段,在全局作用域中聲明了一個變量a此時就已經把a當做屬性賦值給window了,只不過此時還沒有給a賦值,默認值為undefined //in :檢測某個屬性是否隸屬於這個對象
var a = 12; console.log(a);// 全局變量a 12
console.log(window.a);//window的一個屬性名a 12
 window.a = 14; //全局變量值修改,window的屬性值也跟着修改
console.log(a); //14 //全局變量和window中的屬性存在“映射機制”

  全局作用域下不帶var, 直接賦值,那聲明的是window下的屬性。不能這樣做,有可能會修改window下的屬性不安全。

//=> 不加var的本質是window下的屬性 // console.log(n);//Uncaught ReferenceError: n is not defined
console.log(window.n);//undefined
console.log('n' in window);//false
n = 12; // <=> window.n = 12
console.log(n);//12
console.log(window.n);//12

  局部作用域:變量在函數內聲明,變量為局部作用域。只能在函數內部訪問。所以不同函數可以使用相同名稱的變量。函數執行完后局部變量會自動銷毀。函數可以嵌套,嵌套的函數可以訪問父函數里的內容。

  聲明局部作用域的方法:var 變量,function 函數名(){}.

    <script> function fn() { var a = 20; var b = 30; function fn1() { console.log(a + b); //50 嵌套函數可以訪問父函數里的內容
 } fn1(); } fn(); // console.log(a,b); //報錯 a,b是局部變量,在外面訪問不到 // fn1();//報錯 fn1是局部函數,在外面也是問不到的 //全局變量與局部變量重名
        var s1 = 10; function fn1() { var s1 = 20; s1 += 20; window.s1 += 10;  //如果全局變量的名稱在函數中和局部變量相同,想要調用全局變量時要在前面加上window前綴
            console.log(s1);  //40
 } fn1(); console.log(s1); //20 在函數內全局變量進行了改變

        var s2 = 10; function fn2() { console.log(s2); //undefined 函數當中有定義局部變量,函數作用范圍內所有位置都是這個局部變量,                  //此函數中下文定義了局部變量s2,但是這里是在定義之前調用的,所以s2的值為undefined
            s2 += 10; console.log(s2); //NaN undefined加數字為NaN
            var s2 = 20; } fn2(); </script>

  什么是作用域鏈,可以簡單的將作用域鏈理解為變量與函數的查找規則。

  查找規則:如是一個函數需要用到一個變量,那它會先在自己的作用域里去找這個變量,如果自己有那它就直接用自己的,如果自己沒有,那它就會一層層向外面找,直到找到外層的變量,找到后就用外層的變量(只會向外,不會向內找)。

function fn() { //變量提升無 //console.log(m);//Uncaught ReferenceError: m is not defined
    m = 13; console.log(m);//13
    console.log('m' in window);//true 在作用域鏈查找的過程中,如果找到window也沒有這個變量,相當於給window設置了一個屬性m
} fn(); //console.log(m);//13
var x = [12, 23]; function fn(y) { y[0] = 100; y = [100]; y[1] = 200; console.log(y); fn(x); console.log(x);

 

  在上面這個例子中我們要注意以下幾點:

    1、創建函數的時候就已經定義了函數的作用域,函數執行的目的是想讓存儲在堆中的代碼字符串執行,如果想讓代碼字符串執行那就必須有一個執行環境,所以會形成一個私有的上下文

    2、形成私有上下文后會進棧操作,把全局上下文放到棧的底部,新形成的上下文放到棧的頂部,執行完后出棧

    3、在私有上下文中也有可能創建變量,我們把它叫私有變量,私有變量放在AO中,AO是活動對象,函數中的變量對象都稱為AO。AO是VO的一個分支都是變量對象。

    4、在代碼執行前,要經過幾個階段:初始化作用域鏈、初始化this指向、初始化實參集合、形參賦值

    5、最后才是代碼執行

  二、變量提升

  通過前面的知識我們得知,在當前上下文中,js代碼自上而下執行之前,瀏覽器首先會把當前上下文中所有帶“var / function”關鍵字進行提前的聲明和定義,解析到它們對應作用域開始的位置(也可以理解為這是詞法解析的一個環節,語法解析發生在代碼執行前)這種預先處理的機制叫做變量提升,變量提升的意義在於創建變量前使用這個變量不報錯。變量提升也可以稱之為預解析。

  聲明(declare): var a  / function sum(默認值為undefined)

  定義(defined): a = 12(定義其實就是賦值操作)

  在變量提升階段,帶var的只聲明未定義,而帶function聲明和定義都完成了。

  變量提升只發生在當前作用域(如:開始加載頁面的時候只對全局作用域下的進行提升,因為此時函數中存儲的都是字符串而已);瀏覽器很懶,做過的事情不會重復的執行,也就是當代碼執行遇到創建函數這部分代碼后,直接跳過(在提升階段已經完成了函數的賦值操作)。

  私有作用域中,帶var的在變量提升階段,聲明為私有變量,與外界無關。不帶var不是私有變量,會向上級作用域查找,看是否為上級的變量,不是,繼續向上查找,它的上級作用域是誰和它在哪里執行無關,和它在哪里創建有關,在哪里創建,它的上級作用域就是誰。

      var foo = 1;
      function bar() {
        if (!foo) {
          var foo = 10;
        }
        console.log(foo);
      }
      bar();
console.log(g, h); var g = 12, h = 12; function fn() { console.log(g, h); var g = h = 13; console.log(g, h); } fn(); console.log(g, h);

var n = 10; function fn() { var n = 20; function f() { n++; console.log(n); } f(); return f; } var x = fn(); x(); x(); console.log(n);

  

  匿名函數和普通函數的區別:只對等號左邊的進行變量的提升。真實項目中建議用函數表達式創建函數,因為這樣在變量提升階段只會聲明,不會賦值。最好是把函數表達式匿名函數“具名化”,因為雖然是起了函數有了名字,但是這個名字不能在函數外面進行訪問。

/* * 全局上下文中的變量提升 * func=函數 函數在這個階段賦值都做了 */ func(); function func() { var a = 12; console.log('OK'); } func(); //=>Uncaught TypeError: func is not a function
var func = function () { // 真實項目中建議用函數表達式創建函數,因為這樣在變量提升階段只會聲明FUNC,不會賦值
    console.log('OK'); }; func(); var func = function AAA() { // 把原本作為值的函數表達式匿名函數“具名化”(雖說是起了名字,但是這個名字不能在外面訪問 =>也就是不會在當前當下文中創建這個名字) // 當函數執行,在形成的私有上下文中,會把這個具名化的名字做為私有上下文中的變量(值就是這個函數)來進行處理
    console.log('OK'); // console.log(AAA); //=>當前函數 // AAA(); 遞歸調用 而不用嚴格模式下都不支持的 arguments.callee 了
}; // AAA(); //=>Uncaught ReferenceError: AAA is not defined
func();

  條件判斷下的變量提升:在當前作用域下,不管條件是否成立都要進行變量提升。帶var的還是只聲明,帶function的在老版本瀏覽器渲染機制下,聲明和定義都完成,但考慮到es6中的塊級作用域,新版本瀏覽器對於在條件判斷中的函數不管條件是否成立只聲明不定義。

console.log(i);//undefined
if (1 === 2) { var i = 3; console.log(i); } console.log(i);//undefined
 console.log(fn);//undefined
if (1 === 1) { console.log(fn); //函數本身 //當條件成立進入到判斷體中(在es6中它是一個塊級作用域)第一件事並不是代碼執行,而是類似於變量提升一樣,先把fn聲明和定義了也就是判斷體中代碼執行之前,fn就已經賦值
 function fn() { console.log('ok'); } } fn();//函數本身 

  基於var或者function在全局上下文中聲明的變量(全局變量)會映射到GO(全局對象window)上,作為它的屬性,而且一個修改另外一個也會跟着進行修改。

var a = 12; console.log(a); //=>12 全局變量
 console.log(window.a); //=>12 映射到GO上的屬性a
 window.a = 13; console.log(a); //=>13 映射機制是一個修改另外一個也會修改 

   在es6中基於let/const等方式創建的變量或者是函數,不存在變量提升機制,它切斷了全局變量和window屬性的映射機制。 

  在相同作用域中,基於let不能聲明相同名字的變量(不管用什么方式在當前作用下聲明了變量,再次使用let創建都會報錯)。雖然就有變量提升機制,但是在當前作用域代碼自上而下執行之前,瀏覽器會做一個重復性檢測(語法檢測)自上而下查找當前作用域下所有變量,一旦發一有重復的,直接拋出異常,代碼也不會在執行了(雖然沒有把變量提前聲明定義,但是瀏覽器已經記住了,當前作用域下有哪些變量)
// console.log(a);//報錯
let a = 12; console.log(window.a); //undefined
console.log(a);  //12
 let b = 12; console.log(b); let b = 13; //Uncaught SyntaxError: Identifier 'b' has already been declared
console.log(b);

  現在最新版本要向前兼容es3/5規范, 要注意:1.判斷體和函數體等不存在塊級上下文,上下文只有全局和私有 2.不論條件是否成立,帶function的都要聲明和定義。

  向后兼容es6規范,要注意:1.存在塊級作用域,大括號中出現let/const/function...都會被認為是塊級作用域 2.不論是否成立,帶function的只提前聲明,不會提前賦值。

var a = 0; if (true) { a = 1; function a() {} a = 21; console.log(a); } console.log(a);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM