javascript之閉包六(閉包的作用與注意事項)

六、閉包的作用

閉包:內部函數保存到外部

當內部函數被保存到外部時,將會生成閉包。 閉包會導致原有作用域鏈不釋放,造成內存泄漏(內存佔用)

一)閉包的作用

  1. 實現公有變量: eg:函數累加器
  2. 可以做緩存(存儲結構):eg:eater
  3. 可以實現封裝,屬性私有化:eg:new Person();
  4. 模塊化開發,防止污染全局變量

二)閉包作用舉例

1、累加器:

題目:定義一個定時器,計算點擊網頁的次數 這個題目非常簡單,想必大家都能寫出來。

var count = 0;
function addCount() {
    count++;
}
document.body.addEventListener("click", addCount);
複製代碼

count作爲一個全局變量,其他地方都可以對它進行操作,如果其他地方對count重新賦值或者重新定義count,那麼這個計時器就被破壞了。這時候,閉包就起作用了。

function addCount() {
    var count = 0;
    var addCount = function() {
        count++;
   }
    return addCount;
}
document.body.addEventListener("click", addCount);

點擊一次->輸出1
點擊兩次->輸出2
複製代碼

2、緩存:

function  eater(){
    var food="apple";
    var obj={
        eat:function (){
            if(food!=""){
                console.log("i am eating "+ food);
                food="";
            }else{
                console.log("eat emtpy ");
            }
        }
        push:function(myFood){
            food = myFood;
        }
    }
    return obj;
}
var eat1= eater();
eat1.eat();->輸出apple
eat1.eat();->輸出empty
eat1.push('banana');
eat1.eat();->輸出banana
複製代碼

看另外一個緩存的例子:

function isFirstLoad(){
            var list=[];
            return function(option){
                if(list.indexOf(option)>=0){
                //檢測是否存在於現有數組中,有則說明已存在
                    console.log('已存在')
                }else{
                    list.push(option);
                    console.log('首次傳入');
                    //沒有則返回true,並把這次的數據錄入進去
                }
            }
        }

var ifl=isFirstLoad();
ifl("zhangsan"); 
ifl("lisi");
ifl("zhangsan");
複製代碼

在瀏覽器控制檯打印如下:

 

 

 

 可以看到,如果外界想訪問list變量,只能通過我定義的函數isFirstLoad來進行訪問,我對想訪問list的外界只提供了isFirstLoad這一個接口。至於怎麼操作_list,我已經定義好了,外界能做的就只是使用我的函數,然後傳幾個不同的參數罷了。

  最後順便說一下,作用域鏈是在定義的時候就已經確定了,和誰來執行,什麼時候執行均沒有一毛錢關係。

3. 私有化變量:下面例子輸入deng.prepareWife是undefind 形成了私有化變量

function Deng(name,wife){
//正常情況 函數裏的var對象 函數執行完了就會被銷燬,
但是在這裏因爲被this.divorce的函數使用被返回了形成了閉包,所以無法銷燬。
    var prepareWife="xiaozhang";
    this.name=name;
    this.wife=wife;
    this.divorce=function(){
        this.wife=prepareWife;
    }
    this.changePrepareWife=function(target){
        prepareWife=target;
    }
    this.sayPraprewife=function(){
        console.log(prepareWife);
    }
}
var deng=new Deng('deng','xiaoliu');
複製代碼

模擬實現類的私有屬性的例子:

function Boy(name){
     this.name = name;
     var sex = 'boy';
     this.saySex = function(){
     console.log("my sex is "+sex)
};
}
var xiaoming = new Boy('xiaoming');
console.log(xiaoming.name);
console.log(xiaoming.sex);
xiaoming.saySex();

VM344:16 xiaoming
VM344:18 undefined
VM344:9 my sex is boy
複製代碼

4. 立即執行函數解決閉包作用域問題:

立即執行函數(執行完立即銷燬) 針對初始化功能的函數 定義:此類函數沒有生命,在一起執行過後釋放。適合做初始化工作。

function test(){
    var arr=[];
    for(var i=0;i<10;i++){
        (function (j){
            arr[j]=function(){
            document.write(j+" ");//輸爲0,1,2,3,4,5,6,7,8,9
            }
        }(i));
        //用立即執行函數(立即執行函數也可以生成自己的作用域)
        i作爲參數傳給j j不會隨着i改變而改變
    }
    return arr;
}
var myArr=test();
for(var j=0;j<10;j++){
        myArr[j]();
    }
複製代碼
<ul>
  <li id="myli">a</li>
  <li id="myli">a</li>
  <li id="myli">a</li>
</ul>
<script type="text/javascript">
function test(){
  var liList=document.getElementsByTagName("li");
  for (i  = 0; i < liList.length; i++) {
    (function(j){
      liList[j].onclick=function(){
      console.log(j);   //輸出1 2 3   如果不用立即執行函數會報錯
    }
    }(i))
  }
}
test();
複製代碼

解決for循環中點擊事件的問題舉例 我們先來看一段代碼

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
複製代碼
var lis = document.getElementsByTagName("li");
for (var i = 0; i < lis.length; i++) {
    lis[i].onclick = function() {
        console.log(i);
    }
}
複製代碼

運行這段代碼後,大家或許會有疑問,爲什麼點擊任一個li標籤,console臺打印的都是3。 代碼運行結束後,給每個li標籤定義了click函數,但這個函數沒有立即執行,只有當點擊li時,纔會執行該click函數;當點擊li執行函數時,函數中的變量i沒有在函數中定義,根據js的作用域鏈原則,會繼續向上級作用域查詢,因此找到了全局作用域中的i,這時for循環已經執行結束,此時全局作用域中的i已經變爲了3,故打印出來的當然是3了。 要想實現,打印出來的值爲點擊li的順序值,這時,閉包又起到了作用

var lis = document.getElementsByTagName("li");
for (var i = 0; i < lis.length; i++) {
    lis[i].onclick = (function(i) {
       var clickLi = function() { 
           console.log(i);         
       }
       return clickLi;
    })(i)
}
複製代碼

在for循環執行時,立即將當前的i值作爲形參傳入clickLi中,而形參默認爲函數內的局部變量,函數外部是不能對i進行操作的。所以,當點擊li時,執行clickLi函數時,打印出來的則是li的順序值。

三)閉包的缺點及解決

缺點:函數執行完後,函數內的局部變量沒有釋放,佔用內存時間會變長,容易造成內存泄露。

解決:能不用閉包就不用,及時釋放。比如:

f = null;  // 讓內部函數成爲垃圾對象 -->回收閉包
複製代碼

總而言之,你需要它,就是優點;你不需要它,就成了缺點。

七、閉包的注意事項

1. 內存泄漏內存溢出

內存泄漏:佔用的內存沒有及時釋放。內存泄露積累多了就容易導致內存溢出。

常見的內存泄露:

  1. 意外的全局變量
  2. 沒有及時清理的計時器或回調函數
  3. 閉包

情況1舉例:意外的全局變量

    function fn() {
        a = new Array(10000000);
        console.log(a);
    }
    fn();
複製代碼

情況2舉例:沒有及時清理的計時器或回調函數

    var intervalId = setInterval(function () { //啓動循環定時器後不清理
        console.log('----')
    }, 1000)

    // clearInterval(intervalId);  //清理定時器
複製代碼

情況3舉例:閉包占用內存

<script type="text/javascript">
  function fn1() {
    var arr = new Array[100000];   //這個數組佔用了很大的內存空間
    function fn2() {
      console.log(arr.length)
    }
    return fn2
  }
  var f = fn1()
  f()

  f = null //讓內部函數成爲垃圾對象-->回收閉包
</script>
複製代碼

2. 關於閉包當中的this對象

this對象指的是什麼,這個要看函數所運行的環境。如果函數在全局範圍內調用 ,函數內的this指向的是window對象。對象中的方法,通過閉包如果運行的環境爲window時,則this爲window。因爲閉包並不是該對象的方法。

var color="red";
function fn(){
    return this.color;
}
var obj={
    color:"yellow",
    fn:function(){
        return function(){//返回匿名函數
            return this.color;
        }
    }
}
console.log(fn());//red  在外部直接調用this爲window
var b=obj.fn();//b爲window下的變量,獲得的值爲obj對象下的fn方法返回的匿名函數
console.log(b());//red  因爲是在window環境下運行,所以this指缶的是window

//可以通過call或apply改變函數內的this指向
console.log(b.call(obj));//yellow
console.log(b.apply(obj));//yellow
console.log(fn.call(obj));//yellow
複製代碼

通過變量可以獲得上一個作用域中的this指向

var color="red";
function fn(){
    return this.color;
}
var obj={
    color:"yellow",
    fn:function(){
        var _this=this;//將this賦值給變量_this
        return function(){
            return _this.color;//通過_this獲得上一個作用域中的this指向
        }
    }
}
console.log(fn());//red
var b=obj.fn();
console.log(b());//yellow
複製代碼

可以通過構造方法傳參來訪問私有變量

function Desk(){
    var str="";//局部變量str,默認值爲""
    this.getStr=function(){
        return str;
    }
    this.setStr=function(value){
        str=value;
    };
}
var desk=new Desk();
//爲構造函數的局部變量寫入值。
desk.setStr("zhangPeiYue");
//獲取構造函數的局部變量
console.log(desk.getStr());//zhangPeiYue


作者:前端娃娃
鏈接:https://juejin.im/post/5d50d5ddf265da03a715c59c
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章