解析js中作用域、閉包——從一道經典的面試題開始


如何理解js中的作用域,閉包,私有變量,this對象概念呢?

就從一道經典的面試題開始吧!

題目:創建10個<a>標簽,點擊時候彈出相應的序號

先思考一下,再打開看看
     //先思考一下你會怎么寫,是不是這樣?        可是結果呢,彈出來的都是10,為啥?
     
     var i,a
     for(i=0;i<10;i++){
          a=document.createElement('a')
          a.innerHTML=i+'<br>'
          a.addEventListener('click',function(event){
               var event=event||window.event
               event.preventDefault()
               alert(i)                  
          })
          document.body.appendChild(a)
     }
View Code

這個題目答案最后再說,當然,你就可以直接翻到最后了解,不過我不建議這樣!!


 

涉及到今天的變量、作用域、閉包等等,在變量、作用域知識中已經說了一些,再重新簡單的說一下。

執行環境:也稱之為執行上下文,兩個,一個是全局環境(window),一個是函數環境(每個函數都是一個函數環境)

作用域鏈:當代碼在某一個環境中執行,會創建變量對象的一個作用域鏈,以保證對執行環境有權訪問的所有變量和函數的有序訪問。外部環境訪問不到內部環境;內部環境通過作用域鏈可以訪問外部環境。

塊級作用域:js沒有塊級作用域,類似循環語句、條件判斷語句內作用域都是全局的;只有兩個執行環境

變量:var操作符判斷是局部變量還是全局變量。

瀏覽器解析:瀏覽器解析時,先將所有的變量提升,變量都存為undefined,當執行到哪一行才給予賦值;函數聲明一開始就是完整的,而函數表達式跟解析變量是一樣的

     console.log(a)         //undefined
     var a=100
     console.log(a)         //100
     
     person("double")       //double,20
     function person(name){ //聲明函數
          age=20
          console.log(name,age)
          var age
     }

this:引用執行環境的對象;只有在執行時才能確認,定義時無法確認

        //普通的構造函數;this就是構造函數的實例
        function Foo(name){
            //var this
            this.name=name
            //return this
        }
        var f=new Foo("double")

        //普通對象;this就是這個對象
        var obj={
            name:"double",
            printName:function(){
                console.log(this.name)
            }
        }
        obj.printName()

        //普通函數 this就是window
        function fn(){
            console.log(this)
        }
        fn()

        //call apply bind來設置this的值
        function fn1(name,age){
            alert(name)
            console.log(this)
        }
        fn1.call({x:100},'double',20)      //this 就是{x:100}這個對象
        fn1.apply({x:100},["double",20])   //this 就是{x:100}這個對象

        var fn2=function (name,age){
            alert(name)
            console.log(this)
        }.bind('double',20)                //this 就是double了     
        fn2()

閉包

這個概念有些抽象,這樣理解:有權訪問另一個函數作用域中的變量的函數;也就是說一個函數可以訪問另一個函數作用域里的變量

一般兩個地方,一個作為返回值,一個作為傳遞的參數

//閉包

//函數作為返回值
function F1(){
    var  a=100;

    //返回一個函數(函數作為返回值)
    return function(){
        console.log(a)
    }
}

//f1 得到一個函數
var f1=F1()
var a=200
f1()          //a=100     執行返回函數,函數在定義函數的作用域查找變量(而非執行函數的作用域),沒有才會往父級作用域查找


//函數作為參數傳遞

function F1(){
    var a=100
    return function(){
        console.log(a)
    }
}

var f1=F1()   //這是個函數

function F2(fn){
    var a=200
    fn()
}
F2(f1)        //a=100       給函數傳參給F2

 以閉包為返回值為例,詳細說一下

     //這是一個閉包以返回值的例子
function create(){ var a=100 return function(){ console.log(a) } } var a=200 var result=create() //得到返回函數 result() //執行返回函數 a=100 函數會在定義函數的作用域中查找,然后再在父級中查找 當某個函數被調用的時候,會創建一個執行環境及相應的作用域鏈,使用arguments和其他參數 來初始化函數的活動對象,但在作用域鏈中,外部函數的活動對象始終處於第二位, 對於create函數內部的匿名函數,其作用域鏈被初始化包含三個的活動對象,一個自身的,一個為create函數的,一個是全局環境的 一般來說,函數執行完畢,其活動對象就已經被銷毀; 但是對於閉包來說,當create函數執行完畢,函數的活動對象依然不會銷毀,因為內部的匿名函數一直引用其中的活動對象

再來看一下這個

     //普通對象 的 一個方法
     var person={
         name:"double",
         getName:function(){
              console.log(this.name)
         }
     }
     person.getName()     //double
     
     //普通對象方法返回的是匿名函數
     var person={
           name:"double",
           getName:function(){
                  return function(){
                         console.log(this.name)
                  }
           }
     }
     var name="single"
     person.getName()()     //single
   
     //普通對象方法返回的是匿名函數
     var person={
         name:"double",
         getName:function(){
              var that=this           //在定義匿名函數前將this對象賦予給that的變量  this和arguments差不多,也要將其賦值給某個變量
              return function(){
                     console.log(that.name)
              }
         }
     }
     var name="single"
     person.getName()()    //double

 ok,了解閉包、作用域鏈的知識后,回到那道面試題,應該知道其中的原因

    正確的寫法
var i for(i=0;i<10;i++){ (function(i){ var a=document.createElement('a') a.innerHTML=i+'<br>' a.addEventListener('click',function(event){ var event=event||window.event event.preventDefault() alert(i) //去父作用域查找 }) document.body.appendChild(a) })(i) } //js沒有塊級作用域,所以alert(i)的i是在全局作用域下的,返回的當然是10, //如何讓i是每個彈出來的呢? //我們知道環境分函數環境和全局環境,只需要將每個i都有自己的執行環境就可以了 //怎么弄? //直接加個自執行函數,讓每個i都有自己的函數環境,也就是構建出私有作用域

內存泄露 

由於閉包的存在,外部函數的變量始終被引用着,閉包不結束,則引用始終存在,內存也就不會被回收

       //內存泄露
       function person(){
              var element=document.getElementById("someElement")
              element.onclick=function(){
                       alert(element.id)
              }
       }
       
       //解決內存泄露
       function person(){
              var element=document.getElementById("someElement")
              var id=element.id    //將所需內容賦值給一個變量
              element.onclick=function(){
                       alert(id)
              }
           element=null         //閉包會引用包含函數的整個活動對象,其中包含着element
       }

模仿塊級作用域

都是知道的,js沒有塊級作用域,所以要模仿,怎么模仿呢?

上面的面試題已經告訴了:通過匿名函數來構造函數環境,形成私有作用域

下面解釋有關涉及到匿名函數,函數聲明,函數表達式的東西,說明一下塊級作用域

     //變量名只是個值的另一種形式
     var count=10
     alert(count)  
     alert(10)
      
     //既然變量可以,那么函數呢 
     var person=function(){
           alert("hello")
     }
     person()

     var person=function(){
          alert("hello")
     }()

     
     function(){
          alert("hello")
     }()    //報錯
     //為啥?因為js將function關鍵字作為一個函數聲明的開始,而函數聲明是不能加圓括號
     
     (function(){
         alert("hello")
     })()
     //將函數聲明轉為函數表達式即可,外部加個圓括號即可,也就是形成自己的私有作用域

看了湯姆大叔的博客,關於這個,他是這么說的

     //對於函數聲明和函數表達式的轉換
     //js中括號()是不包含語句的,和js中的&&,||,逗號等操作符是在函數表達式和函數聲明上消除歧義的
     //所以
     var i=function(){console.log("hello")}()
     true &&function(){console.log("hello")}()
     0,function(){console.log("hello")}()
     !function(){console.log("hello")}()
     ~function(){console.log("hello")}()
     -function(){console.log("hello")}()
     +function(){console.log("hello")}()
     new function(){console.log("hello")}
     var value=10
     new function(){console.log(value)}(value)   //括號傳遞參數

關於閉包的知識,就說這么多


免責聲明!

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



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