摘要:中作用域的問題可以說是老生常談,個人認為的作用域中存在著兩種作用域,一種是詞法作用域,一種是動態作用域。但是自動有了箭頭函數后,箭頭函數中的并不是動態作用域,而是屬于詞法作用域,再其定義時就已經確定好了,相當于。
js中作用域的問題可以說是老生常談,個人認為js的作用域中存在著兩種作用域,一種是詞法作用域,一種是動態作用域。
詞法作用域詞法作用域就是定義在詞法階段的作用域,也就是說由我們寫代碼時將變量寫在哪里所決定的,當然在js中大部分是這種情況。
var a = 20; function foo () { console.log(a); } foo(); // 20 function bar () { var a = 30; foo(); // 20 } bar();
這個例子就是一個很好的印證,可以發現的是,無論foo在哪里調用,其a的值永遠是全局作用域中的a的值,這就是詞法作用域,定義函數時,作用域是在全局,那么foo上層作用域就是全局,改變其調用位置是不能改變其作用域鏈的,其作用域鏈是在定義時就決定好的。
利用詞法作用域,我們可以引申出閉包的概念,將我們上面的代碼改寫:
var a = 20; function bar () { var a = 30; return function foo () { console.log(a); } } bar()(); // 30
foo函數在bar函數內定義,所以foo函數的作用域鏈上層對應的是bar的作用域,利用閉包將foo暴露出去,由于foo函數仍然保持著對bar作用域的引用,所以bar內部的作用域依然存在,沒有被回收,這也是閉包能夠產生私有變量等效果的原因。
改變作用域對沒錯,詞法作用域可以被改變,通過eval或者with就可以將作用域改變,但是這并不被提倡。
eval改變作用域:
var a = 20; function bar () { eval("var a = 30;"); console.log(a); } bar(); // 30
eval內的代碼可以看做,本身就寫在了那一行,但是在嚴格模式下,eval有著自己的作用域,所以嚴格模式下上面的代碼會報出一個ReferenceError。
with改變作用域:
function foo (obj) { with (obj) { b = 2; a = 2; } } var obj = {a: 1}; foo(obj); console.log(obj, b); // { a: 2 } 2
with可以形成一個新的作用域,其詞法標識符就是這個對象的屬性,所以可以看到的是a = 2,本質上改變的是obj的屬性,而b = 2由于這個作用域下沒有找到該變量,所以會沿著作用域鏈向上查找,由于在foo的作用域內也沒有找到,一直到全局作用域都沒有找到,所以會給全局添加一個屬性,這就將b變為了全局變量,所以我們在全局作用域中可以訪問到b。但是我們在with中用var定義變量時,會將該變量定義到其外層作用域中。
var obj = {a: 1}; with (obj) { var b = 2; var a = 2; } console.log(obj, b, a); // { a: 2 } 2 undefined
所以我們可以看到的with塊內的變量也有著提前聲明,但是其提前聲明的位置是其上層作用域中。這種行為實在是十分讓人理解,將對象放入一個新的作用域,但是同時可以給其上層作用域定義變量。當然在嚴格模式中,完全不用擔心,因為with在嚴格模式中被禁用。
動態作用域動態作用域取決于其調用方式以及在哪里調用,這個聽著有點像this啊,沒錯,在js中唯一的動態作用域就是this,當然也可以叫其延遲綁定。在談起this之前,首先要知道的是,執行上下文是什么?
每一種代碼的執行都依賴于自身的上下文,函數的每一次調用都會進入函數執行中的一個上下文,并且在函數每次調用時都會產生一個變量對象(虛擬出來的,真實代碼中是訪問不出來的,會被視作undefined),函數中的每一個變量都可以視為這個變量對象的一個屬性,在進入上下文時,首先會對函數的形參進行操作,將形參添加到變量對象中,然后會對函數聲明進行操作,假若變量對象中存在同名屬性時,同名屬性將被覆蓋,最后對變量聲明進行操作,假若變量對象中存在同名屬性時,該變量則會被忽略聲明,下面的代碼就可以驗證我們的這個過程:
function foo (fn) { function fn () {} var fn; console.log(fn); // [Function: fn] } foo();
我們的this和執行上下文沒有關系,但是和我們的變量對象有著很大的關系。
在非嚴格模式下,this指向null或者undefined時會指向全局對象。
以這個準則來看我們《JavaScript語言精粹》中提到的函數的四種調用方式與this取值的關系:
1.方法調用模式
var obj = { a: 2, foo: function () { console.log(this.a); } }; obj.foo();
這種情況下,this指向的就是該對象。
2.函數調用模式
var a = 2; function foo () { console.log(this.a); } foo(); // 瀏覽器環境中:2 // 嚴格模式下 var a = 2; function foo () { "use strict"; console.log(this.a); // TypeError } foo();
這種情況下this應該指向的那個我們上文所提到的那個虛擬出來的變量對象,由于其本來并不存在,所以this指向的undefined,在非嚴格模式下指向的是全局window,但是在嚴格模式下,禁止了這種隱式的轉換。函數調用模式下,this其實可以理解為指向我們虛擬出來的那個變量對象。
3.構造器調用模式
也就是該函數被當做構造函數來調用,這是首先會在函數內部創建一個空對象,然后在將this指向這個空對象,最后將這個對象返回出去。
4.call與apply調用
這時this指向的是其第一個參數。
但是自動有了箭頭函數后,箭頭函數中的this并不是動態作用域,而是屬于詞法作用域,再其定義時就已經確定好了,相當于function () {}.bind(this)。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79871.html
摘要:也毫不例外,但在中作用域的特性與其他高級語言稍有不同,這是很多學習者久久難以理清的一個核心知識點。主要使用的是函數作用域。 關于作用域:About Scope 作用域是程序設計里的基礎特性,是作用域使得程序運行時可以使用變量存儲值、記錄和改變程序的狀態。JavaScript 也毫不例外,但在 JavaScript 中作用域的特性與其他高級語言稍有不同,這是很多學習者久久難以理清的一個核...
摘要:作用域分類作用域共有兩種主要的工作模型。換句話說,作用域鏈是基于調用棧的,而不是代碼中的作用域嵌套。詞法作用域詞法作用域中,又可分為全局作用域,函數作用域和塊級作用域。 一篇鞏固基礎的文章,也可能是一系列的文章,梳理知識的遺漏點,同時也探究很多理所當然的事情背后的原理。 為什么探究基礎?因為你不去面試你就不知道基礎有多重要,或者是說當你的工作經歷沒有亮點的時候,基礎就是檢驗你好壞的一項...
摘要:圖片中的作用域鏈,是全局執行環境中的作用域鏈。然后此活動對象被推入作用域鏈的最前端。在最后調用的時候,創建先構建作用域鏈,再創建執行環境,再創建執行環境的時候發現了一個變量標識符。 從圖書館翻過各種JS的書之后,對作用域/執行環境/閉包這些概念有了一個比較清晰的認識。 栗子說明一切 第一個栗子 來看一個來自ECMA-262的栗子: var x = 10; (function foo(...
摘要:在中的應用采用詞法作用域,也就是靜態作用域。那什么又是詞法作用域或者靜態作用域呢請繼續往下看靜態作用域與動態作用域因為采用的是詞法作用域函數的作用域在函數定義的時候就決定了。 開篇 當我們在開始學習任何一門語言的時候,都會接觸到變量的概念,變量的出現其實是為了解決一個問題,為的是存儲某些值,進而,存儲某些值的目的是為了在之后對這個值進行訪問或者修改,正是這種存儲和訪問變量的能力將狀態給...
摘要:作用域作用域是指程序源代碼中定義變量的區域。采用詞法作用域,也就是靜態作用域。而與詞法作用域相對的是動態作用域,函數的作用域是在函數調用的時候才決定的。前面我們已經說了,采用的是靜態作用域,所以這個例子的結果是。 JavaScript深入系列的第二篇,JavaScript采用詞法作用域,什么語言采用了動態作用域?兩者的區別又是什么?還有一個略難的思考題,快來看看吧。 作用域 作用域是指...
閱讀 3917·2021-09-09 09:33
閱讀 1773·2021-09-06 15:14
閱讀 1919·2019-08-30 15:44
閱讀 3075·2019-08-29 18:36
閱讀 3765·2019-08-29 16:22
閱讀 2095·2019-08-29 16:21
閱讀 2530·2019-08-29 15:42
閱讀 1648·2019-08-29 11:00