做用域是理解JS的關鍵所在,一樣做用域關係到性能。其實主要仍是標識符的解析會影響到性能。而咱們主要是從特別細微的地方去分析做用域的性能問題。 可能有一些地方的優化在引擎不斷優化的狀況下已經成效漸微,可是我以爲仍是有必要去從根源瞭解爲何咱們要這麼作,javascript
追根溯源前端
咱們如下面這個函數爲例java
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
var total = add(5, 10);
複製代碼
咱們先來看一下在執行add函數的時候的做用域鏈 閉包
標識符解析: 當函數在執行的時候,每遇到一個變量都會去搜索執行環境的做用域鏈,查找同名的標識符,且搜索過程是從做用域鏈的頭部開始。搜索的時候先以當前運行函數的活動對象開始一直到做用域鏈的最後,若是搜索到則使用這個標識符對應的變量,沒有則爲視爲標識符未定義。 因此一個標識符所在的位置越深,它的讀寫速度越慢,因爲全局變量老是存在於執行環境做用域鏈的最末端。因此函數中局部變量 > 全局變量。函數
other: 當名字相同的兩個變量在做用域鏈的不一樣部分的時候,那麼標識符則爲最早找到的那個性能
var a = 1;
function test(){
var a = 2;
return a;
}
test(); // a = 2
複製代碼
最佳實踐優化
funtion init(){
var doc = document,
bd = doc.body;
doc.getElementById('test').onclick = function(){}
bd.className = '';
}
複製代碼
問:既然做用域對性能有影響,那咱們有什麼辦法去臨時改變做用域鏈麼? 答案是有的,JS中with和try-catch是能夠臨時改變做用域鏈的。ui
疑惑spa
追根溯源3d
咱們仍是要找個例子,好比:
funtion init(){
with(document){
var bd = body,
links = getElementsByTagName('a');
getElementById('test').onclick = function(){}
bd.className = '';
}
}
複製代碼
咱們把前面的init函數改造了一下,當執行到with語句的時候,執行環境的做用域鏈被臨時改變了,一個新的活動對象被建立,它包含了參數指定的對象的全部屬性,並將新的活動對象推入做用域鏈的首位(這就解決了咱們的第一點疑惑,因此定義with語句的目的主要是簡化屢次寫同一個對象的工做)。如圖:
try-catch一樣在try代碼塊中發生錯誤的時候,會自動跳轉到catch,其將異常對象推入做用域鏈,並將其置於做用域鏈的首位。同with。
最佳實踐
try{
init()
} catch(error) {
handleError(error)
}
複製代碼
因爲只有一條語句,且沒有對局部變量的訪問,因此做用域鏈的臨時改動並不會影響性能。
問:函數的閉包也依賴做用域,那閉包有沒有性能問題? 答案:有的
追根溯源
再來個閉包例子:
function assignEvents() {
var id = 'xdi9592';
document.getElementById('save-btn').onclick = function(event) {
saveDocument(id);
}
}
複製代碼
當執行assignEvents函數時,就會建立包含當前環境的第一個活動對象,而後再就是全局的一個活動對象。當閉包被建立的時候,它的[[scope]]屬性被初始化爲前面的兩個活動對象。以下圖:
最佳實踐
參考資料:
《高性能JS》