JavaScript中沒有塊級作用域(es6以前),JavaScript中作用域分為函數作用域和全局作用域。並且,大家可以認為全局作用域其實就是Window函數的函數作用域,我們編寫的js代碼,都存放在Window函數內(這是個假設),也就是說JavaScript中只有函數作用域(前面假設做前提下)。
作用域是一個盒子,盒子內部的變量只能在當前盒子中使用,作用域盒子是可以嵌套的,內部盒子的變量對父級盒子是不可見的,因為盒子封閉了他們並且盒子不透明,但是盒子可以看到父級盒子內部定義的變量,因為內部這個盒子與父級的變量同處一個空間,他們是互相看得到的。就像css中的盒模型一樣。
以上這個圖分為3層作用域,全局作用域、foo函數作用域、bar函數作用域,我們可以清晰的看到三層作用域各自的范圍。
我們經常用到this,this是代表著什麼?this是代表著當前方法執行的環境上下文,那麼何為環境上下文,通俗的說,誰調用了函數,誰就是這個函數的環境上下文。例如:
function run () { console.log(this) } var o = { run: run } run() // window對象 o.run // o對象
在上述代碼中,run是一個函數,首先執行run()返回了window對象,為什麼呢?我們已經講過,var定義的變量會默認掛在到window對象中去,那麼function定義的函數呢,也會默認掛在到window對象中去,其實我們在執行一個函數如:run() 跟window.run()是一樣的,所以,window調用了run,因此函數的this是window。
注意:this的綁定是產生在函數調用時,並非在函數定義時
var o = { color: 'red', getColor: function () { console.log(this.color) } } var color = 'blue' var getColor = o.getColor o.getColor // red getColor // blue
上述代碼,我們將o.getColor方法賦值給了一個變量getColor,然後我們分別執行了兩個方法,缺得到了不同的結果,對於o.getColor並沒有什麼疑問,但是初學者可能認為getColor返回的也應該是red,但事實是blue,這就說明了,this是產生在函數調用時,因為在o.getColor賦值語句,就相當於一下語句
var getColor = function () { console.log(this.color) }
o.getColor返回的是這個函數,並沒有綁定this,也就是說getColor其實是被賦值了一個函數,僅此而已,this是在調用時綁定的,所以得到了那樣的結果。
看下面代碼:
var o = { run : function () { setTimeout(function () { console.log(this) }, 1000) } } o.run() // window對象
以上運行了o.run方法,隔一秒輸出this,輸出了window對象,初學者可能會疑問,o.run調用時明明是在o對象下啊,this應該是o對象啊!不錯,有這個疑問說明對this有一定了解了,但是上面案例是在setTimeout方法中的回調函數中輸出的this,此回調函數執行時,是以fn()方式執行的(涉及到異步回調),前面說過直接執行函數相當於window.fn,因此輸出了window對象。
拓展回調:
回調是進行異步操作常用的功能,上面示例中,我們可以假設為setTimeout是這樣定義的
function setTimeout (fun) { fun() } setTimeout (function () { console.log(this) })
可以看出,fun是直接調用的,輸出的this必定是window對象。
有時候,我們需要強制某個函數中的this為某一對象,我們這時候需要暴力的綁定this,這裡,暴力的方法有3個: call、apply、bind。下面通過一個簡單的demo來看一下他們的用法。
var color = 'blue' var o = { color: 'red', say: function (animal, beautiful) { console.log(this.color + '顏色的' + animal + (beautiful ? '好看': '不好看')) } } var say= o.say say('貓', false) // blue顏色的貓不好看 say.call(o, '貓', true) // red顏色的貓好看 say.apply(o, ['貓', true]) // red顏色的貓好看 say.bind(o)('貓', true) // red顏色的貓好看 o.say.call(null, '貓', false) // blue顏色的貓不好看
我們這次將o函數加一個say方法,say方法接收兩個參數,第一個是動物,第二個是布爾值代表好不好看,我們還是將o.say賦值給變量say。
say('貓', false) 如期返回了blue顏色
say.call(o, '貓', true),say.apply(o, ['貓', true]),say.bind(o)('貓', true) 這三個都返回了red,可見我們都暴力的綁定了this為o對象。
call和apply的第一個參數都是接收要綁定的對象,也就是告訴引擎:'我要讓say方法在o對象的環境下運行,你給我幫頂一下',引擎回答:‘包在我身上’,然後我們就綁定成功了,接下來我們需要傳遞參數到方法中去,call方法是在第二個參數之後,按順序的傳遞參數 到方法中去,apply方法不同,apply方法是在第二個參數以數組的方式將參數傳遞到方法中去。而bind方法是es5中的方法,用途就在於綁定this指向然後返回已綁定好了的函數,因此,以上三個都輸出了red
同樣o.say綁定到null或者undefined,引擎會自動綁定到全局對象window下面。
本著菜鳥的思路,努力講述一下簡單的函數執行背後的歷程,有錯誤請指正。
function fn (a, b) { var c = 2; function f () {...} } var o = {} fn.call(o,1,'ok')
函數fn執行,會首先產生一個函數活動對象,這個活動對象對外是不可見的,該示例中,call方法先綁定了此次調用的this指向o,fn函數活動對象通過參數之間的賦值形成arguments對象,然後進行了函數內部的初始化變量,最後執行函數內部的賦值過程。過程如下:
function fn (a = 1, b = 'ok') { var c var f //活動對象初始化完畢,進行函數體內的其他操作,這根變量提升和函數聲明提升有關 c = 2 f = function () {...} }
總而言之,函數是javascript中最主要的結構,this是javascript甚至每一門高級語言中都需要的動態綁定的指針。能力一般,水平有限,如有錯誤,輕噴輕罵。