javascript
JS的数据访问及优化访问速度
JavaScript中有四種基本的數據存取位置:
- ? 直接量
直接量只代表自身,不存儲在特定位置。JS中的直接量有:字符串,數字,布爾值,對象,數組,函數,正則表達式,以及特殊的null和undefined值。
- ? 變量
用var定義的數據存儲單元
- 數組元素
存儲在JS數組對象內部,以數字作為索引
- ? 數據成員
存儲在JS對象內部,以字符串為索引
作用域工作原理和標識符解析
關鍵字:內部屬性[[Scope]]、可變對象、運行期上下文、執行環境、活動對象
1. 作用域工作原理
?
作用域鏈的作用:存儲執行環境可以訪問的數據,使用方法是標識符解析
每一個JS函數都表示為一個對象,更確切的說,是Function對象的一個實例。Function對象同其他對象一樣,擁有可以編程訪問的屬性,和一系列不能通過代碼訪問而僅供JS引擎存取的內部屬性。內部屬性[[Scope]]指向一個函數被創建的作用域中對象的集合。這個集合被稱為函數的作用域鏈,它決定哪些數據可以被函數訪問。函數作用域中的每個對象被稱為可變對象,每個可變對象都以“鍵值對”的形式存在。當一個函數被創建后,它的作用域鏈會被創建此函數的作用域中可訪問的數據對象所填充。思考下面的全局函數
function add(num1, num2) {????????????
???????? var sum = num1 + num2;
???????? return sum;
}
當函數add()創建時,它的作用域鏈填入了一個單獨的可變對象,包含了函數能訪問到的所有數據。本可變對象是時刻存在的全局對象。
函數add的作用域將會在執行時用到。
var total = add(1, 2);
???????? 執行此函數時會創建一個稱為”運行期上下文”的內部對象。一個運行期上下文定義了一個函數的執行環境。函數每次執行時對應的運行期上下文都是獨一無二的,所以多次調用同一函數就會導致創建多個運行期上下文。當函數執行完畢,執行期上下文會被銷毀。
???????? 每個運行期上下文都有自己的作用域鏈,用于標識符解析。當它被創建時,它的作用域鏈被初始化為當前運行函數的[[Scope]]屬性所指向的對象。這些值按照它們出現在函數中的順序,被復制到運行期上下文的作用域鏈中。這個過程一旦完成,一個被稱為”活動對象”的新對象就為運行期上下文創建好了?;顒訉ο笞鳛楹瘮颠\行期中的可變對象,包含了所有局部變量、命名參數、參數集合arguments以及this.然后此對象被推入到作用域鏈的前端。當運行期上下文被銷毀,活動對象也隨之銷毀。
2. 標識符解析過程
???????? 在函數執行過程中,每遇到一個變量,都會經歷一次標識符解析過程以決定從哪里獲取或存儲數據。該過程搜索運行期上下文的作用域鏈,查找同名的標識符。搜索過程從作用域鏈頭部開始,也就是當前運行函數的活動對象。如果找到了,就使用這個標識符對應的變量;如果沒找到,繼續搜索作用域鏈中的下一個對象。搜索過程會持續進行,直到標識符被找到,或者沒有可用于搜索的對象為止,這種情況下標識符被認為是未定義的。函數執行過程中,每個標識符都要經歷這樣的搜索過程。
???????? 對于所有瀏覽器而言,總的趨勢是,一個標識符所在的位置越深(作用域鏈末端),它的讀寫速度也就越慢。一個好的經驗法則是:如果某個跨作用域值在函數中被引用一次以上,那么就把它存儲在局部變量里。
閉包,作用域和內存
官方解釋為: 所謂“閉包”,指的是一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。
我認為閉包就是能夠讀/寫函數內部的某些變量的子函數,并將這些變量保存在內存中.
function assignEvents() {
???????? var id = ‘index’;
???????? document.getElementById(“save-btn”).onclick = function(event) {
???????? saveDocument(id);
? ? };
}
函數給一個DOM元素設置事件處理器。這個事件處理器就是一個閉包,它在函數執行時被創建,并且能訪問所屬作用域的id變量。為了讓這個閉包訪問id變量,必須創建一個特別的作用域鏈。
???????? 當assignEvents()函數被執行時,一個包含變量id的以及其他一些數據的活動對象被創建。它成為運行期上下文作用域鏈中第一個對象,而全局對象緊隨其后。當閉包被創建時,它的[[Scope]]屬性被初始化為這些對象。(如圖)
因為閉包的[[Scope]]屬性包含了與運行期上下文作用域相同的對象的引用,因此會有一些副作用。
通常的活動對象會隨同運行期上下文一同銷毀。但引入閉包時,由于引用仍然存在閉包的[[Scope]]屬性中,因此活動對象無法被銷毀。這意味著閉包的存在,需要更多的內存開銷。
???????? 當閉包被執行時,一個運行期上下文被創建,它的作用域鏈與屬性[[Scope]]中引用的兩個相同的作用域鏈對象同時被初始化,然后一個活動對象為閉包自身所創建。
???????? 注意在閉包中用到的兩個標識符,id和saveDocument,它們存在作用域鏈第一個對象之后的位置。這就是使用閉包最主要的性能關注點:要經常訪問大量跨作用域的標識符,每次訪問都會導致性能損失。
閉包的應用環境
1. 保護函數內的變量安全
2. 在內存中維持一個變量
????????
對象成員
???????? 訪問對象成員的速度比訪問直接量或變量更慢,在某些瀏覽器中比訪問數組元素還要慢。
???????? JS中的對象是基于原型的。原型是其他對象的基礎,定義并實現了一個新對象必須包含的成員列表。這一概念完全不同于傳統面向對象編程語言的‘類’的概念,‘類’定義了創建新對象的過程。而原型對象為所有對象實例所共享,因此這些實例也共享了原型對象的成員。
???????? 實例通過一個內部屬性綁定它的原型__proto__,比如一旦你創建一個內置對象(Object、Array)的實例,它們就會自動擁有一個Object實例作為原型。
???????? 實例可以有兩種成員類型:
實例成員(也稱‘own’成員):存在對象實例中
原型成員:由對象原型繼承而來。
var Book = {
???????? title: “JavaScript”,
???????? content: ‘JavaScript是一門博大精深的語言’
};
alert(BooK.toString());????????????? //[object Object]
???????? 方法toString()是對象Book繼承而來的原型成員。解析對象成員的過程同解析變量十分相似。當Book.toString()
被調用時,會從對象實例開始,搜索名為’toString’成員。如果實例中不存在,那么會繼續搜索其原型對象,直到
toString()方法被找到并且執行。實例可以訪問它原型中的每一個屬性和方法。
hasOwnProperty()判斷對象是否包含特定的實例成員。
in操作符判斷對象是否包含特定的屬性。
原型鏈
???????? 對象的原型決定了實例的類型。默認情況下,所有對象都是Object的實例,并繼承了所有基本方法。
嵌套成員
???????? window.localhost.href每次遇到點操作符,嵌套成員會導致JavaScript引擎搜索所有對象成員。這些屬性不是對象的實例屬性,那么成員解析還需要搜索原型鏈,這會花更多的時間。
???????? 緩存對象成員值,不適合緩存對象的方法。
小結:
摘自:高性能JavaScript
轉載于:https://www.cnblogs.com/mackxu/archive/2012/12/02/2798744.html
總結
以上是生活随笔為你收集整理的JS的数据访问及优化访问速度的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 封装事件绑定函数解决this在ie下的绑
- 下一篇: 协议森林