由兩道題擴展的對做用域,做用域鏈,閉包,當即執行函數,匿名函數的認識總結

前言

最近在學JS,前幾天看到兩道題,剛開始看懵懵懂懂,這幾天經過各類查資料,慢慢的理解,頓悟了,對匿名函數,閉包,當即執行函數的理解也更深了一點,在此分享給你們個人理解與總結,但願能幫助你們理解.由於這篇文章是我用心總結的,查閱了不少的資料,因此總結的比較細,篇幅較長,若是沒耐心,建議跳出,點個收藏,之後若是要用到,有耐心想看時,方便查閱.另外若是有啥錯誤,還望指正前端


題目一

function fn() {
        for (var i = 0; i < 2; i++) {
            var variate = i;
            setTimeout(function () {
                console.log("setTimeout執行後:" + variate);
            }, 1000);
        }
        console.log(i);
    }
    fn();
複製代碼

最後結果是啥呢? es6

結果是,先打印2,再打印2個1 爲何呢? 先來梳理下函數執行過程:

  • 首先for循環遍歷i,(0,1)的時候分別將遍歷值傳給variate變量,variate變量最後保存的值爲1
  • 當i值爲2時,指針跳出循環,執行到打印i值這步,此時i=2
  • 執行函數fn(),執行完畢後,觸發setTimeout事件,由於循環2次,並且最後保存在這個做用域中變量的值爲1,因此最後輸出2個1
  • 因此最後的打印的值爲2,1,1

分析完了,先不急,咱們先來了解下setTimeout事件編程

setTimeout事件

  • setTimeout事件有兩個參數:事件,時間開始執行時間
  • setTimeout事件是異步的
  • 當調用setTimeout事件時,會把函數參數,放到事件隊列中。等主程序運行完,再調用

理解這個後,答案就很容易得出了瀏覽器


題目二

function fn() {
           for (var i = 0; i < 2; i++) {
               (function () {
                   var variate = i;
                   setTimeout(function () {
                       alert(variate);
                   }, 1000);
               })();
                            
           }
          console.log(i);
          console.log(variate);
       }
       fn(); 
複製代碼

先分析下總體結構: 函數體內包含一個for循環體,循環體內又包含一個匿名函數,造成閉包,加上兩個小括號-->(匿名函數)()造成當即執行函數bash

再思考下函數執行過程閉包

  1. i=0時,進入函數體內,由於是當即執行,因此i值進入匿名函數,經過做用域鏈,變量variate得到i值,匿名函數體內的setTimeout中的變量variate得到i值,第一輪循環結束;異步

  2. i=1時,執行與1一樣的過程;函數

  3. i=2,跳出循環,打印i,variate;ui

結果是啥呢? spa

Excuse me?居然有錯誤?

好,那就讓咱們來解決錯誤,錯誤顯示variate is not defined,原來是這樣,沒定義,那分析一波,爲何會顯示未定義呢? 首先咱們看函數內部,內部已經定義了,因此咱們想到做用域的問題

做用域和做用域鏈

  • 做用域

變量和函數的訪問區域,分全局做用域函數做用域,在es6中添加let關鍵字後有了塊級做用域概念.

變量提高: JS在解析代碼前會先將全部函數體內的變量,提高至函數體頂端,來看個例子

var Gscope = "global";
        function t() {
            var Gscope;
            console.log("這是全局變量:"+Gscope);//這是全局變量:undefined
            let Lscope = "local";
            console.log("這是局部變量"+Lscope);//這是局部變量local
        }  
    t();
    
複製代碼

爲何第一個值爲undefined?由於函數體內的Gscope變量被提高至函數體頂端,可是未賦值,so,undefined.

let關鍵字:let用於聲明變量,可是let聲明的變量只在let所在的代碼塊(塊級做用域)有用,OK,show code

for (let i = 0; i < 2; i++) {
            let i = 'a';
            console.log(i);//a a
        }
    console.log(i);//i is not defined
複製代碼
  • 做用域鏈

什麼是做用域鏈?有什麼用途?怎麼建立起來的?

先引用一句高級程序設計裏的話:

做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不實際包含變量對象

個人理解是:

做用域鏈就至關因而溝通執行環境內的各個變量與函數的橋樑,經過做用域鏈,同一執行環境裏面的變量和函數都有權利訪問對方;

不一樣的執行環境間是怎樣的呢?

不一樣執行環境間的交流仍是經過橋樑(做用域鏈),可是如今橋樑變成單行道了,只能容許內部環境訪問外部環境,但外部環境不能訪問內部環境.內部環境經過橋樑可以向上搜索查詢變量和函數,但外部卻不能向下搜索進入另外一個執行環境.理解這個後,出現題目二的問題,variate is not defined,就很容易理解了:

由於他們兩個壓根不在同一個執行環境,並且,裏面的變量對象經過閉包可以訪問外部環境變量,但外部環境變量無權訪問內部的變量variate.

這時可能又蹦出一個問題了,"橋樑"(執行環境的做用域鏈)怎麼搭建起來的呢?

  1. 先建立一個預先包含全局變量對象的做用域鏈,保存在內部的[scope]屬性中
  2. 調用函數時,爲函數搭建一個執行環境
  3. 複製函數的[scope]中的對象構建起執行環境的做用域鏈
  4. 建立活動對象,並將活動對象推入執行環境的前端

分析完後,再從新閱讀下做用域的概念,會發現頗有道理!


閉包

首先提出幾個問題:什麼是閉包? 爲何要用它?它有啥缺點?怎麼建立?

什麼是閉包?

閉包是指有權訪問另外一個函數做用域中變量的函數

先貼上剛剛那一段代碼

function fn() {
           for (var i = 0; i < 2; i++) {
               (function () {
                   var variate = i;
                   setTimeout(function () {
                       console.log("setTimeout執行後:"+variate);
                   }, 1000);
               })();//閉包,當即執行函數,匿名函數
                            
           }
          console.log(i);//2
          console.log(variate);//variate is not defined
       }
       fn(); 

複製代碼

經過定義能夠知道,閉包本質仍是做用域鏈的問題. 那爲何內部環境能訪問外部環境呢? 那就先探討下,函數調用時會發生什麼吧!

  1. 先建立執行環境和做用域鏈;
  2. 初始化函數的活動對象(命名參數值,arguments);
  3. 在做用鏈中搜索具備相應名字的變量,實現對變量的讀取和寫入;
  4. 調用執行完畢,銷燬局部活動對象,僅保存全局做用域. 因此關鍵仍是內部函數做用域鏈將外部的活動對象添加到本身做用域中了

這個例子中函數fn()內部嵌套了一個匿名函數造成閉包,內部的variate變量變爲私有成員變量,因此外部沒法訪問,於是會報錯variate is not defined

爲何用閉包?

  • 由於在閉包內部保持了對外部活動對象的訪問,但外部的變量卻沒法直接訪問內部,避免了全局污染;
  • 能夠當作私有成員,彌補了因js語法帶來的面向對象編程的不足;
  • 能夠長久的在內存中保存一個本身想要保存的變量.

閉包有啥缺點呢?

  1. 可能致使內存佔用過多,由於閉包攜帶了自身的函數做用域
  2. 閉包只能取得外部包含函數中得最後一個值

怎麼建立閉包? 在函數內部嵌套使用函數


匿名函數

什麼是匿名函數? 顧名思義,就是沒有名字的函數 如例子中的代碼就是一個匿名函數

function () {
                   var variate = i;
                   setTimeout(function () {
                       console.log("setTimeout執行後:"+variate);
                   }, 1000);
               }
複製代碼

匿名函數優缺點?
優勢:能夠經過var關鍵字建立函數表達式,函數表達式不會出現變量提高的狀況,只有在真正被解釋執行的時候纔會執行到函數表達式所在的代碼行,有效避免了全局污染;

缺點:匿名函數綁定的事件不能解綁


當即執行函數

什麼是當即執行函數?有什麼做用?

什麼是當即執行函數?

聲明一個匿名函數,而且立刻調用它{經過加()的形式}

當即執行函數的形式

(匿名函數)();

(function () {
                   var variate = i;
                   setTimeout(function () {
                       console.log("setTimeout執行後:"+variate);
                   }, 1000);
               })()
複製代碼

爲何要用小括號將匿名函數包裹起來?

爲了經過瀏覽器的語法檢查

做用? 建立一個獨立的做用域,避免全局污染


小結

經過兩道題擴展出來知識點,而且總結出來,如今對知識點的基礎概念,以及一些實現原理有了很清晰的認識,這種感受很棒

相關文章
相關標籤/搜索