1. 關(guān)于javascript 中的高級定時器的若干問題

        共 4756字,需瀏覽 10分鐘

         ·

        2021-04-24 03:41

        來源 | https://segmentfault.com/a/1190000007908814


        看到評論里有小伙伴建議我試試箭頭函數(shù),真是受寵若驚,本來寫文章也只是想記錄,寫要點給自己日后看的。

        之前看過一篇總結(jié)javascript中this的文章,也同樣提到了箭頭函數(shù)中this的指向問題,所以,我今天想對此做一個完善與總結(jié)。

        一、問題的起源

        論壇上看到這樣一道js編程題:要求用閉包實現(xiàn)每隔5s輸出0-9之間的十個數(shù)字。這里先給出我寫的最終實現(xiàn)方案,如下圖:

        毫無疑問,這里必須要用到定時器setTimeout或者setInterval,但是考慮到setInterval存在的兩個問題:

        • 某些間隔會被跳過

        • 多個定時器的代碼執(zhí)行之間的間隔可能會比預期的小

        所以,用到setInterval的地方一般都是用遞歸調(diào)用setTimeout的方式來替代,但是關(guān)于這兩個定時函數(shù)中的this我之前的理解有些偏差,我知道這里的this指的是全局對象window,因為setTimeout和setInterval都是作為全局函數(shù),也就是window對象的方法存在的。但是這里有兩個this:

        第一個this:setTimeout(this.func, times)

        第二個this: setTimeout(function(){ alert(this)},times);

        那到底哪一個'this'始終指向的是window呢?

        二、執(zhí)行環(huán)境、活動對象、變量對象、作用域鏈、this

        首先澄清一下幾個概念。

        執(zhí)行環(huán)境

        執(zhí)行環(huán)境定義了變量和函數(shù)有權(quán)訪問的其他數(shù)據(jù),決定了它們各自的行為。每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的變量對象,環(huán)境中定義的所有變量和函數(shù)都保存在這個變量對象中。

        全局執(zhí)行環(huán)境是最外圍的一個執(zhí)行環(huán)境。根據(jù)ECMAScript實現(xiàn)所在的宿主環(huán)境不同,表示全局執(zhí)行環(huán)境的對象也不一樣。在web瀏覽器中,全局執(zhí)行環(huán)境被認為是window對象,因為所有的全局變量和函數(shù)都是作為window對象的屬性和方法創(chuàng)建的。某個執(zhí)行環(huán)境中的代碼執(zhí)行完畢后,該環(huán)境就會被銷毀,保存在其中的所有變量和函數(shù)也隨之銷毀(全局執(zhí)行環(huán)境直到應(yīng)用程序退出時才會銷毀)

        每個函數(shù)都有自己的執(zhí)行環(huán)境。當執(zhí)行流進入一個函數(shù)時,該函數(shù)的執(zhí)行環(huán)境就會被推入一個環(huán)境棧中。而在函數(shù)執(zhí)行后,棧將其環(huán)境彈出,把控制權(quán)返回給之前的執(zhí)行環(huán)境。

        作用域鏈

        當代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。
        作用域鏈本質(zhì)上是一個指向變量對象的指針列表,它只引用,但不實際包含變量對象。
        作用域鏈的作用,是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序性。作用域鏈的最前端,始終都是當前執(zhí)行的代碼所在環(huán)境的變量對象。如果這個環(huán)境是函數(shù),則將其活動對象作為變量對象,活動對象在最開始時只包含一個變量,即arguments對象(這個對象在全局環(huán)境中是不存在的)。作用域鏈中的下一個變量對象來自包含(外部)環(huán)境,而再下一個變量對象則來自再下一個包含環(huán)境。這樣一直延續(xù)到全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對象始終是作用域鏈中的最后一個對象。
        標識符解析就是沿著作用域鏈一級一級地搜索標識符的過程。

        this

        this是一個對象,this對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的。
        在全局函數(shù)中,this等于window;而當函數(shù)作為某個對象的方法調(diào)用時,this等于那個對象。
        匿名函數(shù)的執(zhí)行環(huán)境具有全局性,其this通常指向window。這是因為,每個函數(shù)再被調(diào)用時都會自動取得兩個特殊變量:this和arguments內(nèi)部函數(shù)在搜索這兩個變量時,只會搜索到其活動對象為止,因此永遠不可能訪問到外部函數(shù)中的這兩個變量。

        閉包

        閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)
        當某個函數(shù)被調(diào)用時,會創(chuàng)建一個執(zhí)行環(huán)境及相應(yīng)的作用域鏈。然后用arguments和其他的命名參數(shù)的值來初始化函數(shù)的活動對象。
        閉包的主要用途有:模仿塊級作用域和私用變量。

        變量對象

        變量對象中保存了當前執(zhí)行環(huán)境中定義的所有變量和函數(shù)。
        變量對象是和執(zhí)行環(huán)境綁定的,而this是和函數(shù)運行時所在的執(zhí)行環(huán)境綁定的。比如對于一個全局執(zhí)行環(huán)境,其中的'this'指的是該函數(shù)運行時所在的全局執(zhí)行環(huán)境,也就是window;而變量對象隸屬于這個函數(shù)創(chuàng)建的局部執(zhí)行環(huán)境。

        三、 setTimeout和setInterval中的this

        測試一

        我們先來做幾個測試

        • 測試1

        第10行,setTimeout(this.method,500),此時調(diào)用的是構(gòu)造函數(shù)內(nèi)的method方法,也就是說這里的第一個'this'指向的是構(gòu)造函數(shù)生成的對象,即是根據(jù)setTimeout調(diào)用時所在的執(zhí)行環(huán)境確定的。

        盡管調(diào)用的是對象的method方法,但是方法內(nèi)的this(第二個this)等于window。為什么會是這樣呢?在看下面一個測試

        其實,setTimeout 也只是一個函數(shù)而已,函數(shù)必然有可能需要參數(shù),我們把 this.a 當作一個參數(shù)傳給 setTimeout 這個函數(shù),就像它需要一個 fun 參數(shù),在傳入?yún)?shù)的時候,其實做了個這樣的操作 fun = this.a,看到?jīng)]有,這里我們直接把 fun 指向 this.a 的引用;執(zhí)行的時候其實是執(zhí)行了 fun() 所以已經(jīng)和 obj 無關(guān)了,它是被當作普通函數(shù)直接調(diào)用的,因此 this 指向全局對象。

        • 測試2

        第10行,setTimeout(method,500),此時調(diào)用的是全局函數(shù)method。因為,雖然仍在構(gòu)造函數(shù)的局部執(zhí)行環(huán)境內(nèi),但是局部執(zhí)行環(huán)境的變量對象中并沒有method方法,所以,在進行標識符解析時,沿著作用域鏈在全局執(zhí)行環(huán)境中找到了method方法。

        要注意通過第6句this.method=...聲明的這個方法屬于構(gòu)造函數(shù)生成的對象,而不屬于構(gòu)造函數(shù)的變量對象,也就是說,并不存在于作用域鏈中。

        第二個this仍然等于window。

        • 測試3

        第10行,setTimeout(method,500),此時調(diào)用的是構(gòu)造函數(shù)method。
        第二個this仍然等于window。

        • 測試4

        setTimeout第一個參數(shù)是javascript代碼字符串時,第二個this仍然等于window。

        • 測試5

        setTimeout第一個參數(shù)是匿名函數(shù)時,第二個this仍然等于window。

        結(jié)論一

        根據(jù)以上測試,可以得出以下結(jié)論:

        • setTimeout 中的延遲執(zhí)行函數(shù)中的this (也就是第二個this)始終指向window。

        • setTimeout(this.method, minsec)這種形式的this(也就是第一個this),其指向是根據(jù)上下文的執(zhí)行環(huán)境確定的。

        測試二

        該測試的目的是確定setTimeout 中的延遲執(zhí)行函數(shù)中的變量是如何沿著作用域鏈搜索的。

        • 測試6

        測試7

        測試8

        測試6和測試7本質(zhì)上是相同的,因為函數(shù)名只是一個指針,指向函數(shù)對象。
        測試測試6和測試7中,console.log(value)中的value都是構(gòu)造函數(shù)局部執(zhí)行環(huán)境中的value值,而console.log(this.value)中的value都是全局執(zhí)行環(huán)境中的value值。

        測試8中的test指向的是全局執(zhí)行環(huán)境中的test,相應(yīng)的的value都是全局執(zhí)行環(huán)境中的value值。

        延遲函數(shù)中的變量也是根據(jù)其所在的執(zhí)行環(huán)境上下文來確定的,符合作用域鏈的標識符解析過程。

        • 測試9

        兩個value都指向的是全局執(zhí)行環(huán)境中的value值,因為console.log(value)語句所在的局部執(zhí)行環(huán)境上下文并沒有value值。

        結(jié)論二

        setTimeout 中的延遲執(zhí)行函數(shù)中的變量也是根據(jù)其所在的執(zhí)行環(huán)境上下文來確定的,符合作用域鏈的標識符解析過程。

        四、嚴格模式下的this

        除了正常運行模式,ECMAscript 5添加了第二種運行模式:"嚴格模式"(trict mode)。顧名思義,這種模式使得Javascript在更嚴格的條件下運行。

        關(guān)于嚴格模式的介紹,請移步這里Javascript 嚴格模式詳解

        嚴格模式所帶來的語法和行為的改變大致有以下 條:

        1.全局變量顯示聲明

        2.靜態(tài)綁定

        • (1).禁止使用with語句

        • (2).創(chuàng)設(shè)eval作用域

        3.增強的安全措施

        • (1).禁止this關(guān)鍵字指向全局對象

        • (2).禁止在函數(shù)內(nèi)遍歷調(diào)用棧,主要是指caller和arguments這兩個函數(shù)對象屬性。

        4.禁止刪除變量,只有configurable(不懂這個的去看看《javascript高級教程》中關(guān)于數(shù)據(jù)屬性和訪問器屬性的介紹)設(shè)置為true的對象屬性,才能被刪除。

        5.顯示報錯

        6.重名錯誤

        • (1).對象不能有重名屬性

        • (2).函數(shù)不能有重名參數(shù)

        7.禁止八進制表示法

        8.對arguments對象的限制

        • (1).不允許對arguments賦值

        • (2).arguments不再追蹤參數(shù)的變化

        • (3).禁止使用arguments.callee

        9.只允許在全局作用域或函數(shù)作用域的頂層聲明函數(shù)

        10.保留字

        • 在嚴格模式的情況下執(zhí)行純粹的函數(shù)調(diào)用,那么這里的的 this 并不會指向全局,而是undefined.請看如下測試:

        在這個測試例子中,匿名的自執(zhí)行函數(shù)都返回1,目的是避免函數(shù)返回undefined造成誤解,要知道js的函數(shù)在沒有明確指定返回值的情況下默認是返回undefined,用new調(diào)用的構(gòu)造函數(shù)除外。

        • 在嚴格模式下,setTimeout 方法在調(diào)用傳入函數(shù)的時候,如果這個函數(shù)沒有指定了的 this,那么它會做一個隱式的操作—-自動地注入全局上下文,等同于調(diào)用 foo.apply(window) 而非 foo();因此延遲執(zhí)行函數(shù)中的this仍然指向window,而不是undefined.

        • 當然,如果我們在傳入函數(shù)的時候已經(jīng)指定this,那么就不會被注入全局對象,比如:setTimeout(foo.bind(obj), 1);請看如下測試。

        五、箭頭函數(shù)中的this

        在 ES6 的新規(guī)范中,加入了箭頭函數(shù)(想了解更多,請移步這里ECMAScript 6 入門),它和普通函數(shù)最不一樣的一點就是 this 的指向.

        • 箭頭函數(shù)中的 this 只和定義它的時候所在的作用域的 this 有關(guān),而與在哪里以及如何調(diào)用它無關(guān),同時它的 this 指向是不可改變的。請看如下測試。

        在執(zhí)行 setTimeout 時候,我們先是定義了一個匿名的箭頭函數(shù),關(guān)鍵點就在這,箭頭函數(shù)內(nèi)的 this 執(zhí)行定義時所在的對象,就是指向定義這個箭頭函數(shù)時作用域內(nèi)的 this,也就是obj.foo中的this(不要誤解為是 setTimeout中的this啊,只不過是它的實參而已。),即 obj;所以在執(zhí)行箭頭函數(shù)的時候,它的 this -> obj.foo 中的 this -> obj;

        利用閉包這種固化this的特性,可以完美的解決之前必須用閉包才能給延遲執(zhí)行函數(shù)綁定this的問題。

        • 箭頭函數(shù)內(nèi)的this指向不可改變。請看如下測試。

        六、參考

        1.談?wù)剆etTimeout的作用域以及this的指向問題
        2.http://www.jb51.net/article/30858.htm
        3.javascript高級教程
        4.JavaScript 中的 this !


        學習更多技能

        請點擊下方公眾號



        瀏覽 67
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 国产免费无码 | 久久超碰成人 | 秋霞网一区二区 | 午夜成人免费视频 | 一级一级毛片 |