JS之作用域與變量提升

作用域與變量提升

作用域(棧內存、執行上下文)

全局作用域(全局棧內存)

  • 瀏覽器打開一個頁面,開始運行時率先形成一個棧內存,這個棧內存又叫全局作用域,爲代碼提供執行環境,在全局作用域下會生成一個全局的大對象叫window。
  • 瀏覽器打開,生成的全局作用域一般不銷燬,直到頁面關閉。

全局變量

  • 在全局作用域下聲明的變量就是全局變量
  • 在全局下定義的變量會默認給window新增一個屬性名,屬性名是變量名,屬性值是變量所存儲的值;
  • // 在全局作用域下定義的函數,也會給window新增鍵值對;屬性名是函數名,屬性值時函數的空間地址;
  • c = 100; //在函數裏,也會給window新增鍵值對
  • 當全局作用域中存在n,window中也存在n,會先找全局,再找window;
// let  : let聲明的變量不能給window新增鍵值對;const聲明的常量也不能新增鍵值對;var聲明的變量可以給window新增鍵值對;
const e=0;
let  d=1;
var f=3;
console.log(window.d,window.e,window.f);//undefined undefined 3
  • 全局變量的區別
    1. 用var和function 聲明的變量會在全局作用域下率先創建,而且也會給window增加屬性,屬性名是變量名,屬性值是變量名儲存的值(let,const不可以)
      var s = 12;
      function fn(){}
      console.log(window.s)
      console.log(window.fn)
      let a = 12;
      `console.log(window.a) // undefined``

    2. var跟function可以重複創建同一個變量名(let不可以)
      var a = 12;
      var a = 13;
      console.log(a) // 13
      let a = 12; // 報錯 SyntaxError(語法錯誤)
      `let a = 13;``

    3. var和function有變量提升(let沒有)
      b = 12 // 等價於window.b = 12因爲window.可以省略
      var b = 12(有變量提升)

創建變量和賦值

var a,b,c = 12; //undefined   undefined  12
//創建變量a,b,c,但是隻給c賦值
var a = b = c = 12; // 等價於 var a = 12; b = 12; c = 12;
var a = 12,b = 13, c = 14; // 創建三個變量,給每一個變量都進行賦值
       
		var a, b, c = 100; // 代碼時被var聲明過,只是a,b沒有被賦值
        console.log(b); //undefined
        var d = e = f = 200; // e,f沒有被var;只有d被var過;但是def都被賦值200;
        console.log(e); // 200

私有作用域(私有棧內存)

  • 全局作用域生成之後纔會有私有作用域,私有作用域屬於全局作用域
  • 函數執行時會形成一個私有作用域(私有棧內存)爲函數內的代碼執行提供環境。
  • 函數執行會形成私有的作用域,會保護裏面的私有變量不受外界的干擾;

創建函數時

  1. 首先開闢一個堆內存生成一個16進制的空間地址
  2. 把函數體裏的代碼以字符串的格式存儲進去
  3. 把16進制的地址賦值給函數名

執行函數時

  1. 首先開闢出一個私有棧內存(私有作用域)
  2. 形參賦值
  3. 變量提升
  4. 代碼從上往下執行
  5. 作用域是否被銷燬

私有變量

  • 在私有作用域中定義的變量就是私有變量(var、function、let、const····)
  • 形參也是私有變量
  • 在私有作用域裏使用一個變量,如果自己私有作用域裏有,直接用自己的,如果沒有,就取上一級作用域的
  • 函數外邊不能拿到函數裏邊的變量
  • 在函數私有作用域中定義的變量,不會給window新增鍵值對;
  • 在函數體中函數不會給window新增鍵值對,函數外面不能調用此函數

變量提升(聲)

  • 變量提升就是瀏覽器解析代碼的一個過程,發生在js代碼之前。
  • 變量提升的含義
    • 在當前作用域,代碼執行之前。瀏覽器會從頭到尾檢測一遍所有變量,給帶var和function進行提前聲明和定義。
    • 帶var的會只聲明(創建變量),不定義(賦值)
    • 帶function的既聲明(創建變量),又定義(賦值)
  • 當函數執行的時候,纔會對函數體中的代碼發生變量提升;

  • let會形成塊級作用域,let和{}結合就會形成塊級作用域

{
            let w = 13
        }
        if(true){
            var s = 13;
            let w = 12;
        }
        console.log(w)
        console.log(s)

        console.log(a)
        解決暫時性死區
           console.log(typeof a) 
           let a  = 12;
       
           const w;//const創建常量必須賦值
           console.log(w)

變量提升的特殊情況

  1. 變量提升發生在等號左邊
var a = function () {}//此處,瀏覽器變量提升時,只識別var,不識別function
  1. 不管if條件是否成立,if裏的代碼都要進行變量提升
    • 在老版本瀏覽器裏,if條件裏的function既聲明,又定義
    • 在新版本瀏覽器裏,if條件裏的函數只聲明,不定義
    • 條件一旦成立,馬上給函數進行定義,然後再執行代碼
console.log(num)//undefined
if(false){
   // 只要進入到塊級作用域,立即開闢堆內存;對函數賦值
  var num = 12;
}
console.log(num)//undefined



console.log(fn); // undefined
    // 在老版本瀏覽器裏,if條件裏的function既聲明又定義,
    // 在新版本瀏覽器裏,if條件裏的函數只聲明不定義
if(false){
     // 條件一旦成立,第一件事就是給函數名賦值,然後在執行代碼
     fn()
     function fn(){}
 }
 console.log(fn) // undefined



console.log(fn); // undefined
    // 在老版本瀏覽器裏,if條件裏的function既聲明又定義,
    // 在新版本瀏覽器裏,if條件裏的函數只聲明不定義
if(true){
     // 條件一旦成立,第一件事就是給函數名賦值,然後在執行代碼
     fn()
     function fn(){}
 }
 console.log(fn) // fn(){}
  1. 在函數裏,雖然return下面的代碼不執行,但是要進行變量提升,return 後面的代碼不進行變量提升的;
function fn() {
     console.log(ss); // undefined
     return function f(){
              };//return返回值沒有變量提升,中斷下面代碼執行
     var ss = 34;//此處的ss仍然要變量提升。永遠是undefined
 }
 fn()

  1. 自執行函數、匿名函數不進行變量提升
 // 當代碼執行到這一行時,先開闢一個空間地址,然後再執行
(function(){
            
})()
var  fn = function(){};
//
        var obj = {
            fn:(function(){
                console.log(100);
                // 如果fn的屬性值時一個自執行函數,那麼當代碼以鍵值對存儲的時候
              //(當代碼執行到這一行時,(會先開闢堆內存空間,然後再立即形成棧內存,
        代碼執行)自執行函數就會運行),並且把自執行函數的執行結果賦值給屬性名fn;
                return function(){

                }
            })()
        };
        console.log(200)
        obj.fn();
        obj.fn();// 執行函數中返回的小函數執行

5.如果變量名重名,不再進行重複聲明,但是要重新賦值;


        var num = 1;
        console.log(num);// 1
        var num = 2;
        console.log(2);
        fn();// 4
        function fn(){
            console.log(1);
        }
        function fn(){
            console.log(2);   
        }
        fn();// 4
        function fn(){
            console.log(3);
        }
        fn=100;
        function fn(){
            console.log(4);   
        }
        fn();// 報錯

6.函數裏的 let ,詞法解析的時候定義了一下,let沒有變量提升


//1.let 聲明的變量不進行變量提升;
        
				var a = 1;
        function bar() {
            console.log(b)//Cannot access 'a' before initialization//初始化前無法訪問“a”
            let b = 10;//詞法解析的時候定義了一下,let沒有變量提升
        }
        bar();
//2.let聲明的變量在同一個作用域下不允許重名; 
// 在代碼運行之前,會對當前作用域下帶let進行解析,判斷是否有重名的變量;有的話,就直接報錯;
        // let a=1;
        // let a=2;    
  1. 給window新增鍵值對是發生在變量提升的階段;
//console.log(window);
//var a=100;

微信圖片_20191119124659.png

對象裏的in屬性

in屬性,檢測一個對象中有沒有某個屬性名,如果有就返回true,反之返回false

let obj = {
	name:1
}
console.log('name' in obj)//true
console.log('d' in obj)//false


console.log(fn); // undefined
// var和function給window增加屬性的過程是變量提升的時候發生的
if ('fn' in window) {
	// 如果條件成立,進來的後的第一件事就是給fn賦值,然後在執行代碼
	fn(); // 'erYa'
	function fn() {
	    console.log('erYa');
	}
}
fn();

函數的作用域查找

函數的上一級作用域是誰,在函數定義的時候就已經確定了,函數在哪創建的,他的上一級作用域就是誰,跟函數在哪執行沒有關係。

作用域鏈查找機制

在私有作用域中,函數執行,如果要使用一個變量,獲取變量所對應的值時,自己作用域要是有,就使用自己的,要是沒有,就向上一級作用域查找,上一級還沒有,在向上一級查找,直到找到全局作用域,如果還沒有就報錯—>這種一級一級向上查找的機制就是【作用域鏈查找機制】
TIM截圖20191119163857.png

        /* 
        n = 1
        fn:fn
        x = f
        */
        var n = 1;
        function fn() {
            /* 
            n = 2 1 0
            f:f
            */
            var n = 2;
            function f() {
                /* 
                
                */
                n--;
                console.log(n); // 1 0 
            }
            f();
            return f;
        }
        var x = fn();
        x();
        console.log(n); //1

閉包


  • 函數執行形成的私有作用域就是閉包,他可以保護裏邊的私有變量不受外界干擾,
  • 還可以保存變量
  • 利用了函數執行會形成不銷燬的作用域,可以保存變量的特點=>這個機制也叫閉包
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        #box {
            width: 500px;
            margin: 10px auto;
        }

        ul {
            list-style: none;
            position: relative;
            top: 1px;
            text-align: center;
            display: flex;

        }

        li {
            border: 1px solid black;
            line-height: 50px;
            height: 50px;
            font-size: 30px;
            margin-right: 20px;
            padding: 0 10px;
        }

        #box div {
            width: 500px;
            text-align: center;
            height: 300px;
            line-height: 300px;
            font-size: 50px;
            color: orangered;
            border: 1px solid black;
            display: none;
        }

        #navList li.active {
            border-bottom-color: white;
        }

        #box div.active {
            display: block;
        }
    </style>
</head>

<body>
    <div id="box">
        <ul id="navList">
            <li class="active">erYa</li>
            <li>jinYu</li>
            <li>xiaoHua</li>
        </ul>
        <div class="active">長的這俊哩</div>
        <div>你好帥啊</div>
        <div>哈哈</div>
    </div>
    <script>
        let navList = document.querySelectorAll('#navList li');
        let tabList = document.querySelectorAll('#box div');
        console.log(navList, tabList);

        // for (var i = 0; i < navList.length; i++) {
        //     // 把當前的i保存到元素結構上
        //     navList[i].setAttribute('myIndex', i);
        //     // 給每一個li綁定點擊事件
        //     navList[i].onclick = function () {
        //         // this:點擊誰,this就是誰
        //         //點擊相應的li,獲取到li結構上的myIndex屬性,當做實參傳遞給fn方法
        //         fn(this.getAttribute('myIndex'))
        //     }
        // }

        function fn(index) {
            // 清除每一個li和每一個div的樣式名
            for (var i = 0; i < tabList.length; i++) {
                navList[i].className = ''
                tabList[i].className = ''
            }
            // 給相應的li和div加上樣式名
            navList[index].className = 'active'
            tabList[index].className = 'active'
        }




        // for (var i = 0; i < navList.length; i++) {
        //         // 當往元素的堆內存中存儲鍵值對的時候,自執行函數就已經運行了
        //     navList[i].onclick = (function(index){
        //         // 函數return了一個引用數據類型值,而且return的值被外界所接收,所以這個作用域不銷燬
        //         // 把return後面這個小函數賦值給onclick
        //         // 其實就是利用了函數執行會形成不銷燬的作用域,可以保存變量的特點=>這個機制也叫閉包
        //             return function(){
        //                 fn(index)
        //             }
        //     })(i)
        // }

        /* 
        利用了閉包可以保存私有變量的特點,
        而且這個閉包是不銷燬的作用域
        i=0
        navList[0].onclick = (function(index){ // 0
                    return function(){
                        fn(index)
                    }
            })(i) // 0

        i=1
        navList[1].onclick = (function(index){ // 1
                    return function(){
                        fn(index)
                    }
            })(i) // 1

        i=2
        navList[0].onclick = (function(index){ // 2
                    return function(){
                        fn(index)
                    }
            })(i) // 2
         */


        // for (let i = 0; i < navList.length; i++) {
        //     // ES6中let在for循環的大括號會形成塊級作用域
        //     navList[i].onclick = function () {
        //         fn(i)
        //     }
        // }

        /* 
        {i=0
             navList[0].onclick = function () {fn(i)}
        }

        {i=1
             navList[2].onclick = function () {fn(i)}
        }

        {i=2
             navList[2].onclick = function () {fn(i)}
        }

         */
    </script>
</body>

</html>

塊級作用域

//  
		console.log(num);// undefined
        console.log(fn);// undefined
        if([]){
            // 只要進到當前if條件中,會立即對fn進行賦值;
            // 支持es6的瀏覽器,會把這個if的大闊號解析成一個塊級作用域;
            fn()
            var num=100;
            function fn(){console.log("a")}
        }
        console.log(fn);//fn(){console.log("a")}
//
        console.log(fn);// undefined
        for(var i=0;i<2;i++){
            function fn(){}
        }
        console.log(fn);//function fn(){}
//
        console.log(f); //undefined
        let i = 0;
        while (i < 1) {
            i++
            function f() {

            }
        }
        console.log(f); //function fn(){}
  • 塊級作用域,和形參重名,函數賦值不能影響外面
         var b = {
            a: "hello"
        };
        function fn(b) {
            b.a = "world";
            if (true) {
                function b(){}//塊級作用域,和形參重名,函數賦值不能影響外面
            }
            console.log(b);//{a: "world"}

        }
        fn(b)
  • 在塊級作用域下var和function是不可以重名的
       //  在塊級作用域下var和function是不可以重名的
        if (!("aa" in window)) {
            var aa = 1;

            function aa() {
                console.log(aa)
            }
        }
        console.log(aa);//報錯Identifier 'aa' has already been declared  已聲明標識符“aa”//
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章