Rumah  >  Artikel  >  hujung hadapan web  >  Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

WBOY
WBOYasal
2016-05-16 16:39:461175semak imbas

Nota penterjemah: Ini adalah kali pertama saya menterjemah bahasa asing, jadi bahasa itu tidak dapat dielakkan, tetapi saya cuba sedaya upaya untuk menyatakan niat asal penulis tanpa menggilap dan pembetulan yang keterlaluan. Di samping itu, artikel ini panjang dan mengandungi banyak maklumat, yang mungkin sukar dihadam Anda dialu-alukan untuk meninggalkan mesej untuk membincangkan butirannya. Artikel ini tertumpu terutamanya pada pengoptimuman prestasi V8, dan sesetengah kandungan tidak digunakan untuk semua enjin JS. Akhir sekali, sila nyatakan sumber semasa mencetak semula: )

========================Baris pemisah terjemahan ======================= == =======

Banyak enjin JavaScript, seperti enjin V8 Google (digunakan oleh Chrome dan Node), direka khusus untuk aplikasi JavaScript besar yang memerlukan pelaksanaan pantas. Jika anda seorang pembangun dan bimbang tentang penggunaan memori dan prestasi halaman, anda harus memahami cara enjin JavaScript dalam penyemak imbas pengguna berfungsi. Sama ada ia V8, SpiderMonkey (Firefox), Carakan (Opera), Chakra (IE) atau enjin lain, melakukan ini boleh membantu anda mendapatkan lebih banyak Optimize apl anda dengan baik. Ini tidak bermakna anda harus mengoptimumkan untuk penyemak imbas atau enjin tertentu, jangan lakukan itu. Walau bagaimanapun, anda harus bertanya kepada diri sendiri beberapa soalan: Dalam kod saya, bolehkah saya menjadikan kod itu lebih cekap? Apakah pengoptimuman yang telah dilakukan oleh enjin JavaScript arus perdana

Apakah yang tidak boleh dioptimumkan oleh enjin? Bolehkah pengumpul sampah (GC) memulihkan apa yang saya harapkan?

Memuatkan

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua JavascriptTapak web yang pantas adalah seperti kereta sport yang laju, memerlukan bahagian yang disesuaikan khas:

.

Terdapat beberapa masalah biasa semasa menulis kod berprestasi tinggi, dan dalam artikel ini kami akan menunjukkan kepada anda beberapa cara yang terbukti dan lebih baik untuk menulisnya. Jadi, bagaimanakah JavaScript berfungsi dalam V8?

Jika anda tidak mempunyai pemahaman yang mendalam tentang enjin JS, tiada masalah untuk membangunkan aplikasi web berskala besar, seperti orang yang tahu memandu hanya melihat hud tetapi tidak melihat enjin di dalam tudung kereta. Memandangkan Chrome ialah penyemak imbas pilihan saya, mari kita bincangkan tentang enjin JavaScriptnya. V8 terdiri daripada bahagian teras berikut:

Pengkompil asas yang menghuraikan kod JavaScript dan menjana kod mesin asli sebelum kod dilaksanakan, bukannya melaksanakan kod bait atau hanya mentafsirkannya. Kod ini tidak sangat dioptimumkan untuk bermula.

V8 membina objek ke dalam

Model Objek
    . Dalam JavaScript objek diwakili sebagai tatasusunan bersekutu, tetapi dalam objek V8 dianggap sebagai kelas tersembunyi, sistem jenis dalaman untuk mengoptimumkan pertanyaan.
  • Penganalisis Masa Jalan
  • memantau sistem yang sedang berjalan dan mengenal pasti fungsi "panas" (cth. kod yang mengambil masa yang lama untuk dijalankan).
  • Mengoptimumkan pengkompil
  • Menyusun semula dan mengoptimumkan kod yang dikenal pasti sebagai "panas" oleh penganalisis masa jalan dan melakukan pengoptimuman seperti "inlining" (seperti menggantikan panggilan fungsi dengan badan penerima) Lokasi ).
  • V8 menyokong nyahoptimumkan
  • , yang bermaksud bahawa jika pengkompil pengoptimuman mendapati bahawa andaiannya tentang pengoptimuman kod terlalu optimistik, ia akan membuang kod yang dioptimumkan.
  • V8 mempunyai pengumpul sampah
  • , dan memahami cara ia berfungsi adalah sama pentingnya dengan mengoptimumkan JavaScript.
  • Pengumpulan Sampah
  • Pengumpulan sampah adalah
  • satu bentuk pengurusan memori
Ia sebenarnya konsep pengumpul yang cuba menuntut semula memori yang diduduki oleh objek yang tidak digunakan lagi. Dalam bahasa yang dikumpul sampah seperti JavaScript, objek yang masih dirujuk dalam aplikasi tidak dikosongkan.

Penghapusan manual rujukan objek tidak diperlukan dalam kebanyakan kes. Dengan hanya meletakkan pembolehubah di tempat yang diperlukan (sebaik-baiknya, sebagai skop setempat yang mungkin, iaitu di dalam fungsi yang digunakan dan bukannya di luar fungsi), semuanya akan berfungsi dengan baik.

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

Pengumpul sampah cuba menuntut semula ingatan: Valtteri Mäki.

Dalam JavaScript, adalah mustahil untuk memaksa kutipan sampah. Anda tidak sepatutnya melakukan ini kerana proses kutipan sampah dikawal oleh masa jalan, yang mengetahui bila masa terbaik untuk membersihkannya.

Salah faham "menghapuskan petikan"

Terdapat banyak perbincangan mengenai kitar semula memori JavaScript di Internet tentang kata kunci padam Walaupun ia boleh digunakan untuk memadamkan atribut (kunci) dalam objek (peta), sesetengah pembangun percaya bahawa ia boleh digunakan untuk memaksa " Hapuskan rujukan". . Adalah disyorkan untuk mengelak daripada menggunakan padam jika boleh, dalam contoh berikut delete o.x 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。

var o = { x: 1 }; 
delete o.x; // true 
o.x; // undefined

Anda boleh menemui penyingkiran petikan dengan mudah dalam perpustakaan JS yang popular - ia bertujuan untuk bahasa. Apa yang perlu diperhatikan di sini adalah untuk mengelakkan pengubahsuaian struktur objek "panas" semasa runtime. Enjin JavaScript boleh mengesan objek "panas" sedemikian dan cuba mengoptimumkannya. Jika struktur objek tidak berubah dengan ketara semasa kitaran hayatnya, ia akan menjadi lebih mudah bagi enjin untuk mengoptimumkan objek, dan operasi pemadaman sebenarnya akan mencetuskan perubahan struktur yang begitu besar, yang tidak kondusif untuk pengoptimuman enjin.

Terdapat juga salah faham tentang cara null berfungsi. Menetapkan rujukan objek kepada null tidak menjadikan objek "null", ia hanya menetapkan rujukannya kepada null. Menggunakan o.x=null adalah lebih baik daripada menggunakan padam, tetapi mungkin tidak diperlukan.

var o = { x: 1 }; 
o = null;
o; // null
o.x // TypeError

Jika rujukan ini adalah rujukan terakhir kepada objek semasa, maka objek tersebut akan menjadi sampah yang dikumpul. Jika rujukan ini bukan rujukan terakhir kepada objek semasa, objek itu boleh diakses dan tidak akan dikumpul sampah.

Perkara lain yang perlu diberi perhatian ialah pembolehubah global tidak dibersihkan oleh pemungut sampah semasa kitaran hayat halaman. Tidak kira berapa lama halaman dibuka, pembolehubah dalam skop objek global akan sentiasa wujud apabila JavaScript sedang dijalankan.

var myGlobalNamespace = {};

Objek global hanya dibersihkan apabila anda memuat semula halaman, menavigasi ke halaman lain, menutup tab atau keluar dari penyemak imbas. Pembolehubah dalam skop fungsi akan dikosongkan apabila ia keluar dari skop, iaitu, apabila keluar dari fungsi, tiada lagi rujukan, dan pembolehubah tersebut akan dikosongkan.

Peraturan praktikal

Agar pengutip sampah dapat mengumpul sebanyak mungkin objek seawal mungkin, jangan pegang objek yang tidak digunakan lagi. Berikut ialah beberapa perkara yang perlu diingat:

  • Seperti yang dinyatakan sebelum ini, menggunakan pembolehubah dalam skop yang sesuai adalah alternatif yang lebih baik kepada penyahrujukan manual. Iaitu, jika pembolehubah hanya digunakan dalam skop fungsi, jangan isytiharkannya dalam skop global. Ini bermakna kod yang lebih bersih dan bebas kerumitan.
  • Pastikan untuk melepaskan pendengar acara yang tidak diperlukan lagi, terutamanya yang terikat pada objek DOM yang akan dimusnahkan.
  • Jika data yang digunakan dicache secara setempat, pastikan anda mengosongkan cache atau gunakan mekanisme penuaan untuk mengelak daripada menyimpan sejumlah besar data yang tidak digunakan semula.

Fungsi

Seterusnya, mari bercakap tentang fungsi. Seperti yang telah kami katakan, kutipan sampah berfungsi dengan menuntut semula blok memori (objek) yang tidak lagi boleh diakses. Untuk menggambarkan ini dengan lebih baik, berikut adalah beberapa contoh.

function foo() {
 var bar = new LargeObject();
 bar.someCall();
}

Apabila foo kembali, objek yang ditunjuk oleh bar akan dikitar semula secara automatik oleh pemungut sampah kerana ia tidak lagi mempunyai sebarang rujukan sedia ada.

Bandingkan:

function foo() {
 var bar = new LargeObject();
 bar.someCall();
 return bar;
}

// somewhere else
var b = foo();

Sekarang kita mempunyai rujukan yang menunjuk ke objek bar, supaya kitaran hayat objek bar berlangsung dari panggilan foo sehingga pemanggil menentukan pembolehubah lain b (atau b keluar dari skop).

PENUTUP

Apabila anda melihat fungsi yang mengembalikan fungsi dalam, fungsi dalam itu akan mendapat akses luar skop, walaupun selepas fungsi luar dilaksanakan. Ini ialah penutupan asas — ungkapan yang boleh menetapkan pembolehubah dalam konteks tertentu. Contohnya:

function sum (x) {
 function sumIt(y) {
  return x + y;
 };
 return sumIt;
}

// Usage
var sumA = sum(4);
var sumB = sumA(3);
console.log(sumB); // Returns 7

Objek fungsi (sumIt) yang dijana dalam konteks panggilan jumlah tidak boleh dikitar semula ia dirujuk oleh pembolehubah global (sumA) dan boleh dipanggil melalui sumA(n).

Mari kita lihat contoh lain Bolehkah kita mengakses pembolehubah largeStr di sini?

var a = function () {
 var largeStr = new Array(1000000).join('x');
 return function () {
  return largeStr;
 };
}();

Ya, kami boleh mengakses largeStr melalui a(), jadi ia tidak dikitar semula. Bagaimana dengan yang di bawah?

var a = function () {
 var smallStr = 'x';
 var largeStr = new Array(1000000).join('x');
 return function (n) {
  return smallStr;
 };
}();

Kami tidak lagi boleh mengakses largeStr, ia sudah menjadi calon kutipan sampah. [Nota Penterjemah: Kerana largeStr tidak lagi mempunyai rujukan luaran]

定时器

最糟的内存泄漏地方之一是在循环中,或者在setTimeout()/ setInterval()中,但这是相当常见的。思考下面的例子:

var myObj = {
 callMeMaybe: function () {
  var myRef = this;
  var val = setTimeout(function () { 
   console.log('Time is running out!'); 
   myRef.callMeMaybe();
  }, 1000);
 }
};

如果我们运行myObj.callMeMaybe();来启动定时器,可以看到控制台每秒打印出“Time is running out!”。如果接着运行myObj = null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。

同样值得牢记的是,setTimeout/setInterval调用(如函数)中的引用,将需要执行和完成,才可以被垃圾收集。

当心性能陷阱

永远不要优化代码,直到你真正需要。现在经常可以看到一些基准测试,显示N比M在V8中更为优化,但是在模块代码或应用中测试一下会发现,这些优化真正的效果比你期望的要小的多。

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

做的过多还不如什么都不做. 图片来源: Tim Sheerman-Chase.

比如我们想要创建这样一个模块:

  • 需要一个本地的数据源包含数字ID
  • 绘制包含这些数据的表格
  • 添加事件处理程序,当用户点击的任何单元格时切换单元格的css class

这个问题有几个不同的因素,虽然也很容易解决。我们如何存储数据,如何高效地绘制表格并且append到DOM中,如何更优地处理表格事件?

面对这些问题最开始(天真)的做法是使用对象存储数据并放入数组中,使用jQuery遍历数据绘制表格并append到DOM中,最后使用事件绑定我们期望地点击行为。

注意:这不是你应该做的

var moduleA = function () {

 return {

  data: dataArrayObject,

  init: function () {
   this.addTable();
   this.addEvents();
  },

  addTable: function () {

   for (var i = 0; i < rows; i++) {
    $tr = $('<tr></tr>');
    for (var j = 0; j < this.data.length; j++) {
     $tr.append('<td>' + this.data[j]['id'] + '</td>');
    }
    $tr.appendTo($tbody);
   }

  },
  addEvents: function () {
   $('table td').on('click', function () {
    $(this).toggleClass('active');
   });
  }

 };
}();

这段代码简单有效地完成了任务。

但在这种情况下,我们遍历的数据只是本应该简单地存放在数组中的数字型属性ID。有趣的是,直接使用DocumentFragment和本地DOM方法比使用jQuery(以这种方式)来生成表格是更优的选择,当然,事件代理比单独绑定每个td具有更高的性能。

要注意虽然jQuery在内部使用DocumentFragment,但是在我们的例子中,代码在循环内调用append并且这些调用涉及到一些其他的小知识,因此在这里起到的优化作用不大。希望这不会是一个痛点,但请务必进行基准测试,以确保自己代码ok。

对于我们的例子,上述的做法带来了(期望的)性能提升。事件代理对简单的绑定是一种改进,可选的DocumentFragment也起到了助推作用。

var moduleD = function () {

 return {

  data: dataArray,

  init: function () {
   this.addTable();
   this.addEvents();
  },
  addTable: function () {
   var td, tr;
   var frag = document.createDocumentFragment();
   var frag2 = document.createDocumentFragment();

   for (var i = 0; i < rows; i++) {
    tr = document.createElement('tr');
    for (var j = 0; j < this.data.length; j++) {
     td = document.createElement('td');
     td.appendChild(document.createTextNode(this.data[j]));

     frag2.appendChild(td);
    }
    tr.appendChild(frag2);
    frag.appendChild(tr);
   }
   tbody.appendChild(frag);
  },
  addEvents: function () {
   $('table').on('click', 'td', function () {
    $(this).toggleClass('active');
   });
  }

 };

}();

接下来看看其他提升性能的方式。你也许曾经在哪读到过使用原型模式比模块模式更优,或听说过使用JS模版框架性能更好。有时的确如此,不过使用它们其实是为了代码更具可读性。对了,还有预编译!让我们看看在实践中表现的如何?

moduleG = function () {};

moduleG.prototype.data = dataArray;
moduleG.prototype.init = function () {
 this.addTable();
 this.addEvents();
};
moduleG.prototype.addTable = function () {
 var template = _.template($('#template').text());
 var html = template({'data' : this.data});
 $tbody.append(html);
};
moduleG.prototype.addEvents = function () {
 $('table').on('click', 'td', function () {
  $(this).toggleClass('active');
 });
};

var modG = new moduleG();

事实证明,在这种情况下的带来的性能提升可以忽略不计。模板和原型的选择并没有真正提供更多的东西。也就是说,性能并不是开发者使用它们的原因,给代码带来的可读性、继承模型和可维护性才是真正的原因。

更复杂的问题包括高效地在canvas上绘制图片和操作带或不带类型数组的像素数据。

在将一些方法用在你自己的应用之前,一定要多了解这些方案的基准测试。也许有人还记得JS模版的shoot-off随后的扩展版。你要搞清楚基准测试不是存在于你看不到的那些虚拟应用,而是应该在你的实际代码中去测试带来的优化。

V8优化技巧

详细介绍了每个V8引擎的优化点在本文讨论范围之外,当然这里也有许多值得一提的技巧。记住这些技巧你就能减少那些性能低下的代码了。

  • 特定模式可以使V8摆脱优化的困境,比如说try-catch。欲了解更多有关哪些函数能或不能进行优化,你可以在V8的脚本工具d8中使用–trace-opt file.js命令。
  • 如果你关心速度,尽量使你的函数职责单一,即确保变量(包括属性,数组,函数参数)只使用相同隐藏类包含的对象。举个例子,别这么干:
    function add(x, y) { 
     return x+y;
    } 
    
    add(1, 2); 
    add('a','b'); 
    add(my_custom_object, undefined);
  • 不要加载未初始化或已删除的元素。如果这么做也不会出现什么错误,但是这样会使速度变慢。
  • 不要使函数体过大,这样会使得优化更加困难。

更多内容可以去看Daniel Clifford在Google I/O的分享 Breaking the JavaScript Speed Limit with V8Optimizing For V8 — A Series也非常值得一读。

对象VS数组:我应该用哪个?

  • 如果你想存储一串数字,或者一些相同类型的对象,使用一个数组。
  • 如果你语义上需要的是一堆的对象的属性(不同类型的),使用一个对象和属性。这在内存方面非常高效,速度也相当快。
  • 整数索引的元素,无论存储在一个数组或对象中,都要比遍历对象的属性快得多
  • 对象的属性比较复杂:它们可以被setter们创建,具有不同的枚举性和可写性。数组中则不具有如此的定制性,而只存在有和无这两种状态。在引擎层面,这允许更多存储结构方面的优化。特别是当数组中存在数字时,例如当你需要容器时,不用定义具有x,y,z属性的类,而只用数组就可以了。

JavaScript中对象和数组之间只有一个的主要区别,那就是数组神奇的length属性。如果你自己来维护这个属性,那么V8中对象和数组的速度是一样快的。

使用对象时的技巧

  • 使用一个构造函数来创建对象。这将确保它创建的所有对象具有相同的隐藏类,并有助于避免更改这些类。作为一个额外的好处,它也略快于Object.create()
  • 你的应用中,对于使用不同类型的对象和其复杂度(在合理的范围内:长原型链往往是有害的,呈现只有一个极少数属性的对象比大对象会快一点)是有没限制的。对于“hot”对象,尽量保持短原型链,并且少属性。

对象克隆

对于应用程序开发人员,对象克隆是一个常见的问题。虽然各种基准测试可以证明V8对这个问题处理得很好,但仍要小心。复制大的东西通常是较慢的——不要这么做。JS中的for..in循环尤其糟糕,因为它有着恶魔般的规范,并且无论是在哪个引擎中,都可能永远不会比任何对象快。

当你一定要在关键性能代码路径上复制对象时,使用数组或一个自定义的“拷贝构造函数”功能明确地复制每个属性。这可能是最快的方式:

function clone(original) {
 this.foo = original.foo;
 this.bar = original.bar;
}
var copy = new clone(original);

模块模式中缓存函数

使用模块模式时缓存函数,可能会导致性能方面的提升。参阅下面的例子,因为它总是创建成员函数的新副本,你看到的变化可能会比较慢。

另外请注意,使用这种方法明显更优,不仅仅是依靠原型模式(经过jsPerf测试确认)。

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

使用模块模式或原型模式时的性能提升

这是一个原型模式与模块模式的性能对比测试

 // Prototypal pattern
 Klass1 = function () {}
 Klass1.prototype.foo = function () {
  log('foo');
 }
 Klass1.prototype.bar = function () {
  log('bar');
 }

 // Module pattern
 Klass2 = function () {
  var foo = function () {
   log('foo');
  },
  bar = function () {
   log('bar');
  };

  return {
   foo: foo,
   bar: bar
  }
 }

 // Module pattern with cached functions
 var FooFunction = function () {
  log('foo');
 };
 var BarFunction = function () {
  log('bar');
 };

 Klass3 = function () {
  return {
   foo: FooFunction,
   bar: BarFunction
  }
 }

 // Iteration tests

 // Prototypal
 var i = 1000,
  objs = [];
 while (i--) {
  var o = new Klass1()
  objs.push(new Klass1());
  o.bar;
  o.foo;
 }

 // Module pattern
 var i = 1000,
  objs = [];
 while (i--) {
  var o = Klass2()
  objs.push(Klass2());
  o.bar;
  o.foo;
 }

 // Module pattern with cached functions
 var i = 1000,
  objs = [];
 while (i--) {
  var o = Klass3()
  objs.push(Klass3());
  o.bar;
  o.foo;
 }
// See the test for full details

使用数组时的技巧

接下来说说数组相关的技巧。在一般情况下,不要删除数组元素,这样将使数组过渡到较慢的内部表示。当索引变得稀疏,V8将会使元素转为更慢的字典模式。

数组字面量

数组字面量非常有用,它可以暗示VM数组的大小和类型。它通常用在体积不大的数组中。

// Here V8 can see that you want a 4-element array containing numbers:
var a = [1, 2, 3, 4];

// Don't do this:
a = []; // Here V8 knows nothing about the array
for(var i = 1; i <= 4; i++) {
  a.push(i);
}

存储单一类型VS多类型

将混合类型(比如数字、字符串、undefined、true/false)的数据存在数组中绝不是一个好想法。例如var arr = [1, “1”, undefined, true, “true”]

类型推断的性能测试

正如我们所看到的结果,整数的数组是最快的。

稀疏数组与满数组

当你使用稀疏数组时,要注意访问元素将远远慢于满数组。因为V8不会分配一整块空间给只用到部分空间的数组。取而代之的是,它被管理在字典中,既节约了空间,但花费访问的时间。

稀疏数组与满数组的测试

预分配空间VS动态分配

不要预分配大数组(如大于64K的元素),其最大的大小,而应该动态分配。在我们这篇文章的性能测试之前,请记住这只适用部分JavaScript引擎。

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

空字面量与预分配数组在不同的浏览器进行测试

Nitro (Safari)对预分配的数组更有利。而在其他引擎(V8,SpiderMonkey)中,预先分配并不是高效的。

预分配数组测试

// Empty array
var arr = [];
for (var i = 0; i < 1000000; i++) {
 arr[i] = i;
}

// Pre-allocated array
var arr = new Array(1000000);
for (var i = 0; i < 1000000; i++) {
 arr[i] = i;
}

优化你的应用

在Web应用的世界中,速度就是一切。没有用户希望用一个要花几秒钟计算某列总数或花几分钟汇总信息的表格应用。这是为什么你要在代码中压榨每一点性能的重要原因。

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

图片来源: Per Olof Forsberg.

理解和提高应用程序的性能是非常有用的同时,它也是困难的。我们推荐以下的步骤来解决性能的痛点:

  • 测量:在您的应用程序中找到慢的地方(约45%)
  • 理解:找出实际的问题是什么(约45%)
  • 修复它! (约10%)

下面推荐的一些工具和技术可以协助你。

基准化(BENCHMARKING)

有很多方式来运行JavaScript代码片段的基准测试其性能——一般的假设是,基准简单地比较两个时间戳。这中模式被jsPerf团队指出,并在SunSpiderKraken的基准套件中使用:

var totalTime,
 start = new Date,
 iterations = 1000;
while (iterations--) {
 // Code snippet goes here
}
// totalTime → the number of milliseconds taken 
// to execute the code snippet 1000 times
totalTime = new Date - start;

在这里,要测试的代码被放置在一个循环中,并运行一个设定的次数(例如6次)。在此之后,开始日期减去结束日期,就得出在循环中执行操作所花费的时间。

然而,这种基准测试做的事情过于简单了,特别是如果你想运行在多个浏览器和环境的基准。垃圾收集器本身对结果是有一定影响的。即使你使用window.Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript这样的解决方案,也必须考虑到这些缺陷。

不管你是否只运行基准部分的代码,编写一个测试套件或编码基准库,JavaScript基准其实比你想象的更多。如需更详细的指南基准,我强烈建议你阅读由Mathias Bynens和John-David Dalton提供的Javascript基准测试

分析(PROFILING)

Chrome开发者工具为JavaScript分析有很好的支持。可以使用此功能检测哪些函数占用了大部分时间,这样你就可以去优化它们。这很重要,即使是代码很小的改变会对整体表现产生重要的影响。

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

Panel Analisis Alat Pembangun Chrome

Proses analisis bermula dengan mendapatkan garis dasar prestasi kod, yang kemudiannya ditunjukkan dalam bentuk garis masa. Ini akan memberitahu kami berapa lama kod tersebut dijalankan. Tab "Profil" memberi kita pandangan yang lebih baik tentang perkara yang berlaku dalam aplikasi. Profil CPU JavaScript menunjukkan berapa banyak masa CPU digunakan oleh kod kami, profil pemilih CSS menunjukkan berapa banyak masa yang dibelanjakan untuk memproses pemilih, dan petikan timbunan menunjukkan jumlah memori yang digunakan untuk objek kami.

Dengan menggunakan alatan ini, kami boleh mengasingkan, menala dan menganalisis semula untuk mengukur sama ada pengoptimuman prestasi fungsi atau operasi kami sebenarnya mempunyai kesan.

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript2

Tab "Profil" memaparkan maklumat prestasi kod.

Untuk pengenalan yang baik tentang pemprofilan, baca Pemprofilan JavaScript Dengan Alat Pembangun Chrome Zack Grossbart.

Petua: Sebaik-baiknya, jika anda ingin memastikan analisis anda tidak terjejas dalam apa-apa cara oleh apl atau sambungan yang dipasang, lancarkan Chrome dengan bendera --user-data-dir <empty_directory></empty_directory>. Dalam kebanyakan kes, kaedah mengoptimumkan ujian anda ini sepatutnya mencukupi, tetapi juga memerlukan lebih banyak masa anda. Di sinilah logo V8 boleh membantu.

Elakkan kebocoran memori - 3 teknik syot kilat

Dalam Google, alat pembangun Chrome banyak digunakan oleh pasukan seperti Gmail untuk membantu mencari dan menyelesaikan masalah kebocoran memori.

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

Statistik Memori dalam Alat Pembangun Chrome

Statistik memori termasuk penggunaan memori peribadi, saiz timbunan JavaScript, bilangan nod DOM, pembersihan storan, kaunter mendengar acara dan perkara yang akan dikitar semula oleh pemungut sampah yang penting bagi pasukan kami. Adalah disyorkan untuk membaca Teknik "3 Syot Kilat" Loreena Lee . Intipati teknik ini adalah untuk mengelog beberapa gelagat dalam aplikasi anda, memaksa kutipan sampah, semak sama ada bilangan nod DOM telah kembali ke garis dasar yang dijangkakan, dan kemudian menganalisis tiga petikan timbunan untuk menentukan sama ada terdapat kebocoran memori.

Pengurusan memori untuk aplikasi satu halaman

Pengurusan memori adalah sangat penting untuk aplikasi satu halaman (cth. AngularJS, Backbone, Ember), mereka hampir tidak pernah memuat semula halaman. Ini bermakna bahawa kebocoran memori boleh menjadi agak jelas. Aplikasi satu halaman pada peranti mudah alih penuh dengan perangkap kerana peranti itu mempunyai memori terhad dan aplikasi yang berjalan lama seperti klien e-mel atau rangkaian sosial. Lebih besar kemampuan, lebih besar tanggungjawab.

Terdapat banyak cara untuk menyelesaikan masalah ini. Dalam Backbone, pastikan anda menggunakan dispose() untuk melupuskan pandangan dan rujukan lama (kini tersedia dalam Backbone(Edge)). Fungsi ini telah ditambahkan baru-baru ini dan mengalih keluar pengendali yang ditambahkan pada objek "peristiwa" paparan, serta pendengar acara untuk model atau koleksi yang dihantar ke argumen ketiga paparan (konteks panggilan balik). dispose() juga dipanggil oleh view's remove() dan mengendalikan kerja pembersihan utama apabila elemen dialih keluar. Perpustakaan lain seperti Ember akan membersihkan pendengar apabila mereka mengesan bahawa elemen telah dialih keluar untuk mengelakkan kebocoran memori.

Beberapa nasihat bijak daripada Derick Bailey:

与其了解事件与引用是如何工作的,不如遵循的标准规则来管理JavaScript中的内存。如果你想加载数据到的一个存满用户对象的Backbone集合中,你要清空这个集合使它不再占用内存,那必须这个集合的所有引用以及集合内对象的引用。一旦清楚了所用的引用,资源就会被回收。这就是标准的JavaScript垃圾回收规则。

在文章中,Derick涵盖了许多使用Backbone.js时的常见内存缺陷,以及如何解决这些问题。

Felix Geisendörfer的在Node中调试内存泄漏的教程也值得一读,尤其是当它形成了更广泛SPA堆栈的一部分。

减少回流(REFLOWS)

当浏览器重新渲染文档中的元素时需要 重新计算它们的位置和几何形状,我们称之为回流。回流会阻塞用户在浏览器中的操作,因此理解提升回流时间是非常有帮助的。

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

回流时间图表

你应该批量地触发回流或重绘,但是要节制地使用这些方法。尽量不处理DOM也很重要。可以使用DocumentFragment,一个轻量级的文档对象。你可以把它作为一种方法来提取文档树的一部分,或创建一个新的文档“片段”。与其不断地添加DOM节点,不如使用文档片段后只执行一次DOM插入操作,以避免过多的回流。

例如,我们写一个函数给一个元素添加20个div。如果只是简单地每次append一个div到元素中,这会触发20次回流。

function addDivs(element) {
 var div;
 for (var i = 0; i < 20; i ++) {
 div = document.createElement('div');
 div.innerHTML = 'Heya!';
 element.appendChild(div);
 }
}

要解决这个问题,可以使用DocumentFragment来代替,我们可以每次添加一个新的div到里面。完成后将DocumentFragment添加到DOM中只会触发一次回流。

function addDivs(element) {
 var div; 
 // Creates a new empty DocumentFragment.
 var fragment = document.createDocumentFragment();
 for (var i = 0; i < 20; i ++) {
 div = document.createElement('a');
 div.innerHTML = 'Heya!';
 fragment.appendChild(div);
 }
 element.appendChild(fragment);
}

可以参阅 Make the Web FasterJavaScript Memory OptimizationFinding Memory Leaks

JS内存泄漏探测器

为了帮助发现JavaScript内存泄漏,谷歌的开发人员((Marja Hölttä和Jochen Eisinger)开发了一种工具,它与Chrome开发人员工具结合使用,检索堆的快照并检测出是什么对象导致了内存泄漏。

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

一个JavaScript内存泄漏检测工具

有完整的文章介绍了如何使用这个工具,建议你自己到内存泄漏探测器项目页面看看。

如果你想知道为什么这样的工具还没集成到我们的开发工具,其原因有二。它最初是在Closure库中帮助我们捕捉一些特定的内存场景,它更适合作为一个外部工具。

V8优化调试和垃圾回收的标志位

Chrome支持直接通过传递一些标志给V8,以获得更详细的引擎优化输出结果。例如,这样可以追踪V8的优化:

"/Applications/Google Chrome/Google Chrome" --js-flags="--trace-opt --trace-deopt"

Windows用户可以这样运行 chrome.exe –js-flags=”–trace-opt –trace-deopt”

在开发应用程序时,下面的V8标志都可以使用。

  • pilihan jejak - merekodkan nama fungsi yang dioptimumkan dan menunjukkan kod yang dilangkau kerana pengoptimum tidak tahu cara mengoptimumkan.
  • trace-deopt - Rakam kod yang akan "dioptimumkan" semasa masa jalan.
  • trace-gc - merekodkan setiap kutipan sampah.

Skrip pemprosesan V8 menggunakan * (asterisk) untuk mengenal pasti fungsi yang dioptimumkan dan ~ (tilde) untuk menunjukkan fungsi yang tidak dioptimumkan.

Jika anda berminat untuk mengetahui lebih lanjut tentang bendera V8 dan cara dalaman V8 berfungsi, adalah sangat disyorkan untuk membaca siaran cemerlang Vyacheslav Egorov tentang dalaman V8.

MASA PENYELESAIAN TINGGI dan API PEMASAAN NAVIGATION

Masa berketepatan tinggi (HRT) ialah antara muka masa berketepatan tinggi sub-milisaat yang tidak dipengaruhi oleh masa sistem dan pelarasan pengguna Ia boleh dianggap lebih maju daripada Tarikh dan Tarikh baharu. now() Kaedah pengukuran yang tepat. Ini banyak membantu kami semasa menulis penanda aras.

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

Masa Ketepatan Tinggi (HRT) memberikan ketepatan masa sub-milisaat semasa

Pada masa ini HRT digunakan dalam Chrome (versi stabil) dalam kaedah window.Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript.webkitNow(), tetapi awalan dibuang dalam Chrome Canary, yang membolehkan ia dipanggil dalam window.Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript.now() kaedah. Paul Irish menulis lebih lanjut mengenai HRT di HTML5Rocks.

Sekarang kita tahu masa yang tepat semasa, adakah terdapat API yang boleh mengukur prestasi halaman dengan tepat? Nah, kini terdapat API Pemasa Navigasi tersedia API ini menyediakan cara mudah untuk mendapatkan rekod pengukuran masa yang tepat dan terperinci apabila halaman web dimuatkan dan dipersembahkan kepada pengguna. Anda boleh menggunakan window.Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript.timing dalam konsol untuk mendapatkan maklumat masa:

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

Maklumat masa dipaparkan dalam konsol

Kita boleh mendapatkan banyak maklumat berguna daripada data di atas, seperti kelewatan rangkaian sebagai responseEnd – fetchStart, masa memuatkan halaman sebagai loadEventEnd – responseEnd, dan navigasi pemprosesan dan masa memuatkan halaman sebagai loadEventEnd – navigationStart.

Seperti yang anda lihat, sifat Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript.memory juga boleh memaparkan penggunaan data memori JavaScript, seperti jumlah saiz timbunan.

Untuk butiran lanjut tentang API Pemasa Navigasi, baca Mengukur Kelajuan Muatan Halaman Dengan Pemasa Navigasi Sam Dutton.

TENTANG:MEMORY dan TENTANG:MENJEJAK

Perihal:penjejakan dalam Chrome menyediakan paparan prestasi penyemak imbas, merekodkan semua urutan, halaman tab dan proses Chrome.

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

About:Tracing提供了浏览器的性能视图

Alat ini benar-benar berguna untuk membolehkan anda menangkap data berjalan Chrome supaya anda boleh melaraskan pelaksanaan JavaScript dengan sewajarnya atau mengoptimumkan pemuatan sumber.

Lilli Thompson mempunyai artikel yang ditulis untuk pembangun permainan tentang menggunakan about:Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript untuk menganalisis permainan WebGL. Ia juga sesuai untuk pembangun JavaScript.

Anda boleh memasukkan about:memory dalam bar navigasi Chrome, yang juga sangat praktikal Anda boleh mendapatkan penggunaan memori setiap halaman tab, yang sangat membantu untuk mengesan kebocoran memori.

Ringkasan

Kami melihat bahawa terdapat banyak perangkap tersembunyi dalam dunia JavaScript, dan tiada peluru perak untuk meningkatkan prestasi. Hanya dengan menggunakan beberapa penyelesaian pengoptimuman secara menyeluruh pada persekitaran ujian (dunia nyata) boleh keuntungan prestasi maksimum diperoleh. Walaupun begitu, memahami cara enjin mentafsir dan mengoptimumkan kod boleh membantu anda menala aplikasi anda.

Ukur, fahami, betulkan. Teruskan ulangi proses ini.

Menulis JavaScript Berprestasi Tinggi (Terjemahan)_Petua Javascript

Sumber imej: Sally Hunter

Ingat untuk memberi perhatian kepada pengoptimuman, tetapi anda boleh membuang beberapa pengoptimuman kecil untuk kemudahan. Sebagai contoh, sesetengah pembangun memilih .forEach dan Object.keys dan bukannya untuk dan untuk..dalam gelung, yang lebih perlahan tetapi lebih mudah digunakan. Pastikan anda mempunyai fikiran yang jelas dan mengetahui pengoptimuman yang diperlukan dan pengoptimuman yang tidak.

Juga ambil perhatian bahawa semasa enjin JavaScript semakin pantas, kesesakan sebenar seterusnya ialah DOM. Pengurangan aliran semula dan lukis semula juga penting, jadi jangan sentuh DOM sehingga perlu. Perkara lain yang perlu diberi perhatian ialah permintaan HTTP adalah berharga, terutamanya pada terminal mudah alih, jadi cache HTTP harus digunakan untuk mengurangkan pemuatan sumber.

Mengingat perkara ini akan memastikan anda mendapat kebanyakan maklumat dalam artikel ini, saya harap ia akan membantu anda!

Teks asal: http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/

Pengarang: Addy Osmani

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn