最近我也是經歷過面試別人和去面試的人了,總結幾個常被說起的面試問題,作一下解答和備忘。css
先來看看這個題目:前端
var x = 0;
var foo = {
x:1,
bar:{
x:2,
baz: function () {
console.log(this.x)
}
}
}
var a = foo.bar.baz
foo.bar.baz() // 2
a() //0
複製代碼
上面的例子中,baz被bar調用因此指向的指bar. a 運行時所在的對象是 window,因此指向的是window。git
理解執行環境和上下文github
函數調用都有與之相關的做用域和上下文。從根本上說,做用域是基於函數(function-based)而上下文是基於對象(object-based)。換句話說,做用域是和每次函數調用時變量的訪問有關,而且每次調用都是獨立的。上下文老是關鍵字 this 的值,是調用當前可執行代碼的對象的引用。面試
執行上下文分有global、function、eval,一個函數能夠產生無數個執行上下文,一系列的執行上下文從邏輯上造成了 執行上下文棧,棧底老是全局上下文,棧頂是當前(活動的)執行上下文。bash
執行上下文三屬性:this指針,變量對象(數據做用域),做用域鏈閉包
做用域鏈 即:一變量在本身的做用域中沒有,那麼它會尋找父級的,直到最頂層。過程以下:hexo
上面的文字你們能夠好好琢磨一下,能夠更好的理解函數做用域。
app
咱們先來了解js編譯器在執行代碼的過程:
以執行一段function代碼爲例:
第一步:建立可執行上下文(如下簡稱爲EC),壓入當前的EC棧中。EC中包括瞭如下信息:函數
第二步:收集函數聲明、變量聲明和形參,保存在環境記錄項內。這個收集的過程,就是通常所謂的聲明提高現象的本質。若是發現了重複的標識符,則優先級爲函數聲明 、形參 、變量聲明(優先級低的會被無視)。
第三步:開始執行代碼,環境記錄項內沒有的標識符會根據做用域鏈查找標識符對應的值,環境記錄項亦有可能因賦值語句而被修改。
第四步:函數執行完畢,EC棧被彈出、銷燬。
好了,第二步說的很清楚了 聲明提高(Hoisting)現象就是在收集函數、變量聲明和形參的過程會根據函數聲明、形參、變量聲明的順序優先級來收集。
例子:
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a);
// 輸出1 因爲函數聲明提高,b內的實際是這樣:
// function b() {
// function a() {}; 這裏是函數聲明提高
// a = 10;
// return;
// function a() {}
// }
複製代碼
勘誤:謝謝github上有同窗的指正 關於博客中的一個問題 · Issue #1 · stephenzhao/hexo-theme-damon,上面的正確執行應該爲先進行預編譯,因此先執行function a(){},而後會進行對a的賦值操做。
//正確的順序應該爲:
// function b() {
// function a() {}
// a = 10;
// return;
// }
複製代碼
仍是上面的題目,作個變形。
var x = 0;
var foo = {
x:1,
bar:function () {
console.log(this.x);
var that = this;
return function () {
console.log(this.x)
console.log(that.x)
}
}
}
foo.bar() // 1
foo.bar()() // this: 0, that: 1
複製代碼
上面的例子中ba'r裏面返回了一個匿名函數,這個匿名函數能夠在外部被調用即:foo.bar()() 讀取到了bar的執行上下文的變量對象 that,這個函數就造成了一個閉包。
好了,咱們理解了上面的套路,下面來解釋閉包就好理解了。
閉包就是可以讀取其它函數內部變量的函數
在Javascript語言中,只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成「定義在一個函數內部的函數」
var x = 0;
var bar:function () {
var n = 999;
return function () {
return n;
}
}
var outer = bar();
outer() // 999
複製代碼
用途:
咱們修改一下上面的代碼
var add;
var bar = function () {
var n = 999;
add = function () {
n += 1;
}
return function () {
return n;
}
}
var outer = bar();
outer() // 999
add();
outer(); // 1000
複製代碼
說明,n一直保存在內存當中,而沒有在bar()執行完成以後被銷燬;
緣由:
bar裏面的匿名函數被賦值給了outer,這個致使在outer沒有被銷燬的時候,該匿名函數一直存在內存當中,而匿名函數的存在依賴於bar,因此bar須要使用都在內存當中,因此bar並不會在調用結束後唄垃圾回收機制給收回。
而上面的add接受的也是一個匿名函數,該匿名函數自己也是閉包,因此也能夠在外部操做裏面的變量。
注意點
單例模式的定義是產生一個類的惟一實例
單例模式在js中常常會遇到,好比 var a = {}; 其實就是一個單例子。
可是咱們寫一個更有意義的單例:
var singleton = function( fn ){
var result;
return function(){
return result || ( result = fn .apply( this, arguments ) );
}
}
複製代碼
更簡潔一點的:
var singleton = (function () {
var instance;
return function (object) {
if(!instance){
instance = new object();
}
return instance;
}
})();
複製代碼
又是半夜,這兩天在看里約奧運會的比賽,林丹和李宗偉的那場比賽是今年看過的經次於nba總決賽最後一場的精彩程度。一個偉大的英雄,須要另外一個偉大的對手來成就,感謝林丹,感謝李宗偉世界會記住大家。晚安。