【前端面試】作用域和閉包
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'});// ba.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封裝在函式內部,禁止隨意修改,防止變數銷燬。