學習JavaScript已經很久了
今天打算回憶一下作用域的知識
作用域這個知識很基礎並且非常重要
下面我就總結一下JavaScript中的作用域以及作用域鏈的相關知識
作用域
作用域是什麼?
作用域是變數能夠引用、函數能夠生效的區域
它限制了你對記憶體空間上值的取得與修改
所有的語言都存在作用域
我們可以理解作用域為js引擎根據名稱尋找變數的一套規則
理解了作用域,我們才能去理解閉包等等一系列問題
[[Scope ]]
大家都知道,函數是特殊的可執行對象
既然是對象,就可以擁有屬性
函數中存在這一個內部屬性[[Scope]](我們不能使用,供js引擎使用)
函數被創建時,這個內部屬性就會包含函數被創建的作用域中對象的集合
這個集合呈鍊式鏈接,被稱為函數的作用域鏈
作用域鏈上的每一個物件稱為可變物件(Variable Obejct),
每一個可變物件都以鍵值對形式存在
舉一個例子,看下面的全域函數
var a = 1;function foo(){ ...}
foo函數在建立時,它的作用域鏈中插入了一個全域物件GO(Global Object),包含全域所有定義的變數
// 伪代码 foo.[[Scope]] = { GO: { this: window , window: ... , document: ... , ...... a: undefined, // 预编译阶段还不知道a值是多少 foo: function(){...}, } }
執行環境
在函數執行時,會建立一個叫做執行環境/執行上下文(execution context)的內部物件
它定義了一個函數執行時的環境
函數每次執行時的執行環境獨一無二
多次呼叫函數就多次創建執行環境
並且函數執行完畢後,執行環境就會被銷毀
執行環境有自己的作用域鏈,用於解析標識符
#看到這裡可能大家有點蒙,我再給大家說明一下我的理解
[[Scope]]和執行期上下文雖然保存的都是作用域鏈,但不是同一個東西
現在先明確一點區別
[[Scope] ]屬性是函數建立時產生的,會一直存在
而執行上下文在函數執行時產生,函數執行結束便會銷毀
實例
我把上面的範例擴充一下再進行詳細說明
var a = 1;function foo(x, y){ var b = 2; function bar(){ var c = 3; } bar(); } foo(100, 200);
下面我就透過這幾行程式碼把作用域鏈以及執行環境詳細說明一下
還是建議各位同學先看看我寫的這個預編譯
首先,在執行流的流動中,全域環境中創建了函數foo()(預編譯階段就創建了),於是foo函數有了屬性[[Scope]]
// 伪代码:foo函数创建产生[[Scope]]对象 foo.[[Scope]] = { GO: { this: window , window: ... , document: ... , a: undefined, //预编译阶段还不知道a的值是多少,执行过程中会修改 foo: function(){...}, ...... } }
foo函數執行前,創建了執行期上下文(我暫且把執行期上下文寫作EC,內部叫什麼名我也不知道),執行期上下文取得foo內部[[Scope]]屬性保存的作用域鏈(複製),然後foo函數執行前預編譯產生了一個活動物件AO(Active Object),這個物件被推入EC作用域鏈的最前端
// 伪代码:foo函数执行前产生执行期上下文EC复制foo中[[Scope]]属性保存的作用域链 foo.EC = { GO: { this: window , window: ... , document: ... , a: 1, foo: function(){...}, ...... } }
// 伪代码:foo函数预编译产生AO活动对象,挂载到foo中EC作用域链的最前端 foo.EC = { AO: { this: window, arguments: [100,200], x: 100, y: 200, b: undefined, bar: function(){...} }, GO: { this: window , window: ... , document: ... , a: 1, foo: function(){...}, ...... } }
foo函數在預編譯階段創建了bar函數,於是bar函數創建了屬性[[Scope]],包含bar被創建的作用域中物件的集合,也就是複製了foo.EC
// 伪代码:bar函数创建产生[[Scope]]对象 bar.[[Scope]] = { AO: { this: window, arguments: [100,200], x: 100, y: 200, b: undefined, bar: function(){...} }, GO: { this: window , window: ... , document: ... , a: 1, foo: function(){...}, ...... } }
bar函數執行,過程同foo函數執行相近,我就直接寫最後結果了
// 伪代码:bar函数执行产生执行上下文 bar.EC = { AO: { this: window, arguments: [], c: undefined, }, AO: { this: window, arguments: [100,200], x: 100, y: 200, b: 2, bar: function(){...} }, GO: { this: window , window: ... , document: ... , a: 1, foo: function(){...}, ...... } }
bar函數執行完畢,執行環境被銷毀,相當於
delete bar.EC
foo函數執行完畢,執行環境被銷毀,相當於
delete foo.EC
#程式結束
不知道我寫這麼多大家理解沒有
js引擎就是透過作用域鏈的規則來進行變數查找(準確的說應該是執行上下文的作用域鏈)
查找過程就拿上面的程式碼來說,比如說我在bar函數內加一行
console.log(a);
那麼bar函數執行時,js引擎想要列印a,所以就去作用域鏈上查找
第一層AO沒有
第二層AO沒有
第三層GO找到了變數a
於是回傳了變數a的值
相信大家在了解了作用域之後,就會理解為什麼全域環境為什麼不能存取局部環境
性能问题
今天写high了,像吃了炫迈一样,那就顺便把性能问题也说清楚了吧
js引擎查找作用域链是为了解析标识符
占用了时间理所当然的产生了性能开销
所以解析标识符有代价,你的变量在执行环境作用域链的位置越深,读写速度就越慢
这很容易理解
在函数中读写局部变量总是最快的,读写全局变量通常最慢
当然了,这些额外的性能开销对于优化js引擎(比如chrome的V8 (⊙▽⊙))来说可能微不足道,甚至可以毫不夸张的说没有性能损失
但是还是要照顾大多浏览器
所以推荐大家养成这样的编码习惯:尽量使用局部变量(缓存)
我举一个小例子
function demo(){ var a = document.getElementById('a'); var b = document.getElementById('b'); var c = document.getElementById('c'); a.onclick = function(){ ... } a.style.left = ...; a.style.top = ...; b.style.backgroundColor = ...; c.className = ...; document.body.appendChild(...); document.body.appendChild(...); document.body.appendChild(...); }
这段代码看起来缓存了,优化了代码
但其实这段代码执行过程中,js引擎解析标识符,要查找6次document
而且document存在于window对象
也就是作用域链的最末尾
所以我们再进行缓存,包括document.body、a.style
再加上单一var原则
重构函数
function demo(){ var doc = document, bd = doc.body, a = doc.getElementById('a'), b = doc.getElementById('b'), styleA = a.style; a.onclick = function(){ ... } styleA.left = ...; styleA.top = ...; styleA.backgroundColor = ...; b.className = ...; bd.appendChild(...); bd.appendChild(...); bd.appendChild(...); }
总结
其实写了这么多,还有一个问题我没写到,就是作用域链在某些特殊情况下是可以动态改变的
比如with()、eval()等等,当然这些都不建议使用,我总结了一篇文章
有兴趣的同学可以看看 ->传送门还是总结一下今天写的作用域链相关知识
作用域是变量能够引用、函数能够生效的区域
函数创建时,产生内部属性[[Scope]]包含函数被创建的作用域中对象的集合(作用域链)
作用域链上每个对象称为可变对象(Variable Obejct),
每一个可变对象都以键值对形式存在(VO要细分的话,全局对象GO和活动对象AO)函数执行时,创建内部对象叫做执行环境/执行上下文(execution context)
它定义了一个函数执行时的环境,函数每次执行时的执行环境独一无二
函数执行结束便会销毁js引擎就通过函数执行上下文的作用域链规则来进行解析标识符(用于读写),从作用域链顶端依次向下查找
尽量缓存局部变量,减少作用域查找性能开销(照顾未优化浏览器)
以上就是JavaScript内部属性[[Scope]]与作用域链及其性能问题的内容,更多相关内容请关注PHP中文网(www.php.cn)!

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。

如何在Quartz中提前發送任務通知在使用Quartz定時器進行任務調度時,任務的執行時間是由cron表達式設定的。現�...


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

禪工作室 13.0.1
強大的PHP整合開發環境

Atom編輯器mac版下載
最受歡迎的的開源編輯器

Dreamweaver CS6
視覺化網頁開發工具

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能