【前端面試】作用域和閉包

1. 題目

說一下對變量提升的理解

說明this的幾種不同使用場景

創建10個a標籤,點擊的時候彈出來相應的序號

如何理解作用域

實際開發中閉包的應用

2. 知識點

2.1 執行上下文

範圍:一段script或者一個函數

全局:變量定義、函數聲明 script

函數:變量定義、函數聲明、this、arguments (執行之前)

函數聲明和函數表達式的區別:

a(); //報錯  函數表達式 變量聲明 會提前。
var a = function(){}

b(); // 不報錯  函數聲明
function b(){}

變量定義時會默認把他的變量聲明提升:(僅限於他的執行上下文,比如一段script和一個函數中)

console.log(a);
var a = 0;

實際上是

var a;
console.log(a);
a = 0;

2.2 this

this要在執行時才能確認,定義時無法確認。

        var a = {
            name:'a',
            fn:function(){
                console.log(this.name);
            }
        }

        a.fn();  // a
        a.fn.apply({name:'b'});  // b  a.fn.call({name:'b'});
        var fn1 = a.fn();
        fn1();  // undefined

this的使用場景

構造函數中(指向構造的對象)

    function Fun(name){
        this.name = name;
    }
    var f = new Fun('a');
    console.log(f.name);

對象屬性中(指向該對象)

普通函數中(指向window)

call apply bind

        var fun = function (name){
            console.log(this);
            console.log(name);
        }.bind({a:1});
        fun("name");

arguments中的this:

var length = 10;
function fn(){
    alert(this.length)
}
var obj = {
    length: 5,
    method: function(fn) {
        arguments[0]()
    }
}

obj.method(fn)//輸出1
這裏沒有輸出5,也沒有輸出10,反而輸出了1,有趣。這裏arguments是javascript的一個內置對象(可以參見mdn:arguments - JavaScript),是一個類數組(就是長的比較像數組,但是欠缺一些數組的方法,可以用slice.call轉換,具體參見上面的鏈接),其存儲的是函數的參數。也就是說,這裏arguments[0]指代的就是你method函數的第一個參數:fn,所以arguments[0]()的意思就是:fn()。

不過這裏有個疑問,爲何這裏沒有輸出5呢?我method裏面用this,不應該指向obj麼,至少也會輸出10呀,這個1是鬧哪樣?

實際上,這個1就是arguments.length,也就是本函數參數的個數。爲啥這裏的this指向了arguments呢?因爲在Javascript裏,數組只不過使用數字做屬性名的方法,也就是說:arguments[0]()的意思,和arguments.0()的意思差不多(當然這麼寫是不允許的),你更可以這麼理解:

arguments = {
    0: fn, //也就是 functon() {alert(this.length)} 
    1: 第二個參數, //沒有 
    2: 第三個參數, //沒有
    ..., 
    length: 1 //只有一個參數
}

所以這裏alert出來的結果是1。

如果要輸出5應該咋寫呢?直接 method: fn 就行了。

2.3 作用域

沒有塊級作用域

        if(true){
            var name = "test"
        }
        console.log(name);

儘量不要在塊中聲明變量。

只有函數級作用域

2.4 作用域鏈

自由變量 當前作用域沒有定義的變量 即爲自由變量。

自由變量會去其父級作用域找。是定義時的父級作用域,而不是執行。

        var a = 100;
        function f1(){
            var b = 200;
            function f2(){
                var c = 300;
                console.log(a); //自由變量
                console.log(b); //自由變量
                console.log(c);
            }
            f2();
        };
        f1();

2.5 閉包

 一個函數中嵌套另外一個函數,並且將這個函數return出去,然後將這個return出來的函數保存到了一個變量中,那麼就創建了一個閉包。

閉包的兩個使用場景

1.函數作爲返回值

        function fun(){
            var a = 0;
            return function(){
                console.log(a); //自由變量,去定義時的父級作用域找
            }
        }

        var f1 = fun();
        a = 1000;
        f1();

2.函數作爲參數

        function fun(){
            var a = 0;
            return function(){
                console.log(a); //自由變量,去定義時的父級作用域找
            }
        }

        function fun2(f2){
            a = 10000
            f2();
        }

        var f1 = fun();

        fun2(f1);

具體解釋看 高級-閉包中的說明

閉包的兩個作用:

能夠讀取其他函數內部變量的函數

可以讓函數內部的變量一直保存在內存中

實際應用場景1:

閉包可以將一些不希望暴露在全局的變量封裝成“私有變量”。

假如有一個計算乘積的函數,mult函數接收一些number類型的參數,並返回乘積結果。爲了提高函數性能,我們增加緩存機制,將之前計算過的結果緩存起來,下次遇到同樣的參數,就可以直接返回結果,而不需要參與運算。這裏,存放緩存結果的變量不需要暴露給外界,並且需要在函數運行結束後,仍然保存,所以可以採用閉包。

上代碼:

function calculate(param){
    var cache = {};
    return function(){
        if(!cache.parame){
            return cache.param;
        }else{
            //緩存計算....
            //cache.param = result
            //下次訪問直接取
        }
    }
}

實際應用場景2

延續局部變量的壽命

img 對象經常用於進行數據上報,如下所示:

var report = function( src ){
    var img = new Image();
    img.src = src;
};
report( 'http://xxx.com/getUserInfo' );

但是通過查詢後臺的記錄我們得知,因爲一些低版本瀏覽器的實現存在 bug,在這些瀏覽器
下使用 report 函數進行數據上報會丟失 30%左右的數據,也就是說, report 函數並不是每一次
都成功發起了 HTTP 請求。

丟失數據的原因是 img 是 report 函數中的局部變量,當 report 函數的
調用結束後, img 局部變量隨即被銷燬,而此時或許還沒來得及發出 HTTP 請求,所以此次請求
就會丟失掉。

現在我們把 img 變量用閉包封閉起來,便能解決請求丟失的問題:

var report = (function(){
    var imgs = [];
    return function( src ){
        var img = new Image();
        imgs.push( img );
        img.src = src;
    }
})();

閉包缺點:浪費資源!

3. 題目解答

3.1 說一下對變量提升的理解

變量定義和函數聲明

注意函數聲明和函數表達式的區別

變量定義時會默認把他的變量聲明提升:(僅限於他的執行上下文,比如一段script和一個函數中)

console.log(a);
var a = 0;

實際上是

var a;
console.log(a);
a = 0;

3.2 說明this的幾種不同使用場景

  • 構造函數中(指向構造的對象)
  • 對象屬性中(指向該對象)
  • 普通函數中(指向window)
  • call apply bind

3.3 創建10個a標籤,點擊的時候彈出來相應的序號

實現方法1:用let聲明i

        var body = document.body;
        console.log(body);
        for (let i = 0; i < 10; i++) {
            let obj = document.createElement('i');
            obj.innerHTML = i + '<br>';
            body.appendChild(obj);
            obj.addEventListener('click',function(){
                alert(i);
            })
        }

實現方法2 包裝作用域

    var body = document.body;
    console.log(body);
    for (var i = 0; i < 10; i++) {
        (function (i) {
            var obj = document.createElement('i');
            obj.innerHTML = i + '<br>';
            body.appendChild(obj);
            obj.addEventListener('click', function () {
                alert(i);
            })
        })(i)
    }

3.4 實際開發中閉包的應用

能夠讀取其他函數內部變量的函數

可以讓函數內部的變量一直保存在內存中

封裝變量,權限收斂

應用1

var report = (function(){
    var imgs = [];
    return function( src ){
        var img = new Image();
        imgs.push( img );
        img.src = src;
    }
})();

用於防止變量銷燬。

應用2

    function isFirstLoad() {
        var arr = [];
        return function (str) {
            if (arr.indexOf(str) >= 0) {
                console.log(false);
            } else {
                arr.push(str);
                console.log(true);
            }
        }
    }

    var fun = isFirstLoad();
    fun(10);
    fun(10);

將arr封裝在函數內部,禁止隨意修改,防止變量銷燬。

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