看到評論裏有仁兄建議我試試箭頭函數,真是受寵若驚,原本寫這篇文章也只是想記錄寫要點給本身往後看的。今天早上看到一篇總結javascript
中this
的文章JavaScript 中的 this !,也一樣提到了箭頭函數中this
的指向問題,因此,又對這篇文章進行了完善。javascript
論壇上看到這樣一道js編程題:要求用閉包實現每隔5s輸出0-9之間的十個數字
。這裏先給出我寫的最終實現方案,以下圖:
毫無疑問,這裏必需要用到定時器setTimeout
或者setInterval
,可是考慮到setInterval
存在的兩個問題:html
某些間隔會被跳過前端
多個定時器的代碼執行之間的間隔可能會比預期的小java
因此,用到setInterval
的地方通常都是用遞歸調用setTimeout
的方式來替代,可是關於這兩個定時函數中的this
我以前的理解有些誤差,我知道這裏的this
指的是全局對象window
,由於setTimeout
和setInterval
都是做爲全局函數,也就是window
對象的方法存在的。可是這裏有兩個this
:es6
第一個
this
:setTimeout(this
.func, times)web第二個
this
: setTimeout(function(){ alert(this
)},times);編程
那到底哪個'this'始終指向的是window
呢?瀏覽器
首先澄清一下幾個概念。安全
執行環境定義了變量和函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有一個與之關聯的
變量對象
,環境中定義的全部變量和函數都保存在這個變量對象
中。閉包全局執行環境是最外圍的一個執行環境。根據ECMAScript實現所在的宿主環境不一樣,表示全局執行環境的對象也不同。在web瀏覽器中,全局執行環境被認爲是
window
對象,由於全部的全局變量和函數都是做爲window
對象的屬性和方法建立的。某個執行環境中的代碼執行完畢後,該環境就會被銷燬,保存在其中的全部變量和函數也隨之銷燬(全局執行環境直到應用程序退出時纔會銷燬)每一個函數都有本身的執行環境。當執行流進入一個函數時,該函數的執行環境就會被推入一個環境棧中。而在函數執行後,棧將其環境彈出,把控制權返回給以前的執行環境。
當代碼在一個環境中執行時,會建立
變量對象
的一個做用域鏈
。
做用域鏈本質上是一個指向變量對象
的指針列表,它只引用,但不實際包含變量對象
。
做用域鏈的做用,是保證對執行環境有權訪問的全部變量和函數的有序性。做用域鏈的最前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象
做爲變量對象
,活動對象在最開始時只包含一個變量,即arguments對象(這個對象在全局環境中是不存在的)。做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自再下一個包含環境。這樣一直延續到全局執行環境;全局執行環境的變量對象始終是做用域鏈中的最後一個對象。
標識符解析就是沿着做用域鏈一級一級地搜索標識符的過程。
this是一個對象,this對象是在運行時基於函數的執行環境綁定的。
在全局函數中,this
等於window
;而當函數做爲某個對象的方法調用時,this
等於那個對象。
匿名函數的執行環境具備全局性,其this
一般指向window
。這是由於,每一個函數再被調用時都會自動取得兩個特殊變量:this
和arguments
內部函數在搜索這兩個變量時,只會搜索到其活動對象爲止,所以永遠不可能訪問到外部函數中的這兩個變量。
閉包是指有權訪問另外一個函數做用域中的變量的函數
當某個函數被調用時,會建立一個執行環境及相應的做用域鏈。而後用arguments和其餘的命名參數的值來初始化函數的活動對象。
閉包的主要用途有:模仿塊級做用域和私用變量。
變量對象中保存了當前執行環境中定義的全部變量和函數。
變量對象是和執行環境綁定的,而this
是和函數運行時所在的執行環境綁定的。好比對於一個全局執行環境,其中的'this'指的是該函數運行時所在的全局執行環境,也就是window
;而變量對象隸屬於這個函數建立的局部執行環境。
setTimeout
和setInterval
中的this
咱們先來作幾個測試
測試1
第
10
行,setTimeout(this.method,500)
,此時調用的是構造函數內的method
方法,也就是說這裏的第一個'this'指向的是構造函數生成的對象,便是根據setTimeout
調用時所在的執行環境肯定的。儘管調用的是對象的
method
方法,可是方法內的this
(第二個this
)等於window
。爲何會是這樣呢?在看下面一個測試
其實,
setTimeout
也只是一個函數而已,函數必然有可能須要參數,咱們把this.a
看成一個參數傳給setTimeout
這個函數,就像它須要一個fun
參數,在傳入參數的時候,其實作了個這樣的操做fun = this.a
,看到沒有,這裏咱們直接把fun
指向this.a
的引用;執行的時候實際上是執行了fun()
因此已經和obj
無關了,它是被看成普通函數直接調用的,所以this
指向全局對象。
測試2
第
10
行,setTimeout(method,500)
,此時調用的是全局函數method
。由於,雖然仍在構造函數的局部執行環境內,可是局部執行環境的變量對象
中並無method
方法,因此,在進行標識符解析時,沿着做用域鏈在全局執行環境中找到了method
方法。要注意經過第
6
句this.method=...
聲明的這個方法屬於構造函數生成的對象,而不屬於構造函數的變量對象
,也就是說,並不存在於做用域鏈中。第二個
this
仍然等於window
。
測試3
第
10
行,setTimeout(method,500)
,此時調用的是構造函數method
。
第二個this
仍然等於window
。
測試4
setTimeout
第一個參數是javascript
代碼字符串時,第二個this
仍然等於window
。
測試5
setTimeout
第一個參數是匿名函數時,第二個this
仍然等於window
。
根據以上測試,能夠得出如下結論:
setTimeout
中的延遲執行函數中的this
(也就是第二個this
)始終指向window
。
setTimeout(this.method, minsec)
這種形式的this
(也就是第一個this
),其指向是根據上下文的執行環境肯定的。
該測試的目的是肯定setTimeout
中的延遲執行函數中的變量是如何沿着做用域鏈搜索的。
測試6
測試7
測試8
測試
6
和測試7
本質上是相同的,由於函數名只是一個指針,指向函數對象。
測試測試6
和測試7
中,console.log(value)
中的value
都是構造函數局部執行環境中的value
值,而console.log(this.value)
中的value
都是全局執行環境中的value
值。測試
8
中的test
指向的是全局執行環境中的test
,相應的的value
都是全局執行環境中的value
值。延遲函數中的變量也是根據其所在的執行環境上下文來肯定的,符合做用域鏈的標識符解析過程。
測試9
兩個
value
都指向的是全局執行環境中的value
值,由於console.log(value)
語句所在的局部執行環境上下文並無value
值。
setTimeout
中的延遲執行函數中的變量也是根據其所在的執行環境上下文來肯定的,符合做用域鏈的標識符解析過程。
this
除了正常運行模式,ECMAscript 5
添加了第二種運行模式:"嚴格模式"(trict mode
)。顧名思義,這種模式使得Javascript
在更嚴格的條件下運行。
關於嚴格模式的介紹,請移步這裏Javascript 嚴格模式詳解
嚴格模式所帶來的語法和行爲的改變大體有如下 條:
1.全局變量顯示聲明
2.靜態綁定
(1).禁止使用
with
語句(2).創設
eval
做用域3.加強的安全措施
(1).禁止
this
關鍵字指向全局對象(2).禁止在函數內遍歷調用棧,主要是指
caller
和arguments
這兩個函數對象屬性。4.禁止刪除變量,只有configurable(不懂這個的去看看《javascript高級教程》中關於
數據屬性
和訪問器屬性
的介紹)設置爲true的對象屬性,才能被刪除。5.顯示報錯
6.重名錯誤
(1).對象不能有重名屬性
(2).函數不能有重名參數
7.禁止八進制表示法
8.對
arguments
對象的限制(1).不容許對
arguments
賦值(2).
arguments
再也不追蹤參數的變化(3).禁止使用
arguments.callee
9.只容許在全局做用域或函數做用域的頂層聲明函數
10.保留字
在嚴格模式的狀況下執行純粹的函數調用,那麼這裏的的 this
並不會指向全局,而是undefined
.請看以下測試:
在這個測試例子中,匿名的自執行函數都返回
1
,目的是避免函數返回undefined
形成誤解,要知道js
的函數在沒有明確指定返回值的狀況下默認是返回undefined
,用new
調用的構造函數除外。
在嚴格模式下,setTimeout
方法在調用傳入函數的時候,若是這個函數沒有指定了的 this
,那麼它會作一個隱式的操做—-自動地注入全局上下文,等同於調用 foo.apply(window)
而非 foo()
;所以延遲執行函數中的this
仍然指向window
,而不是undefined
.
固然,若是咱們在傳入函數的時候已經指定this
,那麼就不會被注入全局對象,好比: setTimeout(foo.bind(obj), 1)
;請看以下測試。
this
在 ES6 的新規範中,加入了箭頭函數(想了解更多,請移步這裏ECMAScript 6 入門),它和普通函數最不同的一點就是 this
的指向.
箭頭函數中的 this
只和定義它的時候所在的做用域的 this
有關,而與在哪裏以及如何調用它無關,同時它的 this 指向是不可改變的。請看以下測試。
在執行
setTimeout
時候,咱們先是定義了一個匿名的箭頭函數,關鍵點就在這,箭頭函數內的this
執行定義時所在的對象,就是指向定義這個箭頭函數時做用域內的this
,也就是obj.foo
中的this
(不要誤解爲是setTimeout
中的this
啊,只不過是它的實參而已。),即obj
;因此在執行箭頭函數的時候,它的this -> obj.foo
中的this -> obj
;利用閉包這種固化
this
的特性,能夠完美的解決以前必須用閉包才能給延遲執行函數綁定this
的問題。
箭頭函數內的this
指向不可改變。請看以下測試。
1.談談setTimeout的做用域以及this的指向問題
2.http://www.jb51.net/article/30858.htm
3.javascript高級教程
4.JavaScript 中的 this !