JS面試點-閉包

什麼是閉包:

  • 有權訪問另外一個函數做用域的變量的函數。
  • 閉包屬於一種特殊的做用域,稱爲靜態做用域。
  • 簡單的說,Javascript容許使用內部函數---即函數定義和函數表達式位於另外一個函數的函數體內,內部函數能夠訪問它們所在的外部函數中聲明的全部局部變量、參數和聲明的其餘內部函數。當其中一個這樣的內部函數在包含它們的外部函數以外被調用時,就會造成閉包。
    1. 閉包是嵌套的內部函數
    2. 閉包存在於嵌套的內部函數中

閉包的主要做用:

1.能夠讀取函數內部的變量。面試

2.讓這些變量的值始終保持在內存中,變量或參數不會被垃圾回收機制回收GC。bash

產生閉包的條件:

  1. 函數嵌套
  2. 內部函數引用了外部函數的數據(變量/函數)

閉包的優勢:

  1. 變量長期駐紮在內存中
  2. 避免全局變量的污染

閉包的缺點:

  1. 常駐內存會增大內存的使用量
  2. 使用不當會形成內存泄露
  3. 閉包會在父函數外部,改變父函數內部變量的值

閉包使用詳解:

當想要的獲得f1函數的局部變量時,正常狀況下,這是辦不到的,只有經過變通方法才能實現。那就是在函數的內部,再定義一個函數閉包

function f1() {
  var n = 999;
  function f2() {
  console.log(n); // 999
  }
}
複製代碼

上面代碼中,函數f2就在函數f1內部,這時f1內部的全部局部變量,對f2都是可見的。可是反過來就不行,f2內部的局部變量,對f1就是不可見的。這就是 JavaScript 語言特有的」鏈式做用域」結構(chain scope),子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立。既然f2能夠讀取f1的局部變量,那麼只要把f2做爲返回值,咱們不就能夠在f1外部讀取它的內部變量了嗎! 
函數

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}
var result = f1();
result(); // 999
複製代碼

上面代碼中,函數f1的返回值就是函數f2,因爲f2能夠讀取f1的內部變量,因此就能夠在外部得到f1的內部變量了。閉包就是函數f2,即可以讀取其餘函數內部變量的函數。因爲在 JavaScript 語言中,只有函數內部的子函數才能讀取內部變量,所以能夠把閉包簡單理解成「定義在一個函數內部的函數」。在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。 
性能

閉包的最大用處有兩個,一個是能夠讀取函數內部的變量,另外一個就是讓這些變量始終保持在內存中ui

請看下面的例子,閉包使得內部變量記住上一次調用時的運算結果。this

function zxxs(zxx) {
        return function () {
            return zxx++;
        };
    }
 
    var inc = zxxs(5);
    inc() // 5
    inc() // 6
    inc() // 7
複製代碼

上面代碼中,zxx是函數zxxs的內部變量。經過閉包,zxx的狀態被保留了,每一次調用都是在上一次調用的基礎上進行計算。從中能夠看到,閉包inc使得函數zxxs的內部環境,一直存在。因此,閉包能夠看做是函數內部做用域的一個接口。爲何會這樣呢?緣由就在於inc始終在內存中,而inc的存在依賴於zxxs,所以也始終在內存中,不會在調用結束後,被垃圾回收機制回收。
spa

閉包的另外一個用處,是封裝對象的私有屬性和私有方法:.net

function Person (name) {
        var age
 
        function setAge (n) {
            age = n
        }
 
        function getAge () {
            return age
        }
 
        return {
            name: name,
            getAge: getAge,
            setAge: setAge
        }
    }
 
    var p1 = Person('zxx')
    p1.setAge(18)
    p1.getAge() // 18
複製代碼

上面代碼中,函數Person的內部變量age,經過閉包getAgesetAge,變成了返回對象p1的私有變量。
3d

注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,因此內存消耗很大。所以不能濫用閉包,不然會形成網頁的性能問題。

函數執行完後,函數內的局部變量沒有釋放,佔用內存時間會變長,容易形成內存泄漏(內存被佔用,可是沒有用上)

function fn1 () {
    var arr = new Array[1000]
    function fn2 () {
        console.log(arr.length)
    }
    return fn2
}
var f = fn1() // 已經產生閉包
f()
 
f = null // 讓內部函數成爲垃圾對象-->回收閉包
複製代碼

習題1(this指向詳解

var name = 'The Window'
var object = {
    name: 'My Object',
    getNameFunc: function () {
        return function () {
            return this.name
        }
    }
}
alert(object.getNameFunc()())  //The Window
object.getNameFunc() // 執行完變成一個函數,this指向window, 關於this的指向後續會更新新的文章詳解
var name = 'The Window'
var object = {
    name: 'My Object',
    getNameFunc: function () {
        var that = this
        return function () {
            return that.name
        }
    }
}
alert(object.getNameFunc()())  // My Object
複製代碼

習題2

function outerFun(){
 var a=0;
 function innerFun() {
  a++;
  alert(a);
 }
 return innerFun;
}
var obj = outerFun();
obj();  // 結果爲1
obj();  // 結果爲2
var obj2 = outerFun();
obj2();  // 結果爲1
obj2();  // 結果爲2
複製代碼

習題3

function foo (x) {
    var tmp = 3
    function bar (y) {
        console.log(x + y + (++tmp))
    }
    bar(10)
}
foo(2) // 16
foo(2) // 16
foo(2) // 16
foo(2) // 16
 
這裏只是函數調用,不是閉包
========================
 
function foo (x) {
    var tmp = 3
    return function (y) {
        console.log(x + y + (++tmp))
    }
}
var bar = foo(2)
bar(10) // 16
bar(10) // 17 
bar(10) // 18
bar(10) // 19
 
當你return的是內部function時,就是一個閉包。
一個函數訪問了它的外部變量,那麼它就是一個閉包
複製代碼

一道經典的閉包面試題

function fun(n,o){
        console.log(o);
        return {
            fun:function(m){
                return fun(m,n);
            }
        }
    }
    var a = fun(0); // undefined
    a.fun(1); // 0
    a.fun(2); // 0
    a.fun(3); // 0
    var b=fun(0).fun(1).fun(2).fun(3); //undefined,0,1,2
    var c=fun(0).fun(1); //undefined,0
    c.fun(2); // 1
    c.fun(3); // 1
複製代碼

詳解:

轉換爲等價代碼
return返回的對象的fun屬性對應一個新建的函數對象,
這個函數對象將造成一個閉包做用域,
使其可以訪問外層函數的變量n及外層函數fun,
爲了避免將fun函數和fun屬性搞混,咱們將上述代碼修改以下:
function _fun_(n,o){
    console.log(o);
    return {
        fun:function(m){
            return _fun_(m,n);
        }
    }
}
 
var a = fun(0); // undefined 產生了閉包 n = 0
a.fun(1); // 0 產生了閉包,可是因爲沒有綁定變量,閉包立刻就消失了, 始終用的是a裏面的閉包	
a.fun(2); // 0 產生了閉包,可是因爲沒有綁定變量,閉包立刻就消失了, 始終用的是a裏面的閉包 
a.fun(3); // 0 產生了閉包,可是因爲沒有綁定變量,閉包立刻就消失了, 始終用的是a裏面的閉包
 
var b = fun(0).fun(1).fun(2).fun(3); // undefined,0,1,2 會不斷產生新的閉包
等價代碼
var b1 = b.fun(1);
var b2 = b1.fun(2);
var b3 = b2.fun(3);
 
var c = fun(0).fun(1); // undefined,0,
c.fun(2); // 1 當前c的閉包是 1
c.fun(3); // 1 當前c的閉包是 1
雖然c.fun(2)和c.fun(3)都產生了新的閉包,可是因爲沒有賦給新的變量,閉包接着就消失了
複製代碼

                                          

相關文章
相關標籤/搜索