- 相關(guān)推薦
高效編寫JavaScript代碼的技巧
如何能高效的編寫出優(yōu)秀的JavaScript代碼,有哪些技巧?下面YJBYS小編為你介紹!
原則
不要做任何優(yōu)化除非的確需要優(yōu)化
任何的性能優(yōu)化都必須以測量數(shù)據(jù)為基礎(chǔ),如果你懷疑代碼存在性能問題,首先通過測試來驗證你的想法。
性能優(yōu)化三問
我還能做哪些工作從而讓代碼變得更有效率?
流行的JavaScript引擎通常會做哪些優(yōu)化工作?
哪些優(yōu)化是JavaScript引擎不能做的,垃圾回收器是否能清理我們期望清理的?
對JavaScript引擎的深入了解有助于我們編寫高效的JavaScript代碼,但不要只針對某一特定引擎做性能優(yōu)化。
V8的幾個關(guān)鍵概念
基礎(chǔ)編譯器,解析你的JavaScript代碼并生成Native Machine Code執(zhí)行,而不是執(zhí)行字節(jié)碼或是直接對JavaScript解釋執(zhí)行。
在V8中,對象以object model的形式存在。對象在JavaScript中是以關(guān)聯(lián)數(shù)組的形式存在,但V8采用的是Hidden
Classes——一種對查找操作進行了優(yōu)化的內(nèi)部類型系統(tǒng)。
運行時探查器監(jiān)視運行中的系統(tǒng),并識別出Hot functions,即是耗用了較長時間的代碼
優(yōu)化編譯器重新編譯并優(yōu)化由運行時探查器識別出來的Hot代碼
V8支持反優(yōu)化,優(yōu)化編譯器能夠發(fā)現(xiàn)過度優(yōu)化的代碼并對其進行處理
V8有自己的垃圾回收器
垃圾回收垃圾回收是一種內(nèi)存管理機制,垃圾回收器會嘗試清理掉不再被使用的對象,并回收內(nèi)存。
在絕大多數(shù)情況下都不需要手動解除引用
你不可能強制垃圾回收器工作
刪除引用的誤區(qū)
盡可能不要使用delete,在下面的列子中,delete 帶來的弊遠遠大于利
var o = { x: 1};delete o.x;
主要的原因是為了避免在運行時修改Hot對象的結(jié)構(gòu),因為固定的對象結(jié)構(gòu)有助于JavaScript引擎對其進行優(yōu)化,而delete會導致對象結(jié)構(gòu)改變。
另外一個誤區(qū)是將對象設(shè)置為null,將對象設(shè)置為null不會刪除對象,只是將對象指向null,這要好過采用delete,但通常也是不必要的。
全局變量在整個頁面生命周期中都是不會被清理的,無論頁面打開多長時間,除非是刷新頁面或者轉(zhuǎn)到其他頁面。局部變量(Function-scoped)在方法執(zhí)行完后,且沒有被引用的情況下將會被回收。
所以,請盡量避免使用全局變量。
經(jīng)驗法則為了使垃圾回收器盡早回收對象,不要保持不必要的對象引用。
比手動解除引用更好的方法是將對象放在合適的變量域中,能用局部變量就不要采用全局變量
當事件監(jiān)聽不再需要時,請解除事件綁定,尤其是當事件綁定的DOM對象被刪除時
如果有使用本地緩存,請確保有合適的清理機制(比如時效機制),從而避免大量無用的數(shù)據(jù)存儲。
方法 (Function)
如前面所說,垃圾回收器只有在對象不可觸及的時候才會對其做回收處理?紤]如下兩個列子:
function foo(){
var bar = new LargeObject()
bar.someCall();}
function foo(){
var bar = new LargeObject()
bar.someCall();
return bar;}var b = foo();
在第一個例子中,bar指向的對象會在方法執(zhí)行完畢后處于可回收狀態(tài);在第二個列子中,由于在局部變量外維護了一個全局變量b,bar指向的對象無法被回收。
閉包 (Closures)
當一個方法返回一個內(nèi)部方法時,被返回的內(nèi)部方法能訪問外部方法的局部變量域即使外部方法已經(jīng)執(zhí)行完畢。
function sum(x){
function sumIt(y){
return x + y;
}}var sumA = sum(4);var sumB = sumA(3);
在上面的例子中,sumIt方法即使處于sum的局部變量域中,但由于存在一個sumA全局變量,在sum執(zhí)行完畢后也無法被回收。再看兩個例子
var a = function(){
var largeObj = new LargeObject();
return function(){
return largeObj;
}}();var a = function(){
var smallObj = new SmallObj();
var largeObj = new LargeObj();
return function(n){
return smallObj;
}}();
第一個例子中,largeObj可以通過變量a訪問,因此不可被回收;在第二個例子中,方法一旦執(zhí)行完畢,largeObj就無法被訪問了,因此處于可回收狀態(tài)。
定時器 (Timer)在setTimeout / setInterval方法中的引用,只有當定時器執(zhí)行完成后才能被回收。
V8優(yōu)化小貼士
某些行為會導致V8停止優(yōu)化工作,比如try-catch,為了能弄清哪些代碼可以被優(yōu)化,哪些不能,你可以在V8命令行工具中使用—trace-opt file.js獲得有用的信息。
如果你在意速度,那就盡可能保證你的方法是”單形的(monomophic)"
不要做類似如下的嘗試
function add(x, y){
return x+y;}add(1,2);add('a','b');add(my_custom_object, undefined);
不要加載沒有被初始化或者已被刪除的元素,盡管在輸出上沒有不同,但卻會讓代碼變得更慢
不要寫大方法,因為他們很難被優(yōu)化。
對象還是數(shù)組, 如何選擇?
如果存儲的是大量數(shù)字,或者是相同類型的對象列表,采用數(shù)組;
如果根據(jù)語義你需要一個有很多屬性的對象,那就采用對象,在內(nèi)存利用方面這會很高效,同時也很快;
無論是數(shù)組還是對象,采用整數(shù)索引都最快的。
var sum = 0;for (var x=0; x
sum + = arr[x].payload;}
var sum = 0;for(var x in obj){
sum += obj[x].payload;}
var sum = 0;for(var x=0; x<1000,++x){
sum += obj[x].payload;}
var sum = 0;var keys = Objects.keys(obj);for(var x=0; x
sum += obj[keys[x]].payload;}
在上面的四段代碼中,第一段和第三段速度比第二段和第四段要快很多。其中,第一段代碼執(zhí)行最快,最后一段代碼執(zhí)行最慢。
相比數(shù)組中的元素,對象的屬性在結(jié)構(gòu)上相對復雜。在引擎層面,內(nèi)存中越是簡單的結(jié)構(gòu)越容易被優(yōu)化,尤其是包含數(shù)字的數(shù)組。因此,如果你需要向量,采用數(shù)組而不是一個包含x,y, z屬性的對象會有更優(yōu)的性能表現(xiàn)。
在JavaScript中,數(shù)組和對象最重要的不同是數(shù)組的length屬性,如果你能自己維護這個值,對象在V8中也能跑出數(shù)組的速度。
使用對象的性能小貼士
使用構(gòu)造函數(shù)創(chuàng)建對象,因為所有采用同一構(gòu)造函數(shù)創(chuàng)建的對象都具有相同的hidden class,另外,采用構(gòu)造函數(shù)創(chuàng)建對象也比Object.create()這種方法略塊。
盡管JavaScript沒有限制類型數(shù)量和對象的復雜度,但長原型鏈和大量的對象屬性會對性能造成損害。因此盡可能保持較短的原型鏈和較少的對象屬性。
對象的拷貝for..in循環(huán)是性能殺手,通過該方法遍歷對象屬性進行拷貝非常低效?截惔髮ο笫冀K會降低性能,盡可能不要干這樣的事情,當然大對象的存在本身就是一個錯誤。如果你確實需要在性能攸關(guān)的代碼中拷貝對象,可以采用如下的方式。
function clone(original){
this.foo = original.foo;
this.bar = original.bar;}var copy = new clone(original);
緩存采用模塊化編程(Module Pattern)的方法
// prototypalKlass1 = function(){}Klass1.prototype.foo = function(){
log('foo');}Klass1.prototype.bar = function(){
log('bar');}
// Module patternKlass2 = function(){
var foo = function(){
log('foo');
}
var bar = function(){
log('bar');
}
return {foo:foo,bar:bar}}
// Module pattern with cached functionsvar fooFn = function(){
log('foo');}var barFn = function(){
log('bar')}Klass3 = function(){
return{
foo: fooFn,
bar: barFn }}
執(zhí)行速度從快到慢依次是
Module Pattern with Cached functions → prototypal → Module pattern
使用數(shù)組的性能小貼士
不要刪除數(shù)組元素,當數(shù)組的Key set分布分散后,V8會將存儲方式轉(zhuǎn)為字典,導致速度變慢。
數(shù)組常量更高效,尤其是小數(shù)組和中等大小的數(shù)組。
var a = [1, 2, 3, 4]
var a = [];for(var i=1, i<=4; i++){
a.push(i);}
不要采用第二段代碼中的方法初始化數(shù)組。
不要在數(shù)組中存儲不同類型的元素
V8中,稀疏數(shù)組( Sparse Arrays)是被當成字典對待的,因此相比密集數(shù)組(Full Arrays),執(zhí)行速度更慢
與緊湊的數(shù)組相比,滿身是洞的數(shù)組執(zhí)行更慢,即使是從密集數(shù)組中刪除一個元素,也會帶來性能上的損失。
不要預先給大數(shù)組(大于64k)分配一個最大值
var arr = [];for(var i = 0; i< 1000000; i++){
arr[i] = 1;}
var arr = new Array(1000000);for(var i=0; i<1000000; i++){
arr[i]=i;}
需要注意的是,不同引擎在這一點上有不同,在Nitro(Safari)中,第二段代碼跑得更快,但在V8(Chrome), SpiderMonkey(Firefox)中,第一段更快。
【高效編寫JavaScript代碼的技巧】相關(guān)文章:
在Java中執(zhí)行JavaScript代碼04-01
JavaScript實現(xiàn)網(wǎng)頁刷新代碼段03-25
常用排序算法之JavaScript實現(xiàn)代碼段03-10
網(wǎng)頁程序設(shè)計之實用JavaScript代碼段03-06
如何讓JAVA代碼更高效03-20
關(guān)jQuery彈出窗口簡單實現(xiàn)代碼-javascript編程03-30
J2EE項目代碼編寫規(guī)范01-23
javascript中for/in循環(huán)以及常見的使用技巧04-02
高效溝通的技巧02-26